Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unblock SmallRye Health exposed routes #37352

Merged
merged 1 commit into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,78 @@
package io.quarkus.smallrye.health.test;

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.is;

import java.time.Duration;

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.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusUnitTest;
import io.quarkus.vertx.web.Route;
import io.restassured.RestAssured;
import io.restassured.parsing.Parser;
import io.smallrye.common.annotation.Blocking;
import io.smallrye.health.SmallRyeHealthReporter;
import io.smallrye.mutiny.Uni;

public class BlockingNonBlockingTest {

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

@Test
public void testRegisterHealthOnBlockingThreadStep1() {
// initial startup health blocking call on worker thread
given()
.when().get("/start-health")
.then().statusCode(200);

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();
}
}

@Liveness
static final class BlockingHealthCheck implements HealthCheck {
@Override
public HealthCheckResponse call() {
// await() is illegal on the executor thread
Uni.createFrom().item(42).onItem().delayIt().by(Duration.ofMillis(10)).await().indefinitely();
return HealthCheckResponse.up("blocking");
}
}

@ApplicationScoped
static final class Routes {

@Inject
SmallRyeHealthReporter smallRyeHealthReporter;

@Route(path = "/start-health", methods = Route.HttpMethod.GET)
@Blocking
public String health() {
return smallRyeHealthReporter.getHealth().toString();
}
}
}
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();
}
}
Loading