From 4727a6c3b166de455c07261c60f68fd673ad55dc Mon Sep 17 00:00:00 2001 From: Santiago Pericas-Geertsen Date: Thu, 5 Sep 2024 10:03:08 -0400 Subject: [PATCH 1/2] Adds support for HTTP health checks, including a custom one to verify if gRPC endpoint is ready. Relocates resources from test to main. New test. --- examples/webserver/grpc/pom.xml | 12 +++ .../examples/webserver/grpc/GrpcMain.java | 20 ++++- .../grpc/StringServiceHealthCheck.java | 78 ++++++++++++++++++ .../grpc/src/main/resources/application.yaml | 7 +- .../src/{test => main}/resources/client.p12 | Bin .../resources/logging.properties | 0 .../src/{test => main}/resources/server.p12 | Bin .../webserver/grpc/StringServiceTest.java | 17 ++++ .../grpc/src/test/resources/application.yaml | 5 ++ 9 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 examples/webserver/grpc/src/main/java/io/helidon/examples/webserver/grpc/StringServiceHealthCheck.java rename examples/webserver/grpc/src/{test => main}/resources/client.p12 (100%) rename examples/webserver/grpc/src/{test => main}/resources/logging.properties (100%) rename examples/webserver/grpc/src/{test => main}/resources/server.p12 (100%) diff --git a/examples/webserver/grpc/pom.xml b/examples/webserver/grpc/pom.xml index 7485f246f..d462c0be5 100644 --- a/examples/webserver/grpc/pom.xml +++ b/examples/webserver/grpc/pom.xml @@ -46,6 +46,10 @@ io.grpc grpc-api + + io.helidon.config + helidon-config-yaml + io.helidon.grpc helidon-grpc-core @@ -54,6 +58,14 @@ io.helidon.webserver helidon-webserver-grpc + + io.helidon.webserver.observe + helidon-webserver-observe-health + + + io.helidon.health + helidon-health-checks + com.google.protobuf protobuf-java diff --git a/examples/webserver/grpc/src/main/java/io/helidon/examples/webserver/grpc/GrpcMain.java b/examples/webserver/grpc/src/main/java/io/helidon/examples/webserver/grpc/GrpcMain.java index f39b1b978..632d14bc3 100644 --- a/examples/webserver/grpc/src/main/java/io/helidon/examples/webserver/grpc/GrpcMain.java +++ b/examples/webserver/grpc/src/main/java/io/helidon/examples/webserver/grpc/GrpcMain.java @@ -16,9 +16,12 @@ package io.helidon.examples.webserver.grpc; +import io.helidon.config.Config; import io.helidon.logging.common.LogConfig; import io.helidon.webserver.WebServer; import io.helidon.webserver.grpc.GrpcRouting; +import io.helidon.webserver.observe.ObserveFeature; +import io.helidon.webserver.observe.health.HealthObserver; class GrpcMain { @@ -33,9 +36,24 @@ private GrpcMain() { public static void main(String[] args) { LogConfig.configureRuntime(); + // initialize global config from default configuration + Config config = Config.create(); + Config.global(config); + Config serverConfig = config.get("server"); + + // create a health check to verify gRPC endpoint + ObserveFeature observe = ObserveFeature.builder() + .addObserver(HealthObserver.builder() + .details(true) + .addCheck(new StringServiceHealthCheck(serverConfig)) + .build()) + .build(); + + // start server and register gRPC routing and health check WebServer.builder() - .port(8080) + .config(serverConfig) .addRouting(GrpcRouting.builder().service(new StringService())) + .addFeature(observe) .build() .start(); } diff --git a/examples/webserver/grpc/src/main/java/io/helidon/examples/webserver/grpc/StringServiceHealthCheck.java b/examples/webserver/grpc/src/main/java/io/helidon/examples/webserver/grpc/StringServiceHealthCheck.java new file mode 100644 index 000000000..27594229c --- /dev/null +++ b/examples/webserver/grpc/src/main/java/io/helidon/examples/webserver/grpc/StringServiceHealthCheck.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.examples.webserver.grpc; + +import io.helidon.common.configurable.Resource; +import io.helidon.common.tls.Tls; +import io.helidon.config.Config; +import io.helidon.health.HealthCheck; +import io.helidon.health.HealthCheckResponse; +import io.helidon.health.HealthCheckType; +import io.helidon.webclient.api.WebClient; +import io.helidon.webclient.grpc.GrpcClient; + +class StringServiceHealthCheck implements HealthCheck { + + private final WebClient webClient; + + StringServiceHealthCheck(Config config) { + Tls clientTls = Tls.builder() + .trust(trust -> trust + .keystore(store -> store + .passphrase("password") + .trustStore(true) + .keystore(Resource.create("client.p12")))) + .build(); + int serverPort = config.get("port").asInt().get(); + this.webClient = WebClient.builder() + .tls(clientTls) + .baseUri("https://localhost:" + serverPort) + .build(); + } + + @Override + public String name() { + return StringService.class.getSimpleName(); + } + + @Override + public HealthCheckType type() { + return HealthCheckType.READINESS; + } + + /** + * Self-invocation to verify gRPC endpoint is available and ready. + * + * @return health check response + */ + @Override + public HealthCheckResponse call() { + try { + GrpcClient grpcClient = webClient.client(GrpcClient.PROTOCOL); + StringServiceGrpc.StringServiceBlockingStub service = + StringServiceGrpc.newBlockingStub(grpcClient.channel()); + Strings.StringMessage res = service.upper( + Strings.StringMessage.newBuilder().setText("hello").build()); + return HealthCheckResponse.builder() + .status(res.getText().equals("HELLO")) + .get(); + } catch (Exception e) { + return HealthCheckResponse.builder() + .status(false) + .get(); + } + } +} diff --git a/examples/webserver/grpc/src/main/resources/application.yaml b/examples/webserver/grpc/src/main/resources/application.yaml index 00b3aef2e..bf1f6e3b8 100644 --- a/examples/webserver/grpc/src/main/resources/application.yaml +++ b/examples/webserver/grpc/src/main/resources/application.yaml @@ -15,7 +15,7 @@ # server: - port: 0 + port: 8080 tls: trust: keystore: @@ -28,3 +28,8 @@ server: passphrase: "password" resource: resource-path: "server.p12" + features: + observe: + observers: + health: + details: true diff --git a/examples/webserver/grpc/src/test/resources/client.p12 b/examples/webserver/grpc/src/main/resources/client.p12 similarity index 100% rename from examples/webserver/grpc/src/test/resources/client.p12 rename to examples/webserver/grpc/src/main/resources/client.p12 diff --git a/examples/webserver/grpc/src/test/resources/logging.properties b/examples/webserver/grpc/src/main/resources/logging.properties similarity index 100% rename from examples/webserver/grpc/src/test/resources/logging.properties rename to examples/webserver/grpc/src/main/resources/logging.properties diff --git a/examples/webserver/grpc/src/test/resources/server.p12 b/examples/webserver/grpc/src/main/resources/server.p12 similarity index 100% rename from examples/webserver/grpc/src/test/resources/server.p12 rename to examples/webserver/grpc/src/main/resources/server.p12 diff --git a/examples/webserver/grpc/src/test/java/io/helidon/examples/webserver/grpc/StringServiceTest.java b/examples/webserver/grpc/src/test/java/io/helidon/examples/webserver/grpc/StringServiceTest.java index 4929efa2e..359fc493c 100644 --- a/examples/webserver/grpc/src/test/java/io/helidon/examples/webserver/grpc/StringServiceTest.java +++ b/examples/webserver/grpc/src/test/java/io/helidon/examples/webserver/grpc/StringServiceTest.java @@ -26,6 +26,8 @@ import io.helidon.common.configurable.Resource; import io.helidon.common.tls.Tls; +import io.helidon.http.Status; +import io.helidon.webclient.api.HttpClientResponse; import io.helidon.webclient.api.WebClient; import io.helidon.webclient.grpc.GrpcClient; import io.helidon.webserver.Router; @@ -38,7 +40,9 @@ import io.grpc.stub.StreamObserver; import org.junit.jupiter.api.Test; +import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; /** @@ -132,6 +136,19 @@ void testUnaryUpperInterceptor() { assertThat(res.getText(), is("[[HELLO]]")); } + /** + * Tests server health using HTTP, not gRPC. + */ + @Test + void testHealthHttp() { + try (HttpClientResponse res = webClient.get("/observe/health").request()) { + assertThat(res.status(), is(Status.OK_200)); + String value = res.as(String.class); + assertThat(value, containsString("UP")); + assertThat(value, not(containsString("DOWN"))); + } + } + static Strings.StringMessage newStringMessage(String data) { return Strings.StringMessage.newBuilder().setText(data).build(); } diff --git a/examples/webserver/grpc/src/test/resources/application.yaml b/examples/webserver/grpc/src/test/resources/application.yaml index 78f88534a..fa5b0ee7f 100644 --- a/examples/webserver/grpc/src/test/resources/application.yaml +++ b/examples/webserver/grpc/src/test/resources/application.yaml @@ -28,6 +28,11 @@ server: passphrase: "password" resource: resource-path: "server.p12" + features: + observe: + observers: + health: + details: true grpc-client: poll-wait-time: PT30S From 21f97f7b916e403ee5d1dfbcbdfa27b7d1429c5c Mon Sep 17 00:00:00 2001 From: Santiago Pericas-Geertsen Date: Fri, 6 Sep 2024 11:47:16 -0400 Subject: [PATCH 2/2] Improves health check action to use a scheduled task to check readiness periodically. Use config to init HealthObserver. Signed-off-by: Santiago Pericas-Geertsen --- examples/webserver/grpc/pom.xml | 4 ++ .../examples/webserver/grpc/GrpcMain.java | 2 +- .../grpc/StringServiceHealthCheck.java | 45 ++++++++++++++----- 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/examples/webserver/grpc/pom.xml b/examples/webserver/grpc/pom.xml index d462c0be5..6c5ed8d9c 100644 --- a/examples/webserver/grpc/pom.xml +++ b/examples/webserver/grpc/pom.xml @@ -66,6 +66,10 @@ io.helidon.health helidon-health-checks + + io.helidon.scheduling + helidon-scheduling + com.google.protobuf protobuf-java diff --git a/examples/webserver/grpc/src/main/java/io/helidon/examples/webserver/grpc/GrpcMain.java b/examples/webserver/grpc/src/main/java/io/helidon/examples/webserver/grpc/GrpcMain.java index 632d14bc3..3f970f99a 100644 --- a/examples/webserver/grpc/src/main/java/io/helidon/examples/webserver/grpc/GrpcMain.java +++ b/examples/webserver/grpc/src/main/java/io/helidon/examples/webserver/grpc/GrpcMain.java @@ -44,7 +44,7 @@ public static void main(String[] args) { // create a health check to verify gRPC endpoint ObserveFeature observe = ObserveFeature.builder() .addObserver(HealthObserver.builder() - .details(true) + .config(serverConfig.get("features.observe.observers.health")) .addCheck(new StringServiceHealthCheck(serverConfig)) .build()) .build(); diff --git a/examples/webserver/grpc/src/main/java/io/helidon/examples/webserver/grpc/StringServiceHealthCheck.java b/examples/webserver/grpc/src/main/java/io/helidon/examples/webserver/grpc/StringServiceHealthCheck.java index 27594229c..9ddb19a77 100644 --- a/examples/webserver/grpc/src/main/java/io/helidon/examples/webserver/grpc/StringServiceHealthCheck.java +++ b/examples/webserver/grpc/src/main/java/io/helidon/examples/webserver/grpc/StringServiceHealthCheck.java @@ -15,20 +15,27 @@ */ package io.helidon.examples.webserver.grpc; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + import io.helidon.common.configurable.Resource; import io.helidon.common.tls.Tls; import io.helidon.config.Config; import io.helidon.health.HealthCheck; import io.helidon.health.HealthCheckResponse; import io.helidon.health.HealthCheckType; +import io.helidon.scheduling.Scheduling; import io.helidon.webclient.api.WebClient; import io.helidon.webclient.grpc.GrpcClient; class StringServiceHealthCheck implements HealthCheck { private final WebClient webClient; + private CountDownLatch latch; + private volatile boolean readiness = true; StringServiceHealthCheck(Config config) { + // set up client to access gRPC service Tls clientTls = Tls.builder() .trust(trust -> trust .keystore(store -> store @@ -53,26 +60,44 @@ public HealthCheckType type() { return HealthCheckType.READINESS; } + @Override + public HealthCheckResponse call() { + if (latch == null) { + latch = new CountDownLatch(1); + Scheduling.fixedRate() // task to check for readiness + .delay(1) + .initialDelay(0) + .timeUnit(TimeUnit.MINUTES) + .task(i -> checkReadiness()) + .build(); + } + try { + boolean check = latch.await(5, TimeUnit.SECONDS); + return HealthCheckResponse.builder() + .status(check && readiness) + .get(); + } catch (Exception e) { + return HealthCheckResponse.builder() + .status(false) + .get(); + } + } + /** * Self-invocation to verify gRPC endpoint is available and ready. - * - * @return health check response */ - @Override - public HealthCheckResponse call() { + private void checkReadiness() { try { GrpcClient grpcClient = webClient.client(GrpcClient.PROTOCOL); StringServiceGrpc.StringServiceBlockingStub service = StringServiceGrpc.newBlockingStub(grpcClient.channel()); Strings.StringMessage res = service.upper( Strings.StringMessage.newBuilder().setText("hello").build()); - return HealthCheckResponse.builder() - .status(res.getText().equals("HELLO")) - .get(); + readiness = res.getText().equals("HELLO"); } catch (Exception e) { - return HealthCheckResponse.builder() - .status(false) - .get(); + readiness = false; + } finally { + latch.countDown(); } } }