diff --git a/300-quarkus-vertx-webClient/pom.xml b/300-quarkus-vertx-webClient/pom.xml index 7cd2b467..e25c31ca 100644 --- a/300-quarkus-vertx-webClient/pom.xml +++ b/300-quarkus-vertx-webClient/pom.xml @@ -14,11 +14,11 @@ io.quarkus - quarkus-resteasy + quarkus-opentelemetry io.quarkus - quarkus-resteasy-mutiny + quarkus-opentelemetry-exporter-jaeger io.quarkus @@ -32,13 +32,25 @@ io.smallrye.reactive smallrye-mutiny-vertx-web-client - - io.quarkus - quarkus-resteasy-jsonb - com.github.tomakehurst wiremock-jre8 + test + + + org.awaitility + awaitility + test + + + org.testcontainers + testcontainers + test + + + io.quarkus + quarkus-junit5-internal + test \ No newline at end of file diff --git a/300-quarkus-vertx-webClient/src/main/java/io/quarkus/qe/vertx/webclient/exceptions/ApplicationExceptionHandler.java b/300-quarkus-vertx-webClient/src/main/java/io/quarkus/qe/vertx/webclient/exceptions/ApplicationExceptionHandler.java deleted file mode 100644 index 6a2e05f3..00000000 --- a/300-quarkus-vertx-webClient/src/main/java/io/quarkus/qe/vertx/webclient/exceptions/ApplicationExceptionHandler.java +++ /dev/null @@ -1,21 +0,0 @@ -package io.quarkus.qe.vertx.webclient.exceptions; - -import io.smallrye.mutiny.TimeoutException; - -import javax.ws.rs.core.Response; -import javax.ws.rs.ext.ExceptionMapper; -import javax.ws.rs.ext.Provider; - -@Provider -public class ApplicationExceptionHandler implements ExceptionMapper { - @Override - public Response toResponse(RuntimeException e) { - - Response error = Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()).build(); - - if(e instanceof TimeoutException) - error = Response.status(Response.Status.REQUEST_TIMEOUT).entity(e.getMessage()).build(); - - return error; - } -} diff --git a/300-quarkus-vertx-webClient/src/main/java/io/quarkus/qe/vertx/webclient/ChuckNorrisResource.java b/300-quarkus-vertx-webClient/src/main/java/io/quarkus/qe/vertx/webclient/handler/ChuckNorrisResource.java similarity index 63% rename from 300-quarkus-vertx-webClient/src/main/java/io/quarkus/qe/vertx/webclient/ChuckNorrisResource.java rename to 300-quarkus-vertx-webClient/src/main/java/io/quarkus/qe/vertx/webclient/handler/ChuckNorrisResource.java index afd613b7..3099561c 100644 --- a/300-quarkus-vertx-webClient/src/main/java/io/quarkus/qe/vertx/webclient/ChuckNorrisResource.java +++ b/300-quarkus-vertx-webClient/src/main/java/io/quarkus/qe/vertx/webclient/handler/ChuckNorrisResource.java @@ -1,28 +1,31 @@ -package io.quarkus.qe.vertx.webclient; +package io.quarkus.qe.vertx.webclient.handler; + +import static io.quarkus.vertx.web.Route.HttpMethod; + +import java.net.HttpURLConnection; +import java.time.Duration; +import java.util.Arrays; +import java.util.List; +import java.util.function.BiFunction; + +import javax.annotation.PostConstruct; +import javax.inject.Inject; + +import org.eclipse.microprofile.config.inject.ConfigProperty; import io.quarkus.qe.vertx.webclient.config.ChuckEndpointValue; import io.quarkus.qe.vertx.webclient.config.VertxWebClientConfig; +import io.quarkus.qe.vertx.webclient.model.Joke; +import io.quarkus.vertx.web.Route; +import io.quarkus.vertx.web.RouteBase; import io.smallrye.mutiny.Uni; -import io.vertx.core.json.Json; import io.vertx.mutiny.core.Vertx; +import io.vertx.mutiny.ext.web.client.HttpResponse; import io.vertx.mutiny.ext.web.client.WebClient; import io.vertx.mutiny.ext.web.client.predicate.ResponsePredicate; import io.vertx.mutiny.ext.web.codec.BodyCodec; -import org.eclipse.microprofile.config.inject.ConfigProperty; -import javax.annotation.PostConstruct; -import javax.inject.Inject; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import java.time.Duration; -import java.util.Arrays; -import java.util.List; -import java.util.function.BiFunction; - -@Path("/chuck") +@RouteBase(path = "/chuck") public class ChuckNorrisResource { @Inject @@ -41,51 +44,46 @@ void initialize() { this.client = WebClient.create(vertx); } - @GET - @Produces(MediaType.APPLICATION_JSON) - @Path("/") - public Uni getRandomJoke() { + @Route(methods = HttpMethod.GET, path = "/") + public Uni getRandomJoke() { return getChuckQuoteAsJoke() - .map(resp -> Response.ok(resp).build()) .ifNoItem().after(Duration.ofSeconds(httpClientConf.timeout)).fail() .onFailure().retry().atMost(httpClientConf.retries); } - @GET - @Produces(MediaType.APPLICATION_JSON) - @Path("/bodyCodec") - public Uni getRandomJokeWithBodyCodec() { - return client.getAbs(chuckNorrisQuote.getValue()) + @Route(methods = HttpMethod.GET, path = "/bodyCodec", produces = "application/json") + public Uni getRandomJokeWithBodyCodec() { + return client.getAbs(chuckNorrisQuote.getValue()) .as(BodyCodec.json(Joke.class)) .putHeader("Accept", "application/json") - .expect(ResponsePredicate.status(Response.Status.OK.getStatusCode())) + .expect(ResponsePredicate.status(HttpURLConnection.HTTP_OK)) .send() - .map(resp -> Response.ok(resp.body()).build()) + .map(HttpResponse::body) .ifNoItem().after(Duration.ofSeconds(httpClientConf.timeout)).fail() .onFailure().retry().atMost(httpClientConf.retries); } - @GET - @Produces(MediaType.APPLICATION_JSON) - @Path("/combine") - public Uni getTwoRandomJokes() { + @Route(methods = HttpMethod.GET, path = "/combine", produces = "application/json") + public Uni> getTwoRandomJokes() { Uni jokeOne = getChuckQuoteAsJoke(); Uni jokeTwo = getChuckQuoteAsJoke(); return Uni.combine() .all() .unis(jokeOne, jokeTwo) - .combinedWith((BiFunction>) Arrays::asList) - .map(resp -> Response.ok(Json.encode(resp)).build()); + .combinedWith((BiFunction>) Arrays::asList); + } + + @Route(methods = HttpMethod.GET, path = "/pong", produces = "application/json") + public Uni ping() { + return Uni.createFrom().item("pong"); } private Uni getChuckQuoteAsJoke() { return client.getAbs(chuckNorrisQuote.getValue()) .putHeader("Accept", "application/json") - .expect(ResponsePredicate.status(Response.Status.OK.getStatusCode())) + .expect(ResponsePredicate.status(HttpURLConnection.HTTP_OK)) .send() .map(resp -> resp.bodyAsJsonObject().mapTo(Joke.class)); } - } - diff --git a/300-quarkus-vertx-webClient/src/main/java/io/quarkus/qe/vertx/webclient/handler/FailureHandler.java b/300-quarkus-vertx-webClient/src/main/java/io/quarkus/qe/vertx/webclient/handler/FailureHandler.java new file mode 100644 index 00000000..a7816951 --- /dev/null +++ b/300-quarkus-vertx-webClient/src/main/java/io/quarkus/qe/vertx/webclient/handler/FailureHandler.java @@ -0,0 +1,21 @@ +package io.quarkus.qe.vertx.webclient.handler; + +import io.quarkus.vertx.web.Route; +import io.smallrye.mutiny.TimeoutException; +import io.vertx.core.http.HttpServerResponse; +import io.vertx.core.json.Json; +import io.vertx.core.json.JsonObject; +import java.net.HttpURLConnection; +import javax.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class FailureHandler { + @Route(path = "/*", type = Route.HandlerType.FAILURE, produces = "application/json") + void runtimeFailures(RuntimeException e, HttpServerResponse response) { + if(e instanceof TimeoutException){ + response.setStatusCode(HttpURLConnection.HTTP_CLIENT_TIMEOUT).end(Json.encode(new JsonObject().put("msg", e.getMessage()))); + }else{ + response.setStatusCode(HttpURLConnection.HTTP_INTERNAL_ERROR).end(Json.encode(new JsonObject().put("msg", e.getMessage()))); + } + } +} diff --git a/300-quarkus-vertx-webClient/src/main/java/io/quarkus/qe/vertx/webclient/handler/TracingExampleResource.java b/300-quarkus-vertx-webClient/src/main/java/io/quarkus/qe/vertx/webclient/handler/TracingExampleResource.java new file mode 100644 index 00000000..f965e154 --- /dev/null +++ b/300-quarkus-vertx-webClient/src/main/java/io/quarkus/qe/vertx/webclient/handler/TracingExampleResource.java @@ -0,0 +1,20 @@ +package io.quarkus.qe.vertx.webclient.handler; + +import javax.inject.Inject; + +import io.quarkus.qe.vertx.webclient.service.PongService; +import io.quarkus.vertx.web.Route; +import io.quarkus.vertx.web.RouteBase; +import io.smallrye.mutiny.Uni; + +@RouteBase(path = "/trace") +public class TracingExampleResource { + + @Inject + PongService pongService; + + @Route(methods = Route.HttpMethod.GET, path = "/ping") + Uni pingRequest() { + return pongService.pong().onItem().transform(pong -> "ping-" + pong); + } +} diff --git a/300-quarkus-vertx-webClient/src/main/java/io/quarkus/qe/vertx/webclient/Joke.java b/300-quarkus-vertx-webClient/src/main/java/io/quarkus/qe/vertx/webclient/model/Joke.java similarity index 94% rename from 300-quarkus-vertx-webClient/src/main/java/io/quarkus/qe/vertx/webclient/Joke.java rename to 300-quarkus-vertx-webClient/src/main/java/io/quarkus/qe/vertx/webclient/model/Joke.java index 4b37eea4..207f03a1 100644 --- a/300-quarkus-vertx-webClient/src/main/java/io/quarkus/qe/vertx/webclient/Joke.java +++ b/300-quarkus-vertx-webClient/src/main/java/io/quarkus/qe/vertx/webclient/model/Joke.java @@ -1,4 +1,4 @@ -package io.quarkus.qe.vertx.webclient; +package io.quarkus.qe.vertx.webclient.model; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; diff --git a/300-quarkus-vertx-webClient/src/main/java/io/quarkus/qe/vertx/webclient/service/PongService.java b/300-quarkus-vertx-webClient/src/main/java/io/quarkus/qe/vertx/webclient/service/PongService.java new file mode 100644 index 00000000..7647af7f --- /dev/null +++ b/300-quarkus-vertx-webClient/src/main/java/io/quarkus/qe/vertx/webclient/service/PongService.java @@ -0,0 +1,42 @@ +package io.quarkus.qe.vertx.webclient.service; + +import java.net.HttpURLConnection; + +import javax.annotation.PostConstruct; +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import org.eclipse.microprofile.config.inject.ConfigProperty; + +import io.smallrye.mutiny.Uni; +import io.vertx.mutiny.core.Vertx; +import io.vertx.mutiny.ext.web.client.HttpResponse; +import io.vertx.mutiny.ext.web.client.WebClient; +import io.vertx.mutiny.ext.web.client.predicate.ResponsePredicate; + +@ApplicationScoped +public class PongService { + + @Inject + Vertx vertx; + + @ConfigProperty(name = "quarkus.http.port") + public int port; + + private WebClient client; + private String basePath; + + @PostConstruct + void initialize() { + this.client = WebClient.create(vertx); + this.basePath = "http://localhost:" + port; + } + + public Uni pong() { + return client.getAbs(basePath + "/chuck/pong") + .putHeader("Accept", "application/json") + .expect(ResponsePredicate.status(HttpURLConnection.HTTP_OK)) + .send() + .map(HttpResponse::bodyAsString); + } +} diff --git a/300-quarkus-vertx-webClient/src/main/resources/application.properties b/300-quarkus-vertx-webClient/src/main/resources/application.properties index 51bc68d4..cd9938f9 100644 --- a/300-quarkus-vertx-webClient/src/main/resources/application.properties +++ b/300-quarkus-vertx-webClient/src/main/resources/application.properties @@ -1,4 +1,8 @@ # Configuration file +quarkus.http.port=8081 chucknorris.api.domain=https://api.chucknorris.io vertx.webclient.timeout-sec=2 -vertx.webclient.retries=3 \ No newline at end of file +vertx.webclient.retries=3 + +# Jaeger +quarkus.opentelemetry.tracer.exporter.jaeger.endpoint=http://localhost:14250/api/traces \ No newline at end of file diff --git a/300-quarkus-vertx-webClient/src/test/java/io/quarkus/qe/vertx/webclient/ChuckNorrisResourceTest.java b/300-quarkus-vertx-webClient/src/test/java/io/quarkus/qe/vertx/webclient/ChuckNorrisResourceTest.java index ee53c594..d3cf9da9 100644 --- a/300-quarkus-vertx-webClient/src/test/java/io/quarkus/qe/vertx/webclient/ChuckNorrisResourceTest.java +++ b/300-quarkus-vertx-webClient/src/test/java/io/quarkus/qe/vertx/webclient/ChuckNorrisResourceTest.java @@ -1,94 +1,178 @@ package io.quarkus.qe.vertx.webclient; +import io.quarkus.qe.vertx.webclient.resources.JaegerTestResource; +import io.quarkus.qe.vertx.webclient.resources.WireMockChuckNorrisResource; import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.junit.QuarkusTest; +import io.restassured.response.Response; +import java.net.HttpURLConnection; +import java.time.Duration; +import java.util.List; +import java.util.concurrent.TimeUnit; +import org.apache.http.HttpStatus; +import org.hamcrest.Matcher; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import javax.ws.rs.core.Response; - -import static com.github.tomakehurst.wiremock.client.WireMock.*; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static io.restassured.RestAssured.given; +import static org.awaitility.Awaitility.await; import static org.hamcrest.CoreMatchers.containsStringIgnoringCase; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.core.Every.everyItem; +import static org.hamcrest.core.StringContains.containsString; @QuarkusTest @QuarkusTestResource(WireMockChuckNorrisResource.class) +@QuarkusTestResource(JaegerTestResource.class) public class ChuckNorrisResourceTest { + private final static String jaegerEndpoint = "http://localhost:16686/api/traces"; + static final String EXPECTED_ID = "aBanNLDwR-SAz7iMHuCiyw"; + static final String EXPECTED_VALUE = "Chuck Norris has already been to mars; that why there's no signs of life"; + static final int DELAY = 3500; // must be greater than vertx.webclient.timeout-sec + static final String QUARKUS_PROFILE = "quarkus.profile"; + static final String NATIVE = "native"; + static final boolean IS_NATIVE = System.getProperty(QUARKUS_PROFILE, "").equals(NATIVE); - final static String EXPECTED_ID = "aBanNLDwR-SAz7iMHuCiyw"; - final static String EXPECTED_VALUE = "Chuck Norris has already been to mars; that why there's no signs of life"; + private Response resp; @Test @DisplayName("Vert.x WebClient [flavor: mutiny] -> Map json response body to POJO") - public void getChuckJokeAsJSON(){ - - stubFor(get(urlEqualTo("/jokes/random")) - .willReturn(aResponse() - .withHeader("Accept", "application/json") - .withBody(String.format("{\"categories\":[]," + - "\"created_at\":\"2020-01-05 13:42:19.576875\"," + - "\"icon_url\":\"https://assets.chucknorris.host/img/avatar/chuck-norris.png\"," + - "\"id\":\"%s\"," + - "\"updated_at\":\"2020-01-05 13:42:19.576875\"," + - "\"url\":\"https://api.chucknorris.io/jokes/sC09X1xQQymE4SciIjyV0g\"," + - "\"value\":\"%s\"}", EXPECTED_ID, EXPECTED_VALUE)))); - + public void getChuckJokeAsJSON() { + setupMockHttpServer(); given() .when() - .get("/chuck") + .get("/chuck/") .then() - .statusCode(Response.Status.OK.getStatusCode()) - .body(containsStringIgnoringCase((String.format("{\"id\":\"%s\",\"jokeText\":\"%s\"}", EXPECTED_ID, EXPECTED_VALUE)))); + .statusCode(HttpURLConnection.HTTP_OK) + .body("id", containsStringIgnoringCase(EXPECTED_ID)) + .body("value", containsStringIgnoringCase(EXPECTED_VALUE)); } @Test @DisplayName("Vert.x WebClient [flavor: mutiny] -> Mapped json response by 'as' mutiny method.") public void getChuckJokeByJsonBodyCodec() throws InterruptedException { + setupMockHttpServer(); + given() + .when() + .get("/chuck/bodyCodec/") + .then() + .statusCode(HttpURLConnection.HTTP_OK) + .body("id", containsStringIgnoringCase(EXPECTED_ID)) + .body("value", containsStringIgnoringCase(EXPECTED_VALUE)); + } + @Test + @DisplayName("Vert.x WebClient [flavor: mutiny] -> If third party server exceed http client timeout, then throw a timeout exception.") + public void getTimeoutWhenResponseItsTooSlow() { stubFor(get(urlEqualTo("/jokes/random")) .willReturn(aResponse() .withHeader("Accept", "application/json") - .withBody(String.format("{\"categories\":[]," + - "\"created_at\":\"2020-01-05 13:42:19.576875\"," + - "\"icon_url\":\"https://assets.chucknorris.host/img/avatar/chuck-norris.png\"," + - "\"id\":\"%s\"," + - "\"updated_at\":\"2020-01-05 13:42:19.576875\"," + - "\"url\":\"https://api.chucknorris.io/jokes/sC09X1xQQymE4SciIjyV0g\"," + - "\"value\":\"%s\"}", EXPECTED_ID, EXPECTED_VALUE)))); + .withFixedDelay(DELAY))); given() - .filter( - (request, response, ctx) -> { - io.restassured.response.Response resp = ctx.next(request, response); - if (resp.statusCode() >= 400) { - System.err.println(resp.body().prettyPrint()); - System.err.println(request.getMethod() + " " + request.getURI() + " => " - + response.getStatusCode() + " " + response.getStatusLine()); - } - return resp; - }) .when() - .get("/chuck/bodyCodec") + .get("/chuck/bodyCodec/") .then() - .statusCode(Response.Status.OK.getStatusCode()) - .body(containsStringIgnoringCase((String.format("{\"id\":\"%s\",\"jokeText\":\"%s\"}", EXPECTED_ID, EXPECTED_VALUE)))); + .statusCode(HttpURLConnection.HTTP_CLIENT_TIMEOUT); } @Test - @DisplayName("Vert.x WebClient [flavor: mutiny] -> If third party server exceed http client timeout, then throw a timeout exception.") - public void getTimeoutWhenResponseItsTooSlow(){ - final int delay = 3500; // must be greater than vertx.webclient.timeout-sec + public void endpointShouldTrace() { + final int pageLimit = 50; + final String expectedOperationName = "trace/ping"; + await().atMost(1, TimeUnit.MINUTES).pollInterval(Duration.ofSeconds(1)).untilAsserted(() -> { + whenIMakePingRequest(); + thenRetrieveTraces(pageLimit, "1h", getServiceName(), expectedOperationName); + thenStatusCodeMustBe(HttpStatus.SC_OK); + thenTraceDataSizeMustBe(greaterThan(0)); + thenTraceSpanSizeMustBe(greaterThan(0)); + thenTraceSpanTagsSizeMustBe(greaterThan(0)); + thenTraceSpansOperationNameMustBe(not(empty())); + thenCheckThatAllOperationNamesAreEqualTo(expectedOperationName); + }); + } + @Test + @Disabled("https://github.com/quarkusio/quarkus/issues/16507") + public void httpClientShouldHaveHisOwnSpan() { + final int pageLimit = 50; + final String expectedOperationName = "trace/ping"; + await().atMost(1, TimeUnit.MINUTES).pollInterval(Duration.ofSeconds(1)).untilAsserted(() -> { + whenIMakePingRequest(); + thenRetrieveTraces(pageLimit, "1h", getServiceName(), expectedOperationName); + thenStatusCodeMustBe(HttpStatus.SC_OK); + thenTraceDataSizeMustBe(greaterThan(0)); + thenTraceSpanSizeMustBe(greaterThan(1)); + thenTraceSpanTagsSizeMustBe(greaterThan(0)); + thenTraceSpansOperationNameMustBe(not(empty())); + thenCheckThatAllOperationNamesAreEqualTo(expectedOperationName); + }); + } + + private void whenIMakePingRequest() { + given().when() + .get("/trace/ping") + .then() + .statusCode(HttpStatus.SC_OK).body(containsStringIgnoringCase("ping-pong")); + } + + private void thenRetrieveTraces(int pageLimit, String lookBack, String serviceName, String operationName) { + resp = given().when() + .queryParam("limit", pageLimit) + .queryParam("lookback", lookBack) + .queryParam("service", serviceName) + .queryParam("operation", operationName) + .get(jaegerEndpoint); + } + + private void thenStatusCodeMustBe(int expectedStatusCode) { + resp.then().statusCode(expectedStatusCode); + } + + private void thenTraceDataSizeMustBe(Matcher matcher) { + resp.then().body("data.size()", matcher); + } + + private void thenTraceSpanSizeMustBe(Matcher matcher) { + resp.then().body("data[0].spans.size()", matcher); + } + + private void thenTraceSpanTagsSizeMustBe(Matcher matcher) { + resp.then().body("data[0].spans[0].tags.size()", matcher); + } + + private void thenTraceSpansOperationNameMustBe(Matcher matcher) { + resp.then().body("data.spans.operationName", matcher); + } + + private void thenCheckThatAllOperationNamesAreEqualTo(String expectedOperationName) { + List operationNames = resp.then().extract().jsonPath().getList("data.spans.operationName", String.class); + assertThat(operationNames, everyItem(containsString(expectedOperationName))); + } + + private void setupMockHttpServer() { stubFor(get(urlEqualTo("/jokes/random")) .willReturn(aResponse() .withHeader("Accept", "application/json") - .withFixedDelay(delay))); + .withBody(String.format("{\"categories\":[]," + + "\"created_at\":\"2020-01-05 13:42:19.576875\"," + + "\"icon_url\":\"https://assets.chucknorris.host/img/avatar/chuck-norris.png\"," + + "\"id\":\"%s\"," + + "\"updated_at\":\"2020-01-05 13:42:19.576875\"," + + "\"url\":\"https://api.chucknorris.io/jokes/sC09X1xQQymE4SciIjyV0g\"," + + "\"value\":\"%s\"}", EXPECTED_ID, EXPECTED_VALUE)))); + } - given() - .when() - .get("/chuck/bodyCodec") - .then() - .statusCode(Response.Status.REQUEST_TIMEOUT.getStatusCode()); + private String getServiceName() { + // TODO https://github.com/quarkusio/quarkus/issues/16499 + return (IS_NATIVE) ? "300-quarkus-vertx-webclient" : "<>"; } } diff --git a/300-quarkus-vertx-webClient/src/test/java/io/quarkus/qe/vertx/webclient/resources/JaegerContainer.java b/300-quarkus-vertx-webClient/src/test/java/io/quarkus/qe/vertx/webclient/resources/JaegerContainer.java new file mode 100644 index 00000000..170d50ff --- /dev/null +++ b/300-quarkus-vertx-webClient/src/test/java/io/quarkus/qe/vertx/webclient/resources/JaegerContainer.java @@ -0,0 +1,19 @@ +package io.quarkus.qe.vertx.webclient.resources; + +import java.time.Duration; + +import org.testcontainers.containers.GenericContainer; + +public class JaegerContainer extends GenericContainer { + public static final int REST_PORT = 16686; + private static final int TRACE_PORT = 14250; + + private static final int STARTUP_TIMEOUT = 30000; + + public JaegerContainer() { + super("jaegertracing/all-in-one:latest"); + withStartupTimeout(Duration.ofMillis(STARTUP_TIMEOUT)); + addFixedExposedPort(REST_PORT, REST_PORT); + addFixedExposedPort(TRACE_PORT, TRACE_PORT); + } +} diff --git a/300-quarkus-vertx-webClient/src/test/java/io/quarkus/qe/vertx/webclient/resources/JaegerTestResource.java b/300-quarkus-vertx-webClient/src/test/java/io/quarkus/qe/vertx/webclient/resources/JaegerTestResource.java new file mode 100644 index 00000000..3b50bcdc --- /dev/null +++ b/300-quarkus-vertx-webClient/src/test/java/io/quarkus/qe/vertx/webclient/resources/JaegerTestResource.java @@ -0,0 +1,27 @@ +package io.quarkus.qe.vertx.webclient.resources; + +import java.util.Collections; +import java.util.Map; +import java.util.Optional; + +import org.testcontainers.containers.GenericContainer; + +import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; + +public class JaegerTestResource implements QuarkusTestResourceLifecycleManager { + + private GenericContainer container; + + @Override + public Map start() { + container = new JaegerContainer(); + container.start(); + + return Collections.emptyMap(); + } + + @Override + public void stop() { + Optional.ofNullable(container).ifPresent(GenericContainer::stop); + } +} diff --git a/300-quarkus-vertx-webClient/src/test/java/io/quarkus/qe/vertx/webclient/WireMockChuckNorrisResource.java b/300-quarkus-vertx-webClient/src/test/java/io/quarkus/qe/vertx/webclient/resources/WireMockChuckNorrisResource.java similarity index 82% rename from 300-quarkus-vertx-webClient/src/test/java/io/quarkus/qe/vertx/webclient/WireMockChuckNorrisResource.java rename to 300-quarkus-vertx-webClient/src/test/java/io/quarkus/qe/vertx/webclient/resources/WireMockChuckNorrisResource.java index cbffa1f3..fd5cca92 100644 --- a/300-quarkus-vertx-webClient/src/test/java/io/quarkus/qe/vertx/webclient/WireMockChuckNorrisResource.java +++ b/300-quarkus-vertx-webClient/src/test/java/io/quarkus/qe/vertx/webclient/resources/WireMockChuckNorrisResource.java @@ -1,26 +1,26 @@ -package io.quarkus.qe.vertx.webclient; - -import com.github.tomakehurst.wiremock.WireMockServer; -import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; +package io.quarkus.qe.vertx.webclient.resources; import java.util.Collections; import java.util.Map; import java.util.Objects; -public class WireMockChuckNorrisResource implements QuarkusTestResourceLifecycleManager { +import com.github.tomakehurst.wiremock.WireMockServer; +import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; + +public class WireMockChuckNorrisResource implements QuarkusTestResourceLifecycleManager { private WireMockServer wireMockServer; @Override public Map start() { wireMockServer = new WireMockServer(); wireMockServer.start(); - return Collections.singletonMap("chucknorris.api.domain", wireMockServer.baseUrl()); } @Override public void stop() { - if (Objects.nonNull(wireMockServer)) wireMockServer.stop(); + if (Objects.nonNull(wireMockServer)) + wireMockServer.stop(); } } diff --git a/300-quarkus-vertx-webClient/src/test/resources/application.properties b/300-quarkus-vertx-webClient/src/test/resources/application.properties index bb6960a7..08385610 100644 --- a/300-quarkus-vertx-webClient/src/test/resources/application.properties +++ b/300-quarkus-vertx-webClient/src/test/resources/application.properties @@ -1,6 +1,10 @@ # Configuration file quarkus.test.native-image-profile=test +quarkus.http.port=8081 chucknorris.api.domain=https://api.chucknorris.io/ vertx.webclient.timeout-sec=1 -vertx.webclient.retries=1 \ No newline at end of file +vertx.webclient.retries=1 + +# Jaeger +quarkus.opentelemetry.tracer.exporter.jaeger.endpoint=http://localhost:14250/api/traces \ No newline at end of file diff --git a/304-quarkus-vertx-routes/src/main/java/io/quarkus/qe/ReactiveRoutesTracing.java b/304-quarkus-vertx-routes/src/main/java/io/quarkus/qe/ReactiveRoutesTracing.java new file mode 100644 index 00000000..0913ea35 --- /dev/null +++ b/304-quarkus-vertx-routes/src/main/java/io/quarkus/qe/ReactiveRoutesTracing.java @@ -0,0 +1,13 @@ +package io.quarkus.qe; + +import io.quarkus.vertx.web.Route; +import io.quarkus.vertx.web.RouteBase; +import io.vertx.ext.web.RoutingContext; + +@RouteBase(path = "/trace") +public class ReactiveRoutesTracing { + @Route(methods = Route.HttpMethod.GET, path = "/hello") + boolean validateRequestSinglePara() { + return true; + } +}