From 4c559986f03f207b22af1271e0c61160d3c02084 Mon Sep 17 00:00:00 2001 From: xstefank Date: Thu, 12 Oct 2023 10:24:41 +0200 Subject: [PATCH] Unblock SmallRye Health exposed routes --- extensions/smallrye-health/deployment/pom.xml | 5 + .../deployment/SmallRyeHealthProcessor.java | 21 ---- .../health/test/BlockingNonBlockingTest.java | 106 ++++++++++++++++++ .../QuarkusAsyncHealthCheckFactory.java | 7 +- .../runtime/SmallRyeHealthGroupHandler.java | 5 +- .../health/runtime/SmallRyeHealthHandler.java | 5 +- .../runtime/SmallRyeHealthHandlerBase.java | 36 +++--- .../SmallRyeIndividualHealthGroupHandler.java | 5 +- .../runtime/SmallRyeLivenessHandler.java | 5 +- .../runtime/SmallRyeReadinessHandler.java | 5 +- .../runtime/SmallRyeStartupHandler.java | 5 +- .../runtime/SmallRyeWellnessHandler.java | 5 +- 12 files changed, 155 insertions(+), 55 deletions(-) create mode 100644 extensions/smallrye-health/deployment/src/test/java/io/quarkus/smallrye/health/test/BlockingNonBlockingTest.java diff --git a/extensions/smallrye-health/deployment/pom.xml b/extensions/smallrye-health/deployment/pom.xml index 500367c3c058d..809b86543156c 100644 --- a/extensions/smallrye-health/deployment/pom.xml +++ b/extensions/smallrye-health/deployment/pom.xml @@ -51,6 +51,11 @@ quarkus-junit5-internal test + + io.quarkus + quarkus-scheduler-deployment + test + io.rest-assured rest-assured 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 3899c03b69169..69d292bd81634 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 @@ -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; @@ -217,7 +216,6 @@ public void defineHealthRoutes(BuildProducer routes, .routeConfigKey("quarkus.smallrye-health.root-path") .handler(new SmallRyeHealthHandler()) .displayOnNotFoundPage() - .blockingRoute() .build()); // Register the liveness handler @@ -226,7 +224,6 @@ public void defineHealthRoutes(BuildProducer routes, .nestedRoute(healthConfig.rootPath, healthConfig.livenessPath) .handler(new SmallRyeLivenessHandler()) .displayOnNotFoundPage() - .blockingRoute() .build()); // Register the readiness handler @@ -235,29 +232,14 @@ public void defineHealthRoutes(BuildProducer routes, .nestedRoute(healthConfig.rootPath, healthConfig.readinessPath) .handler(new SmallRyeReadinessHandler()) .displayOnNotFoundPage() - .blockingRoute() .build()); - // 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(nonApplicationRootPathBuildItem.routeBuilder() .management("quarkus.smallrye-health.management.enabled") .nestedRoute(healthConfig.rootPath, healthConfig.groupPath) .handler(new SmallRyeHealthGroupHandler()) .displayOnNotFoundPage() - .blockingRoute() .build()); SmallRyeIndividualHealthGroupHandler handler = new SmallRyeIndividualHealthGroupHandler(); @@ -266,7 +248,6 @@ public void defineHealthRoutes(BuildProducer routes, .nestedRoute(healthConfig.rootPath, healthConfig.groupPath + "/*") .handler(handler) .displayOnNotFoundPage() - .blockingRoute() .build()); // Register the wellness handler @@ -275,7 +256,6 @@ public void defineHealthRoutes(BuildProducer routes, .nestedRoute(healthConfig.rootPath, healthConfig.wellnessPath) .handler(new SmallRyeWellnessHandler()) .displayOnNotFoundPage() - .blockingRoute() .build()); // Register the startup handler @@ -284,7 +264,6 @@ public void defineHealthRoutes(BuildProducer routes, .nestedRoute(healthConfig.rootPath, healthConfig.startupPath) .handler(new SmallRyeStartupHandler()) .displayOnNotFoundPage() - .blockingRoute() .build()); } diff --git a/extensions/smallrye-health/deployment/src/test/java/io/quarkus/smallrye/health/test/BlockingNonBlockingTest.java b/extensions/smallrye-health/deployment/src/test/java/io/quarkus/smallrye/health/test/BlockingNonBlockingTest.java new file mode 100644 index 0000000000000..05b4da5bc3611 --- /dev/null +++ b/extensions/smallrye-health/deployment/src/test/java/io/quarkus/smallrye/health/test/BlockingNonBlockingTest.java @@ -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(); + } + } +} diff --git a/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/QuarkusAsyncHealthCheckFactory.java b/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/QuarkusAsyncHealthCheckFactory.java index de343b4f38b85..cfec52c933b1b 100644 --- a/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/QuarkusAsyncHealthCheckFactory.java +++ b/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/QuarkusAsyncHealthCheckFactory.java @@ -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; @@ -28,14 +27,12 @@ public QuarkusAsyncHealthCheckFactory(Vertx vertx) { @Override public Uni callSync(HealthCheck healthCheck) { Uni healthCheckResponseUni = super.callSync(healthCheck); - return BlockingOperationControl.isBlockingAllowed() ? healthCheckResponseUni - : healthCheckResponseUni.runSubscriptionOn(MutinyHelper.blockingExecutor(vertx, false)); + return healthCheckResponseUni.runSubscriptionOn(MutinyHelper.blockingExecutor(vertx, false)); } @Override public Uni callAsync(AsyncHealthCheck asyncHealthCheck) { Uni healthCheckResponseUni = super.callAsync(asyncHealthCheck); - return !BlockingOperationControl.isBlockingAllowed() ? healthCheckResponseUni - : healthCheckResponseUni.runSubscriptionOn(MutinyHelper.executor(vertx)); + return healthCheckResponseUni.runSubscriptionOn(MutinyHelper.executor(vertx)); } } 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 index 84c5c6fa62d0c..95b87746c1b08 100644 --- 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 @@ -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 getHealth(SmallRyeHealthReporter reporter, RoutingContext ctx) { + return reporter.getHealthGroupsAsync(); } } diff --git a/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeHealthHandler.java b/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeHealthHandler.java index 6960bb284bce9..6d9d33066e8fb 100644 --- a/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeHealthHandler.java +++ b/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeHealthHandler.java @@ -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 getHealth(SmallRyeHealthReporter reporter, RoutingContext ctx) { + return reporter.getHealthAsync(); } } diff --git a/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeHealthHandlerBase.java b/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeHealthHandlerBase.java index fff1485398fbc..e999375418769 100644 --- a/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeHealthHandlerBase.java +++ b/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeHealthHandlerBase.java @@ -10,7 +10,11 @@ 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; @@ -18,7 +22,7 @@ abstract class SmallRyeHealthHandlerBase implements Handler { - protected abstract SmallRyeHealth getHealth(SmallRyeHealthReporter reporter, RoutingContext routingContext); + protected abstract Uni getHealth(SmallRyeHealthReporter reporter, RoutingContext routingContext); @Override public void handle(RoutingContext ctx) { @@ -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); + } + }); } - } 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 index 66f960791ad8d..e0c7ba3874439 100644 --- 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 @@ -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 getHealth(SmallRyeHealthReporter reporter, RoutingContext ctx) { String group = ctx.normalizedPath().substring(ctx.normalizedPath().lastIndexOf("/") + 1); - return reporter.getHealthGroup(group); + return reporter.getHealthGroupAsync(group); } } diff --git a/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeLivenessHandler.java b/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeLivenessHandler.java index a5cf3dd904cbe..ad33e824ff3d7 100644 --- a/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeLivenessHandler.java +++ b/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeLivenessHandler.java @@ -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 getHealth(SmallRyeHealthReporter reporter, RoutingContext ctx) { + return reporter.getLivenessAsync(); } } diff --git a/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeReadinessHandler.java b/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeReadinessHandler.java index a23a3e1f9d538..18c652bd673bd 100644 --- a/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeReadinessHandler.java +++ b/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeReadinessHandler.java @@ -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 getHealth(SmallRyeHealthReporter reporter, RoutingContext ctx) { + return reporter.getReadinessAsync(); } } diff --git a/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeStartupHandler.java b/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeStartupHandler.java index c450430735ecb..cd1ae14846cc9 100644 --- a/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeStartupHandler.java +++ b/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeStartupHandler.java @@ -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 getHealth(SmallRyeHealthReporter reporter, RoutingContext ctx) { + return reporter.getStartupAsync(); } } diff --git a/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeWellnessHandler.java b/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeWellnessHandler.java index 84ca3860c1cae..e2131f51de416 100644 --- a/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeWellnessHandler.java +++ b/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeWellnessHandler.java @@ -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 getHealth(SmallRyeHealthReporter reporter, RoutingContext ctx) { + return reporter.getWellnessAsync(); } }