commit 0c7e31bb3933116042c231d25433102eb868e896 Author: Julien Lengrand-Lambert Date: Mon Jun 7 21:20:49 2021 +0200 Create repo diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..c8b241f --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +target/* \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..397690d --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +.idea +target +.classpath +.project +.settings \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..89f3f6f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,30 @@ + +# 1st stage, build the app +FROM maven:3.6-jdk-11 as build + +WORKDIR /helidon + +# Create a first layer to cache the "Maven World" in the local repository. +# Incremental docker builds will always resume after that, unless you update +# the pom +ADD pom.xml . +RUN mvn package -Dmaven.test.skip -Declipselink.weave.skip + +# Do the Maven build! +# Incremental docker builds will resume here when you change sources +ADD src src +RUN mvn package -DskipTests + +RUN echo "done!" + +# 2nd stage, build the runtime image +FROM openjdk:11-jre-slim +WORKDIR /helidon + +# Copy the binary built in the 1st stage +COPY --from=build /helidon/target/helidon-quickstart-se.jar ./ +COPY --from=build /helidon/target/libs ./libs + +CMD ["java", "-jar", "helidon-quickstart-se.jar"] + +EXPOSE 8080 diff --git a/Dockerfile.jlink b/Dockerfile.jlink new file mode 100644 index 0000000..ad87dfc --- /dev/null +++ b/Dockerfile.jlink @@ -0,0 +1,25 @@ + +# 1st stage, build the app +FROM maven:3.6.3-jdk-11-slim as build + +WORKDIR /helidon + +# Create a first layer to cache the "Maven World" in the local repository. +# Incremental docker builds will always resume after that, unless you update +# the pom +ADD pom.xml . +RUN mvn package -Dmaven.test.skip -Declipselink.weave.skip + +# Do the Maven build to create the custom Java Runtime Image +# Incremental docker builds will resume here when you change sources +ADD src src +RUN mvn -Ddocker.build=true package -Pjlink-image -DskipTests +RUN echo "done!" + +# 2nd stage, build the final image with the JRI built in the 1st stage + +FROM debian:stretch-slim +WORKDIR /helidon +COPY --from=build /helidon/target/helidon-quickstart-se-jri ./ +ENTRYPOINT ["/bin/bash", "/helidon/bin/start"] +EXPOSE 8080 diff --git a/Dockerfile.native b/Dockerfile.native new file mode 100644 index 0000000..45b2646 --- /dev/null +++ b/Dockerfile.native @@ -0,0 +1,29 @@ + +# 1st stage, build the app +FROM helidon/jdk11-graalvm-maven:21.0.0 as build + +WORKDIR /helidon + +# Create a first layer to cache the "Maven World" in the local repository. +# Incremental docker builds will always resume after that, unless you update +# the pom +ADD pom.xml . +RUN mvn package -Pnative-image -Dnative.image.skip -Dmaven.test.skip -Declipselink.weave.skip + +# Do the Maven build! +# Incremental docker builds will resume here when you change sources +ADD src src +RUN mvn package -Pnative-image -Dnative.image.buildStatic -DskipTests + +RUN echo "done!" + +# 2nd stage, build the runtime image +FROM scratch +WORKDIR /helidon + +# Copy the binary built in the 1st stage +COPY --from=build /helidon/target/helidon-quickstart-se . + +ENTRYPOINT ["./helidon-quickstart-se"] + +EXPOSE 8080 diff --git a/README.md b/README.md new file mode 100644 index 0000000..edaf50a --- /dev/null +++ b/README.md @@ -0,0 +1,175 @@ +# Helidon Quickstart SE + +Sample Helidon SE project that includes multiple REST operations. + +## Build and run + +With JDK11+ +```bash +mvn package +java -jar target/helidon-quickstart-se.jar +``` + +## Exercise the application + +``` +curl -X GET http://localhost:8080/greet +{"message":"Hello World!"} + +curl -X GET http://localhost:8080/greet/Joe +{"message":"Hello Joe!"} + +curl -X PUT -H "Content-Type: application/json" -d '{"greeting" : "Hola"}' http://localhost:8080/greet/greeting + +curl -X GET http://localhost:8080/greet/Jose +{"message":"Hola Jose!"} +``` + +## Try health and metrics + +``` +curl -s -X GET http://localhost:8080/health +{"outcome":"UP",... +. . . + +# Prometheus Format +curl -s -X GET http://localhost:8080/metrics +# TYPE base:gc_g1_young_generation_count gauge +. . . + +# JSON Format +curl -H 'Accept: application/json' -X GET http://localhost:8080/metrics +{"base":... +. . . + +``` + +## Build the Docker Image + +``` +docker build -t helidon-quickstart-se . +``` + +## Start the application with Docker + +``` +docker run --rm -p 8080:8080 helidon-quickstart-se:latest +``` + +Exercise the application as described above + +## Deploy the application to Kubernetes + +``` +kubectl cluster-info # Verify which cluster +kubectl get pods # Verify connectivity to cluster +kubectl create -f app.yaml # Deploy application +kubectl get pods # Wait for quickstart pod to be RUNNING +kubectl get service helidon-quickstart-se # Get service info +``` + +Note the PORTs. You can now exercise the application as you did before but use the second +port number (the NodePort) instead of 8080. + +After you’re done, cleanup. + +``` +kubectl delete -f app.yaml +``` + +## Build a native image with GraalVM + +GraalVM allows you to compile your programs ahead-of-time into a native + executable. See https://www.graalvm.org/docs/reference-manual/aot-compilation/ + for more information. + +You can build a native executable in 2 different ways: +* With a local installation of GraalVM +* Using Docker + +### Local build + +Download Graal VM at https://www.graalvm.org/downloads, the versions + currently supported for Helidon are `20.1.0` and above. + +``` +# Setup the environment +export GRAALVM_HOME=/path +# build the native executable +mvn package -Pnative-image +``` + +You can also put the Graal VM `bin` directory in your PATH, or pass + `-DgraalVMHome=/path` to the Maven command. + +See https://github.com/oracle/helidon-build-tools/tree/master/helidon-maven-plugin#goal-native-image + for more information. + +Start the application: + +``` +./target/helidon-quickstart-se +``` + +### Multi-stage Docker build + +Build the "native" Docker Image + +``` +docker build -t helidon-quickstart-se-native -f Dockerfile.native . +``` + +Start the application: + +``` +docker run --rm -p 8080:8080 helidon-quickstart-se-native:latest +``` + +## Build a Java Runtime Image using jlink + +You can build a custom Java Runtime Image (JRI) containing the application jars and the JDK modules +on which they depend. This image also: + +* Enables Class Data Sharing by default to reduce startup time. +* Contains a customized `start` script to simplify CDS usage and support debug and test modes. + +You can build a custom JRI in two different ways: +* Local +* Using Docker + + +### Local build + +``` +# build the JRI +mvn package -Pjlink-image +``` + +See https://github.com/oracle/helidon-build-tools/tree/master/helidon-maven-plugin#goal-jlink-image + for more information. + +Start the application: + +``` +./target/helidon-quickstart-se-jri/bin/start +``` + +### Multi-stage Docker build + +Build the JRI as a Docker Image + +``` +docker build -t helidon-quickstart-se-jri -f Dockerfile.jlink . +``` + +Start the application: + +``` +docker run --rm -p 8080:8080 helidon-quickstart-se-jri:latest +``` + +See the start script help: + +``` +docker run --rm helidon-quickstart-se-jri:latest --help +``` diff --git a/app.yaml b/app.yaml new file mode 100644 index 0000000..3249d3c --- /dev/null +++ b/app.yaml @@ -0,0 +1,53 @@ +# +# Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. +# +# 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. +# + +kind: Service +apiVersion: v1 +metadata: + name: helidon-quickstart-se + labels: + app: helidon-quickstart-se +spec: + type: NodePort + selector: + app: helidon-quickstart-se + ports: + - port: 8080 + targetPort: 8080 + name: http +--- +kind: Deployment +apiVersion: apps/v1 +metadata: + name: helidon-quickstart-se +spec: + replicas: 1 + selector: + matchLabels: + app: helidon-quickstart-se + template: + metadata: + labels: + app: helidon-quickstart-se + version: v1 + spec: + containers: + - name: helidon-quickstart-se + image: helidon-quickstart-se + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8080 +--- diff --git a/helidon-quickstart-se.iml b/helidon-quickstart-se.iml new file mode 100644 index 0000000..4836172 --- /dev/null +++ b/helidon-quickstart-se.iml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jreleaser.yml b/jreleaser.yml new file mode 100644 index 0000000..4c87930 --- /dev/null +++ b/jreleaser.yml @@ -0,0 +1,23 @@ +project: + name: helidon-quickstart-se + version: 1.0.0-SNAPSHOT + description: helidon-quickstart-se + longDescription: A simple helidon-quickstart-se quickstart + website: https://acme.com/app + authors: + - jlengrand + license: Apache-2 + java: + groupId: io.helidon.examples.quickstart.se + version: 8 + extraProperties: + inceptionYear: 2021 + +release: + github: + owner: jlengrand + +distributions: + app: + artifacts: + - path: target/helidon-quickstart-se.jar diff --git a/out/jreleaser/checksums/app/helidon-quickstart-se.jar.sha256 b/out/jreleaser/checksums/app/helidon-quickstart-se.jar.sha256 new file mode 100644 index 0000000..f4c0034 --- /dev/null +++ b/out/jreleaser/checksums/app/helidon-quickstart-se.jar.sha256 @@ -0,0 +1 @@ +18d8e2a55406eb15cfe3da97abfd49b041be168adad8dcd1cb619667b61a992b \ No newline at end of file diff --git a/out/jreleaser/checksums/checksums_sha256.txt b/out/jreleaser/checksums/checksums_sha256.txt new file mode 100644 index 0000000..eccee53 --- /dev/null +++ b/out/jreleaser/checksums/checksums_sha256.txt @@ -0,0 +1 @@ +18d8e2a55406eb15cfe3da97abfd49b041be168adad8dcd1cb619667b61a992b app/helidon-quickstart-se.jar diff --git a/out/jreleaser/output.properties b/out/jreleaser/output.properties new file mode 100644 index 0000000..750a4e6 --- /dev/null +++ b/out/jreleaser/output.properties @@ -0,0 +1,16 @@ +#JReleaser 0.4.0 +#Mon Jun 07 21:19:42 CEST 2021 +commitFullHash=ea792e845c2583781817fec4f7286cb0fa2bcce5 +commitShortHash=ea792e8 +javaVersion=11.0.10 +milestoneName=early-access +projectName=helidon-quickstart-se +projectSnapshot=true +projectVersion=1.0.0-SNAPSHOT +projectVersionMajor=1 +projectVersionMinor=0 +projectVersionPatch=0 +projectVersionTag=SNAPSHOT +releaseName=Release early-access +tagName=early-access +timestamp=2021-06-07T21\:19\:39.915208+02\:00 diff --git a/out/jreleaser/release/CHANGELOG.md b/out/jreleaser/release/CHANGELOG.md new file mode 100644 index 0000000..7687789 --- /dev/null +++ b/out/jreleaser/release/CHANGELOG.md @@ -0,0 +1,2 @@ +## Changelog + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..624b4ed --- /dev/null +++ b/pom.xml @@ -0,0 +1,80 @@ + + + 4.0.0 + + io.helidon.applications + helidon-se + 2.3.0 + + + io.helidon.examples + helidon-quickstart-se + 1.0-SNAPSHOT + myproject + + + io.helidon.examples.quickstart.se.Main + + + + + io.helidon.webserver + helidon-webserver + + + io.helidon.media + helidon-media-jsonp + + + io.helidon.config + helidon-config-yaml + + + io.helidon.health + helidon-health + + + io.helidon.health + helidon-health-checks + + + io.helidon.metrics + helidon-metrics + + + org.junit.jupiter + junit-jupiter-api + test + + + io.helidon.webclient + helidon-webclient + test + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-libs + + + + + io.helidon.build-tools + helidon-maven-plugin + + + third-party-license-report + + + + + + diff --git a/src/main/java/io/helidon/examples/quickstart/se/GreetService.java b/src/main/java/io/helidon/examples/quickstart/se/GreetService.java new file mode 100644 index 0000000..1dfa74d --- /dev/null +++ b/src/main/java/io/helidon/examples/quickstart/se/GreetService.java @@ -0,0 +1,137 @@ + +package io.helidon.examples.quickstart.se; + +import java.util.Collections; +import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.json.Json; +import javax.json.JsonBuilderFactory; +import javax.json.JsonException; +import javax.json.JsonObject; + +import io.helidon.common.http.Http; +import io.helidon.config.Config; +import io.helidon.webserver.Routing; +import io.helidon.webserver.ServerRequest; +import io.helidon.webserver.ServerResponse; +import io.helidon.webserver.Service; + +/** + * A simple service to greet you. Examples: + * + * Get default greeting message: + * curl -X GET http://localhost:8080/greet + * + * Get greeting message for Joe: + * curl -X GET http://localhost:8080/greet/Joe + * + * Change greeting + * curl -X PUT -H "Content-Type: application/json" -d '{"greeting" : "Howdy"}' http://localhost:8080/greet/greeting + * + * The message is returned as a JSON object + */ + +public class GreetService implements Service { + + /** + * The config value for the key {@code greeting}. + */ + private final AtomicReference greeting = new AtomicReference<>(); + + private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap()); + + private static final Logger LOGGER = Logger.getLogger(GreetService.class.getName()); + + GreetService(Config config) { + greeting.set(config.get("app.greeting").asString().orElse("Ciao")); + } + + /** + * A service registers itself by updating the routing rules. + * @param rules the routing rules. + */ + @Override + public void update(Routing.Rules rules) { + rules + .get("/", this::getDefaultMessageHandler) + .get("/{name}", this::getMessageHandler) + .put("/greeting", this::updateGreetingHandler); + } + + /** + * Return a worldly greeting message. + * @param request the server request + * @param response the server response + */ + private void getDefaultMessageHandler(ServerRequest request, ServerResponse response) { + sendResponse(response, "World"); + } + + /** + * Return a greeting message using the name that was provided. + * @param request the server request + * @param response the server response + */ + private void getMessageHandler(ServerRequest request, ServerResponse response) { + String name = request.path().param("name"); + sendResponse(response, name); + } + + private void sendResponse(ServerResponse response, String name) { + String msg = String.format("%s %s!", greeting.get(), name); + + JsonObject returnObject = JSON.createObjectBuilder() + .add("message", msg) + .build(); + response.send(returnObject); + } + + private static T processErrors(Throwable ex, ServerRequest request, ServerResponse response) { + + if (ex.getCause() instanceof JsonException){ + + LOGGER.log(Level.FINE, "Invalid JSON", ex); + JsonObject jsonErrorObject = JSON.createObjectBuilder() + .add("error", "Invalid JSON") + .build(); + response.status(Http.Status.BAD_REQUEST_400).send(jsonErrorObject); + } else { + + LOGGER.log(Level.FINE, "Internal error", ex); + JsonObject jsonErrorObject = JSON.createObjectBuilder() + .add("error", "Internal error") + .build(); + response.status(Http.Status.INTERNAL_SERVER_ERROR_500).send(jsonErrorObject); + } + + return null; + } + + private void updateGreetingFromJson(JsonObject jo, ServerResponse response) { + if (!jo.containsKey("greeting")) { + JsonObject jsonErrorObject = JSON.createObjectBuilder() + .add("error", "No greeting provided") + .build(); + response.status(Http.Status.BAD_REQUEST_400) + .send(jsonErrorObject); + return; + } + + greeting.set(jo.getString("greeting")); + response.status(Http.Status.NO_CONTENT_204).send(); + } + + /** + * Set the greeting to use in future messages. + * @param request the server request + * @param response the server response + */ + private void updateGreetingHandler(ServerRequest request, + ServerResponse response) { + request.content().as(JsonObject.class) + .thenAccept(jo -> updateGreetingFromJson(jo, response)) + .exceptionally(ex -> processErrors(ex, request, response)); + } +} \ No newline at end of file diff --git a/src/main/java/io/helidon/examples/quickstart/se/Main.java b/src/main/java/io/helidon/examples/quickstart/se/Main.java new file mode 100644 index 0000000..d64930b --- /dev/null +++ b/src/main/java/io/helidon/examples/quickstart/se/Main.java @@ -0,0 +1,86 @@ + +package io.helidon.examples.quickstart.se; + +import io.helidon.common.LogConfig; +import io.helidon.common.reactive.Single; +import io.helidon.config.Config; +import io.helidon.health.HealthSupport; +import io.helidon.health.checks.HealthChecks; +import io.helidon.media.jsonp.JsonpSupport; +import io.helidon.metrics.MetricsSupport; +import io.helidon.webserver.Routing; +import io.helidon.webserver.WebServer; + +/** + * The application main class. + */ +public final class Main { + + /** + * Cannot be instantiated. + */ + private Main() { + } + + /** + * Application main entry point. + * @param args command line arguments. + */ + public static void main(final String[] args) { + startServer(); + } + + /** + * Start the server. + * @return the created {@link WebServer} instance + */ + static Single startServer() { + + // load logging configuration + LogConfig.configureRuntime(); + + // By default this will pick up application.yaml from the classpath + Config config = Config.create(); + + WebServer server = WebServer.builder(createRouting(config)) + .config(config.get("server")) + .addMediaSupport(JsonpSupport.create()) + .build(); + + Single webserver = server.start(); + + // Try to start the server. If successful, print some info and arrange to + // print a message at shutdown. If unsuccessful, print the exception. + webserver.thenAccept(ws -> { + System.out.println("WEB server is up! http://localhost:" + ws.port() + "/greet"); + ws.whenShutdown().thenRun(() -> System.out.println("WEB server is DOWN. Good bye!")); + }) + .exceptionallyAccept(t -> { + System.err.println("Startup failed: " + t.getMessage()); + t.printStackTrace(System.err); + }); + + return webserver; + } + + /** + * Creates new {@link Routing}. + * + * @return routing configured with JSON support, a health check, and a service + * @param config configuration of this server + */ + private static Routing createRouting(Config config) { + + MetricsSupport metrics = MetricsSupport.create(); + GreetService greetService = new GreetService(config); + HealthSupport health = HealthSupport.builder() + .addLiveness(HealthChecks.healthChecks()) // Adds a convenient set of checks + .build(); + + return Routing.builder() + .register(health) // Health at "/health" + .register(metrics) // Metrics at "/metrics" + .register("/greet", greetService) + .build(); + } +} diff --git a/src/main/java/io/helidon/examples/quickstart/se/package-info.java b/src/main/java/io/helidon/examples/quickstart/se/package-info.java new file mode 100644 index 0000000..770cdae --- /dev/null +++ b/src/main/java/io/helidon/examples/quickstart/se/package-info.java @@ -0,0 +1,2 @@ + +package io.helidon.examples.quickstart.se; diff --git a/src/main/resources/META-INF/native-image/reflect-config.json b/src/main/resources/META-INF/native-image/reflect-config.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/src/main/resources/META-INF/native-image/reflect-config.json @@ -0,0 +1 @@ +[] diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml new file mode 100644 index 0000000..984387f --- /dev/null +++ b/src/main/resources/application.yaml @@ -0,0 +1,7 @@ + +app: + greeting: "Hello" + +server: + port: 8080 + host: 0.0.0.0 diff --git a/src/main/resources/logging.properties b/src/main/resources/logging.properties new file mode 100644 index 0000000..cd238eb --- /dev/null +++ b/src/main/resources/logging.properties @@ -0,0 +1,19 @@ + +# Example Logging Configuration File +# For more information see $JAVA_HOME/jre/lib/logging.properties + +# Send messages to the console +handlers=io.helidon.common.HelidonConsoleHandler + +# HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread +java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n + +# Global logging level. Can be overridden by specific loggers +.level=INFO + +# Component specific log levels +#io.helidon.webserver.level=INFO +#io.helidon.config.level=INFO +#io.helidon.security.level=INFO +#io.helidon.common.level=INFO +#io.netty.level=INFO diff --git a/src/test/java/io/helidon/examples/quickstart/se/MainTest.java b/src/test/java/io/helidon/examples/quickstart/se/MainTest.java new file mode 100644 index 0000000..b86112c --- /dev/null +++ b/src/test/java/io/helidon/examples/quickstart/se/MainTest.java @@ -0,0 +1,96 @@ + +package io.helidon.examples.quickstart.se; + +import java.util.Collections; +import java.util.concurrent.TimeUnit; + +import javax.json.Json; +import javax.json.JsonBuilderFactory; +import javax.json.JsonObject; + +import io.helidon.media.jsonp.JsonpSupport; +import io.helidon.webclient.WebClient; +import io.helidon.webclient.WebClientResponse; +import io.helidon.webserver.WebServer; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class MainTest { + + private static WebServer webServer; + private static WebClient webClient; + private static final JsonBuilderFactory JSON_BUILDER = Json.createBuilderFactory(Collections.emptyMap()); + private static final JsonObject TEST_JSON_OBJECT; + + static { + TEST_JSON_OBJECT = JSON_BUILDER.createObjectBuilder() + .add("greeting", "Hola") + .build(); + } + + @BeforeAll + public static void startTheServer() { + webServer = Main.startServer().await(); + + webClient = WebClient.builder() + .baseUri("http://localhost:" + webServer.port()) + .addMediaSupport(JsonpSupport.create()) + .build(); + } + + @AfterAll + public static void stopServer() throws Exception { + if (webServer != null) { + webServer.shutdown() + .toCompletableFuture() + .get(10, TimeUnit.SECONDS); + } + } + + @Test + public void testHelloWorld() { + JsonObject jsonObject; + WebClientResponse response; + + jsonObject = webClient.get() + .path("/greet") + .request(JsonObject.class) + .await(); + assertEquals("Hello World!", jsonObject.getString("message")); + + jsonObject = webClient.get() + .path("/greet/Joe") + .request(JsonObject.class) + .await(); + assertEquals("Hello Joe!", jsonObject.getString("message")); + + response = webClient.put() + .path("/greet/greeting") + .submit(TEST_JSON_OBJECT) + .await(); + assertEquals(204, response.status().code()); + + jsonObject = webClient.get() + .path("/greet/Joe") + .request(JsonObject.class) + .await(); + assertEquals("Hola Joe!", jsonObject.getString("message")); + + response = webClient.get() + .path("/health") + .request() + .await(); + assertEquals(200, response.status().code()); + + response = webClient.get() + .path("/metrics") + .request() + .await(); + assertEquals(200, response.status().code()); + } + +}