From bf4acd301e3a492f10dddb412bc1a9b590676517 Mon Sep 17 00:00:00 2001 From: yasmin-aumeeruddy Date: Fri, 11 Aug 2023 17:09:22 +0100 Subject: [PATCH] Parameter and error tests (#113) * tck changes * JAX-RS TCK test fixes - Code review comments - Change expected response code to Bad Request (400) so that we can distinguish between the expected error code and an error code generated by an unexpected exception * Add parameters to MpRestClientAsyncTest * Correct assertEquals arguments wrong way round --------- Co-authored-by: Andrew Rouse --- .../tck/async/JaxRsClientAsyncTest.java | 67 ++++++++++++-- .../async/JaxRsClientAsyncTestEndpoint.java | 77 +++++++++++++--- .../tck/async/JaxRsServerAsyncTest.java | 82 ++++++++++++++++- .../async/JaxRsServerAsyncTestEndpoint.java | 71 ++++++++++++++- .../JaxRsServerAsyncTestEndpointClient.java | 12 ++- .../tck/async/MpRestClientAsyncTest.java | 59 ++++++++++-- .../async/MpRestClientAsyncTestEndpoint.java | 89 +++++++++++++++++-- .../tracing/tck/spi/ExporterSpiTest.java | 2 - 8 files changed, 416 insertions(+), 43 deletions(-) diff --git a/tracing/tck/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/async/JaxRsClientAsyncTest.java b/tracing/tck/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/async/JaxRsClientAsyncTest.java index 633afb80..9ca489a8 100644 --- a/tracing/tck/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/async/JaxRsClientAsyncTest.java +++ b/tracing/tck/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/async/JaxRsClientAsyncTest.java @@ -25,6 +25,7 @@ import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_STATUS_CODE; import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_TARGET; import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_URL; +import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; import static java.net.HttpURLConnection.HTTP_OK; import java.net.URL; @@ -49,7 +50,6 @@ import io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider; import io.opentelemetry.sdk.trace.data.SpanData; import jakarta.inject.Inject; -import jakarta.ws.rs.GET; import jakarta.ws.rs.HttpMethod; public class JaxRsClientAsyncTest extends Arquillian { @@ -77,6 +77,7 @@ public static WebArchive createDeployment() { URL url; public static final String TEST_PASSED = "Test Passed"; + public static final String QUERY_VALUE = "bar"; @Inject private InMemorySpanExporter spanExporter; @@ -92,16 +93,65 @@ void setUp() { @Test public void testIntegrationWithJaxRsClient() throws Exception { - basicClient.get("/JaxRsClientAsyncTestEndpoint/jaxrsclient"); + basicClient.get("/JaxRsClientAsyncTestEndpoint/jaxrsclient?baggageValue=" + QUERY_VALUE); readSpans(); } @Test public void testIntegrationWithJaxRsClientAsync() throws Exception { - basicClient.get("/JaxRsClientAsyncTestEndpoint/jaxrsclientasync"); + basicClient.get("/JaxRsClientAsyncTestEndpoint/jaxrsclientasync?baggageValue=" + QUERY_VALUE); readSpans(); } + @Test + public void testIntegrationWithJaxRsClientError() throws Exception { + basicClient.get("/JaxRsClientAsyncTestEndpoint/jaxrsclienterror"); + readErrorSpans(); + } + + public void readErrorSpans() { + + List spanData = spanExporter.getFinishedSpanItems(3); + + List serverSpans = spanExporter.getSpansWithKind(SpanKind.SERVER); + + SpanData firstURL = null; + SpanData secondURL = null; + for (SpanData span : serverSpans) { + if (span.getAttributes().get(HTTP_TARGET).contains("JaxRsClientAsyncTestEndpoint/jaxrsclient")) { + firstURL = span; + } else { + secondURL = span; + } + } + + Assert.assertNotNull(firstURL); + Assert.assertNotNull(secondURL); + + SpanData httpGet = spanExporter.getFirst(SpanKind.CLIENT); + + // Assert correct parent-child links + // Shows that propagation occurred + Assert.assertEquals(httpGet.getSpanId(), secondURL.getParentSpanId()); + Assert.assertEquals(firstURL.getSpanId(), httpGet.getParentSpanId()); + + Assert.assertEquals(firstURL.getAttributes().get(HTTP_METHOD), HttpMethod.GET); + Assert.assertEquals(firstURL.getAttributes().get(HTTP_SCHEME), "http"); + + // getError returns an internal server error... + Assert.assertEquals(secondURL.getAttributes().get(HTTP_STATUS_CODE).intValue(), HTTP_BAD_REQUEST); + // Which gets received by the client + Assert.assertEquals(httpGet.getAttributes().get(HTTP_STATUS_CODE).intValue(), HTTP_BAD_REQUEST); + // The exception from the client is inspected and handled so this method should return OK + Assert.assertEquals(firstURL.getAttributes().get(HTTP_STATUS_CODE).intValue(), HTTP_OK); + + // There are many different URLs that will end up here. But all should contain "JaxRsClientAsyncTestEndpoint" + Assert.assertTrue(httpGet.getAttributes().get(HTTP_URL).contains("JaxRsClientAsyncTestEndpoint")); + + Assert.assertEquals(httpGet.getAttributes().get(HTTP_METHOD), HttpMethod.GET); + Assert.assertTrue(httpGet.getAttributes().get(HTTP_URL).contains("JaxRsClientAsyncTestEndpoint")); + } + public void readSpans() { List spanData = spanExporter.getFinishedSpanItems(3); @@ -128,15 +178,16 @@ public void readSpans() { Assert.assertEquals(httpGet.getSpanId(), secondURL.getParentSpanId()); Assert.assertEquals(firstURL.getSpanId(), httpGet.getParentSpanId()); - Assert.assertEquals(HTTP_OK, firstURL.getAttributes().get(HTTP_STATUS_CODE).intValue()); - Assert.assertEquals(HttpMethod.GET, firstURL.getAttributes().get(HTTP_METHOD)); - Assert.assertEquals("http", firstURL.getAttributes().get(HTTP_SCHEME)); + Assert.assertEquals(firstURL.getAttributes().get(HTTP_STATUS_CODE).intValue(), HTTP_OK); + Assert.assertEquals(firstURL.getAttributes().get(HTTP_METHOD), HttpMethod.GET); + Assert.assertEquals(firstURL.getAttributes().get(HTTP_SCHEME), "http"); + Assert.assertTrue(firstURL.getAttributes().get(HTTP_TARGET).contains(QUERY_VALUE)); // There are many different URLs that will end up here. But all should contain "JaxRsClientAsyncTestEndpoint" Assert.assertTrue(httpGet.getAttributes().get(HTTP_URL).contains("JaxRsClientAsyncTestEndpoint")); - Assert.assertEquals(HTTP_OK, httpGet.getAttributes().get(HTTP_STATUS_CODE).intValue()); - Assert.assertEquals(HttpMethod.GET, httpGet.getAttributes().get(HTTP_METHOD)); + Assert.assertEquals(httpGet.getAttributes().get(HTTP_STATUS_CODE).intValue(), HTTP_OK); + Assert.assertEquals(httpGet.getAttributes().get(HTTP_METHOD), HttpMethod.GET); Assert.assertTrue(httpGet.getAttributes().get(HTTP_URL).contains("JaxRsClientAsyncTestEndpoint")); } } diff --git a/tracing/tck/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/async/JaxRsClientAsyncTestEndpoint.java b/tracing/tck/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/async/JaxRsClientAsyncTestEndpoint.java index 3138c12f..1c6e19a7 100644 --- a/tracing/tck/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/async/JaxRsClientAsyncTestEndpoint.java +++ b/tracing/tck/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/async/JaxRsClientAsyncTestEndpoint.java @@ -19,8 +19,12 @@ */ package org.eclipse.microprofile.telemetry.tracing.tck.async; +import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.fail; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import org.eclipse.microprofile.telemetry.tracing.tck.exporter.InMemorySpanExporter; @@ -35,6 +39,8 @@ import jakarta.ws.rs.ApplicationPath; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.client.Client; import jakarta.ws.rs.client.ClientBuilder; import jakarta.ws.rs.core.Application; @@ -69,21 +75,22 @@ private void closeClient() { @GET @Path("/jaxrsclient") - public Response getJax(@Context UriInfo uriInfo) { + public Response getJax(@Context UriInfo uriInfo, @QueryParam(value = "baggageValue") String baggageValue) { Assert.assertNotNull(Span.current()); - try (Scope s = Baggage.builder().put("foo", "bar").build().makeCurrent()) { + try (Scope s = Baggage.builder().put("foo", baggageValue).build().makeCurrent()) { Baggage baggage = Baggage.current(); - Assert.assertEquals("bar", baggage.getEntryValue("foo")); + Assert.assertEquals(baggage.getEntryValue("foo"), baggageValue); String url = new String(uriInfo.getAbsolutePath().toString()); - url = url.replace("jaxrsclient", "jaxrstwo"); // The jaxrsclient will use the URL as given so it needs - // the final part to be provided. + // Use our own URL to work out the URL of the other test endpoint + url = url.replace("jaxrsclient", "jaxrstwo"); String result = client.target(url) + .queryParam("baggageValue", baggageValue) .request(MediaType.TEXT_PLAIN) .get(String.class); - Assert.assertEquals(TEST_PASSED, result); + Assert.assertEquals(result, TEST_PASSED); } finally { client.close(); } @@ -92,25 +99,26 @@ public Response getJax(@Context UriInfo uriInfo) { @GET @Path("/jaxrsclientasync") - public Response getJaxAsync(@Context UriInfo uriInfo) { + public Response getJaxAsync(@Context UriInfo uriInfo, @QueryParam(value = "baggageValue") String baggageValue) { Assert.assertNotNull(Span.current()); - try (Scope s = Baggage.builder().put("foo", "bar").build().makeCurrent()) { + try (Scope s = Baggage.builder().put("foo", baggageValue).build().makeCurrent()) { Baggage baggage = Baggage.current(); - Assert.assertEquals("bar", baggage.getEntryValue("foo")); + Assert.assertEquals(baggage.getEntryValue("foo"), baggageValue); String url = new String(uriInfo.getAbsolutePath().toString()); - url = url.replace("jaxrsclientasync", "jaxrstwo"); // The jaxrsclient will use the URL as given so it needs - // the final part to be provided. + // Use our own URL to work out the URL of the other test endpoint + url = url.replace("jaxrsclientasync", "jaxrstwo"); Client client = ClientBuilder.newClient(); Future result = client.target(url) + .queryParam("baggageValue", baggageValue) .request(MediaType.TEXT_PLAIN) .async() .get(String.class); try { String resultValue = result.get(10, SECONDS); - Assert.assertEquals(TEST_PASSED, resultValue); + Assert.assertEquals(resultValue, TEST_PASSED); } catch (Exception e) { throw new RuntimeException(e); } finally { @@ -120,16 +128,57 @@ public Response getJaxAsync(@Context UriInfo uriInfo) { return Response.ok(Span.current().getSpanContext().getTraceId()).build(); } + @GET + @Path("/jaxrsclienterror") + public Response getJaxError(@Context UriInfo uriInfo, @QueryParam(value = "baggageValue") String baggageValue) { + Assert.assertNotNull(Span.current()); + + try (Scope s = Baggage.builder().put("foo", baggageValue).build().makeCurrent()) { + Baggage baggage = Baggage.current(); + Assert.assertEquals(baggage.getEntryValue("foo"), baggageValue); + + String url = new String(uriInfo.getAbsolutePath().toString()); + // Use our own URL to work out the URL of the other test endpoint + url = url.replace("jaxrsclienterror", "error"); + + Client client = ClientBuilder.newClient(); + Future result = client.target(url) + .request(MediaType.TEXT_PLAIN) + .async() + .get(String.class); + try { + result.get(10, SECONDS); + fail("Client didn't throw an exception"); + } catch (ExecutionException e) { + // Expected because server returned BAD_REQUEST + WebApplicationException webEx = (WebApplicationException) e.getCause(); + assertEquals(webEx.getResponse().getStatus(), HTTP_BAD_REQUEST); + } catch (Exception e) { + // Wrap and throw unexpected exceptions + throw new RuntimeException(e); + } finally { + client.close(); + } + } + return Response.ok(Span.current().getSpanContext().getTraceId()).build(); + } + // A method to be called by JAX Clients // This method triggers span creation and span propagation is automatic. @GET @Path("/jaxrstwo") - public Response getJaxRsTwo() { + public Response getJaxRsTwo(@QueryParam(value = "baggageValue") String baggageValue) { Assert.assertNotNull(Span.current()); Baggage baggage = Baggage.current(); // Assert that Baggage is propagated from Jax Server to Jax Client - Assert.assertEquals("bar", baggage.getEntryValue("foo")); + Assert.assertEquals(baggage.getEntryValue("foo"), baggageValue); return Response.ok(TEST_PASSED).build(); } + @GET + @Path("/error") + public Response getError() { + return Response.status(HTTP_BAD_REQUEST).build(); + } + } diff --git a/tracing/tck/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/async/JaxRsServerAsyncTest.java b/tracing/tck/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/async/JaxRsServerAsyncTest.java index 5f950c79..27eda595 100644 --- a/tracing/tck/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/async/JaxRsServerAsyncTest.java +++ b/tracing/tck/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/async/JaxRsServerAsyncTest.java @@ -20,9 +20,14 @@ package org.eclipse.microprofile.telemetry.tracing.tck.async; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_STATUS_CODE; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_TARGET; +import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; import static org.eclipse.microprofile.telemetry.tracing.tck.async.JaxRsServerAsyncTestEndpoint.BAGGAGE_VALUE_ATTR; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.fail; +import java.net.HttpURLConnection; import java.net.URISyntaxException; import java.net.URL; import java.util.List; @@ -49,6 +54,7 @@ import io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider; import io.opentelemetry.sdk.trace.data.SpanData; import jakarta.inject.Inject; +import jakarta.ws.rs.WebApplicationException; class JaxRsServerAsyncTest extends Arquillian { @@ -70,6 +76,7 @@ public static WebArchive createDeployment() { } private static final String TEST_VALUE = "test.value"; + public static final String QUERY_VALUE = "bar"; @Inject private InMemorySpanExporter spanExporter; @@ -87,12 +94,22 @@ void setUp() { @Test(groups = "optional-jaxrs-tests") public void testJaxRsServerAsyncCompletionStage() { - doAsyncTest(JaxRsServerAsyncTestEndpointClient::getCompletionStage); + doAsyncTest((client) -> client.getCompletionStage(QUERY_VALUE)); + } + + @Test(groups = "optional-jaxrs-tests") + public void testJaxRsServerAsyncCompletionStageError() { + doErrorAsyncTest((client) -> client.getCompletionStageError(QUERY_VALUE)); } @Test(groups = "optional-jaxrs-tests") public void testJaxRsServerAsyncSuspend() { - doAsyncTest(JaxRsServerAsyncTestEndpointClient::getSuspend); + doAsyncTest((client) -> client.getSuspend(QUERY_VALUE)); + } + + @Test(groups = "optional-jaxrs-tests") + public void testJaxRsServerAsyncSuspendError() { + doErrorAsyncTest((client) -> client.getSuspendError(QUERY_VALUE)); } private void doAsyncTest(Function requestFunction) { @@ -106,9 +123,8 @@ private void doAsyncTest(Function re JaxRsServerAsyncTestEndpointClient client = RestClientBuilder.newBuilder() .baseUri(url.toURI()) .build(JaxRsServerAsyncTestEndpointClient.class); - String response = requestFunction.apply(client); - Assert.assertEquals("OK", response); + Assert.assertEquals(response, "OK"); } catch (URISyntaxException e) { throw new RuntimeException(e); } @@ -131,6 +147,64 @@ private void doAsyncTest(Function re // Assert baggage propagated on subtask span Assert.assertTrue(subtaskSpan.getAttributes().get(BAGGAGE_VALUE_ATTR).contains(TEST_VALUE)); + // Assert that query parameter was passed correctly + Assert.assertTrue(serverSpan.getAttributes().get(HTTP_TARGET).contains(QUERY_VALUE)); + + // Assert that the server span finished after the subtask span + // Even though the resource method returned quickly, the span should not end until the response is actually + // returned + Assert.assertTrue(serverSpan.getEndEpochNanos() >= subtaskSpan.getEndEpochNanos()); + } + + private void doErrorAsyncTest(Function requestFunction) { + Baggage baggage = Baggage.builder() + .put(JaxRsServerAsyncTestEndpoint.BAGGAGE_KEY, TEST_VALUE) + .build(); + + try (Scope s = baggage.makeCurrent()) { + // Make the request to the test endpoint + try { + JaxRsServerAsyncTestEndpointClient client = RestClientBuilder.newBuilder() + .baseUri(url.toURI()) + .build(JaxRsServerAsyncTestEndpointClient.class); + try { + requestFunction.apply(client); + fail("Client did not throw an exception"); + } catch (WebApplicationException e) { + assertEquals(e.getResponse().getStatus(), HttpURLConnection.HTTP_BAD_REQUEST); + readErrorSpans(); + } + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } + } + + private void readErrorSpans() { + List spanData = spanExporter.getFinishedSpanItems(3); + + SpanData subtaskSpan = spanExporter.getFirst(SpanKind.INTERNAL); + SpanData clientSpan = spanExporter.getFirst(SpanKind.CLIENT); + SpanData serverSpan = spanExporter.getFirst(SpanKind.SERVER); + + // Assert correct parent-child links + // Shows that propagation occurred + assertEquals(serverSpan.getSpanId(), subtaskSpan.getParentSpanId()); + assertEquals(clientSpan.getSpanId(), serverSpan.getParentSpanId()); + + // Assert the status code for the client and server spans + assertEquals(serverSpan.getAttributes().get(HTTP_STATUS_CODE).intValue(), HTTP_BAD_REQUEST); + assertEquals(clientSpan.getAttributes().get(HTTP_STATUS_CODE).intValue(), HTTP_BAD_REQUEST); + + // Assert that the expected headers were used + Assert.assertTrue(serverSpan.getAttributes().get(BAGGAGE_VALUE_ATTR).contains(TEST_VALUE)); + + // Assert baggage propagated on subtask span + Assert.assertTrue(subtaskSpan.getAttributes().get(BAGGAGE_VALUE_ATTR).contains(TEST_VALUE)); + + // Assert that query parameter was passed correctly + Assert.assertTrue(serverSpan.getAttributes().get(HTTP_TARGET).contains(QUERY_VALUE)); + // Assert that the server span finished after the subtask span // Even though the resource method returned quickly, the span should not end until the response is actually // returned diff --git a/tracing/tck/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/async/JaxRsServerAsyncTestEndpoint.java b/tracing/tck/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/async/JaxRsServerAsyncTestEndpoint.java index 5589ca9e..3aac9224 100644 --- a/tracing/tck/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/async/JaxRsServerAsyncTestEndpoint.java +++ b/tracing/tck/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/async/JaxRsServerAsyncTestEndpoint.java @@ -35,9 +35,12 @@ import jakarta.ws.rs.ApplicationPath; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; +import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.container.AsyncResponse; import jakarta.ws.rs.container.Suspended; import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.Response.Status; /** * This endpoint is used to test MP Telemetry integration with async JAX-RS resource methods. @@ -76,7 +79,7 @@ public class JaxRsServerAsyncTestEndpoint extends Application { @GET @Path("completionstage") - public CompletionStage getCompletionStage() { + public CompletionStage getCompletionStage(@QueryParam(value = "baggageValue") String queryValue) { Span span = Span.current(); // Retrieve the test baggage value (if present) and store in the span @@ -93,9 +96,27 @@ public CompletionStage getCompletionStage() { return result; } + @GET + @Path("completionstageerror") + public CompletionStage getCompletionStageError(@QueryParam(value = "baggageValue") String queryValue) { + Span span = Span.current(); + + // Retrieve the test baggage value (if present) and store in the span + String baggageValue = Baggage.current().getEntryValue(BAGGAGE_KEY); + if (baggageValue != null) { + span.setAttribute(BAGGAGE_VALUE_ATTR, baggageValue); + } + + // Call a subtask, propagating the context + ExecutorService contextExecutor = Context.taskWrapping(managedExecutor); + CompletableFuture result = CompletableFuture.supplyAsync(this::subtaskError, contextExecutor);; + // Return the async result + return result; + } + @GET @Path("suspend") - public void getSuspend(@Suspended AsyncResponse async) { + public void getSuspend(@Suspended AsyncResponse async, @QueryParam(value = "baggageValue") String queryValue) { Span span = Span.current(); // Retrieve the test baggage value (if present) and store in the span @@ -116,6 +137,29 @@ public void getSuspend(@Suspended AsyncResponse async) { }); } + @GET + @Path("suspenderror") + public void getSuspendError(@Suspended AsyncResponse async, @QueryParam(value = "baggageValue") String queryValue) { + Span span = Span.current(); + + // Retrieve the test baggage value (if present) and store in the span + String baggageValue = Baggage.current().getEntryValue(BAGGAGE_KEY); + if (baggageValue != null) { + span.setAttribute(BAGGAGE_VALUE_ATTR, baggageValue); + } + + // Call a subtask, propagating the context + ExecutorService contextExecutor = Context.taskWrapping(managedExecutor); + contextExecutor.execute(() -> { + // Ensure we call resume, either with the result or a thrown exception + try { + async.resume(subtaskError()); + } catch (Throwable t) { + async.resume(t); + } + }); + } + private String subtask() { Span span = tracer.spanBuilder("subtask").startSpan(); try (Scope scope = span.makeCurrent()) { @@ -136,4 +180,27 @@ private String subtask() { span.end(); } } + + private Response subtaskError() { + Span span = tracer.spanBuilder("subtask").startSpan(); + try (Scope scope = span.makeCurrent()) { + // Sleep a while to ensure that this is running after get() has returned + Thread.sleep(3000); + + // Retrieve the test baggage value (if present) and store in the span + String baggageValue = Baggage.current().getEntryValue(BAGGAGE_KEY); + if (baggageValue != null) { + span.setAttribute(BAGGAGE_VALUE_ATTR, baggageValue); + } + + // Return bad request error code so we can differentiate between an + // unexpected exception which would cause an internal server error + return Response.status(Status.BAD_REQUEST).build(); + + } catch (InterruptedException e) { + throw new RuntimeException(e); + } finally { + span.end(); + } + } } diff --git a/tracing/tck/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/async/JaxRsServerAsyncTestEndpointClient.java b/tracing/tck/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/async/JaxRsServerAsyncTestEndpointClient.java index 3113bc6c..aac89edf 100644 --- a/tracing/tck/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/async/JaxRsServerAsyncTestEndpointClient.java +++ b/tracing/tck/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/async/JaxRsServerAsyncTestEndpointClient.java @@ -22,6 +22,7 @@ import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; +import jakarta.ws.rs.QueryParam; /** * Client interface for {@link JaxRsServerAsyncTestEndpoint} @@ -31,10 +32,17 @@ public interface JaxRsServerAsyncTestEndpointClient { @GET @Path("completionstage") - public String getCompletionStage(); + public String getCompletionStage(@QueryParam(value = "baggageValue") String baggageValue); + + @GET + @Path("completionstageerror") + public String getCompletionStageError(@QueryParam(value = "baggageValue") String baggageValue); @GET @Path("suspend") - public String getSuspend(); + public String getSuspend(@QueryParam(value = "baggageValue") String baggageValue); + @GET + @Path("suspenderror") + public String getSuspendError(@QueryParam(value = "baggageValue") String baggageValue); } diff --git a/tracing/tck/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/async/MpRestClientAsyncTest.java b/tracing/tck/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/async/MpRestClientAsyncTest.java index 2e3c17a9..a940ac8c 100644 --- a/tracing/tck/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/async/MpRestClientAsyncTest.java +++ b/tracing/tck/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/async/MpRestClientAsyncTest.java @@ -25,6 +25,7 @@ import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_STATUS_CODE; import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_TARGET; import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_URL; +import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; import static java.net.HttpURLConnection.HTTP_OK; import java.net.URL; @@ -49,7 +50,6 @@ import io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider; import io.opentelemetry.sdk.trace.data.SpanData; import jakarta.inject.Inject; -import jakarta.ws.rs.GET; import jakarta.ws.rs.HttpMethod; public class MpRestClientAsyncTest extends Arquillian { @@ -96,6 +96,12 @@ public void testIntegrationWithMpRestClient() throws Exception { readSpans(); } + @Test + public void testIntegrationWithMpRestClientAsyncError() throws Exception { + basicClient.get("/MpRestClientAsyncTestEndpoint/mpclientasyncerror"); + readSpansError(); + } + @Test public void testIntegrationWithMpRestClientAsync() throws Exception { basicClient.get("/MpRestClientAsyncTestEndpoint/mpclientasync"); @@ -128,12 +134,53 @@ public void readSpans() { Assert.assertEquals(httpGet.getSpanId(), secondURL.getParentSpanId()); Assert.assertEquals(firstURL.getSpanId(), httpGet.getParentSpanId()); - Assert.assertEquals(HTTP_OK, firstURL.getAttributes().get(HTTP_STATUS_CODE).intValue()); - Assert.assertEquals(HttpMethod.GET, firstURL.getAttributes().get(HTTP_METHOD)); - Assert.assertEquals("http", firstURL.getAttributes().get(HTTP_SCHEME)); + Assert.assertEquals(firstURL.getAttributes().get(HTTP_STATUS_CODE).intValue(), HTTP_OK); + Assert.assertEquals(firstURL.getAttributes().get(HTTP_METHOD), HttpMethod.GET); + Assert.assertEquals(firstURL.getAttributes().get(HTTP_SCHEME), "http"); + + Assert.assertEquals(httpGet.getAttributes().get(HTTP_STATUS_CODE).intValue(), HTTP_OK); + Assert.assertEquals(httpGet.getAttributes().get(HTTP_METHOD), HttpMethod.GET); + Assert.assertTrue(httpGet.getAttributes().get(HTTP_URL).contains("MpRestClientAsyncTestEndpoint")); + } + + public void readSpansError() { + + List spanData = spanExporter.getFinishedSpanItems(3); + + List serverSpans = spanExporter.getSpansWithKind(SpanKind.SERVER); + + SpanData firstURL = null; + SpanData secondURL = null; + SpanData httpGet = spanExporter.getFirst(SpanKind.CLIENT); + + for (SpanData span : serverSpans) { + if (span.getAttributes().get(HTTP_TARGET).contains("MpRestClientAsyncTestEndpoint/mpclient")) { + firstURL = span; + } else { + secondURL = span; + } + } + Assert.assertNotNull(firstURL); + Assert.assertNotNull(secondURL); + + // Assert correct parent-child links + // Shows that propagation occurred + Assert.assertEquals(httpGet.getSpanId(), secondURL.getParentSpanId()); + Assert.assertEquals(firstURL.getSpanId(), httpGet.getParentSpanId()); + + // requestMpClientError() returns a BAD_REQUEST status + Assert.assertEquals(secondURL.getAttributes().get(HTTP_STATUS_CODE).intValue(), HTTP_BAD_REQUEST); + Assert.assertEquals(secondURL.getAttributes().get(HTTP_METHOD), HttpMethod.GET); + Assert.assertEquals(secondURL.getAttributes().get(HTTP_SCHEME), "http"); - Assert.assertEquals(HTTP_OK, httpGet.getAttributes().get(HTTP_STATUS_CODE).intValue()); - Assert.assertEquals(HttpMethod.GET, httpGet.getAttributes().get(HTTP_METHOD)); + // which is received by the client + Assert.assertEquals(httpGet.getAttributes().get(HTTP_STATUS_CODE).intValue(), HTTP_BAD_REQUEST); + Assert.assertEquals(httpGet.getAttributes().get(HTTP_METHOD), HttpMethod.GET); Assert.assertTrue(httpGet.getAttributes().get(HTTP_URL).contains("MpRestClientAsyncTestEndpoint")); + + // Exception is handled in the receiving code so the status code here should be OK + Assert.assertEquals(firstURL.getAttributes().get(HTTP_STATUS_CODE).intValue(), HTTP_OK); + Assert.assertEquals(firstURL.getAttributes().get(HTTP_METHOD), HttpMethod.GET); + Assert.assertEquals(firstURL.getAttributes().get(HTTP_SCHEME), "http"); } } diff --git a/tracing/tck/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/async/MpRestClientAsyncTestEndpoint.java b/tracing/tck/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/async/MpRestClientAsyncTestEndpoint.java index d497feb7..a9036e37 100644 --- a/tracing/tck/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/async/MpRestClientAsyncTestEndpoint.java +++ b/tracing/tck/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/async/MpRestClientAsyncTestEndpoint.java @@ -19,6 +19,9 @@ */ package org.eclipse.microprofile.telemetry.tracing.tck.async; +import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; +import static org.testng.Assert.assertEquals; + import java.net.URI; import java.util.concurrent.CompletionStage; @@ -33,6 +36,8 @@ import jakarta.ws.rs.ApplicationPath; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.core.Application; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.Response; @@ -43,6 +48,7 @@ public class MpRestClientAsyncTestEndpoint extends Application { public static final String TEST_PASSED = "Test Passed"; + public static final String TEST_VALUE = "TEST_VALUE"; @Inject private InMemorySpanExporter spanExporter; @@ -69,7 +75,7 @@ public Response requestMpClient(@Context UriInfo uriInfo) { .baseUri(baseUri) .build(MpClientTwo.class); - String result = mpClientTwo.requestMpClient(); + String result = mpClientTwo.requestMpClient(TEST_VALUE); Assert.assertEquals(TEST_PASSED, result); } return Response.ok(Span.current().getSpanContext().getTraceId()).build(); @@ -97,16 +103,69 @@ public Response requestMpClientAsync(@Context UriInfo uriInfo) { .baseUri(baseUri) .build(MpClientTwoAsync.class); - String result = mpClientTwo.requestMpClient().toCompletableFuture().join(); + String result = mpClientTwo.requestMpClient(TEST_VALUE).toCompletableFuture().join(); Assert.assertEquals(TEST_PASSED, result); } return Response.ok(Span.current().getSpanContext().getTraceId()).build(); } + @GET + @Path("/mpclientasyncerror") + public Response requestMpClientAsyncError(@Context UriInfo uriInfo) { + Assert.assertNotNull(Span.current()); + + try (Scope s = Baggage.builder().put("foo", "bar").build().makeCurrent()) { + Baggage baggage = Baggage.current(); + Assert.assertEquals("bar", baggage.getEntryValue("foo")); + + String baseUrl = uriInfo.getAbsolutePath().toString().replace("/mpclientasyncerror", ""); + URI baseUri = null; + try { + baseUri = new URI(baseUrl); + } catch (Exception e) { + throw new RuntimeException(e); + } + + Assert.assertNotNull(Span.current()); + MpClientTwoAsyncError mpClientTwoError = RestClientBuilder.newBuilder() + .baseUri(baseUri) + .build(MpClientTwoAsyncError.class); + + String result = mpClientTwoError.requestMpClientError(TEST_VALUE) + .exceptionally(e -> "Exception:" + extractResponseStatus(e)) + .toCompletableFuture() + .join(); + + assertEquals("Exception:400", result); + } + return Response.ok(Span.current().getSpanContext().getTraceId()).build(); + } + + /** + * Search the cause chain of an exception to find a WebApplicationException and extract the status code + * + * @param originalException + * the exception + * @return the status code + */ + private static int extractResponseStatus(Throwable originalException) { + Throwable t = originalException; + // Look up the cause chain to find a WebApplicationException and extract the response + while (t != null && !(t instanceof WebApplicationException)) { + t = t.getCause(); + } + if (t == null) { + throw new RuntimeException("Non WebApplicationException thrown", originalException); + } + WebApplicationException webEx = (WebApplicationException) t; + return webEx.getResponse().getStatus(); + } + @GET @Path("requestMpClient") - public Response requestMpClient() { + public Response requestMpClient(@QueryParam("value") String value) { Assert.assertNotNull(Span.current()); + Assert.assertEquals(TEST_VALUE, value); Baggage baggage = Baggage.current(); Assert.assertEquals("bar", baggage.getEntryValue("foo")); @@ -114,11 +173,23 @@ public Response requestMpClient() { return Response.ok(TEST_PASSED).build(); } + @GET + @Path("requestMpClientError") + public Response requestMpClientError(@QueryParam("value") String value) { + Assert.assertNotNull(Span.current()); + Assert.assertEquals(TEST_VALUE, value); + Baggage baggage = Baggage.current(); + + Assert.assertEquals("bar", baggage.getEntryValue("foo")); + + return Response.status(HTTP_BAD_REQUEST).build(); + } + public interface MpClientTwo { @GET @Path("requestMpClient") - public String requestMpClient(); + public String requestMpClient(@QueryParam("value") String value); } @@ -126,7 +197,15 @@ public interface MpClientTwoAsync { @GET @Path("requestMpClient") - public CompletionStage requestMpClient(); + public CompletionStage requestMpClient(@QueryParam("value") String value); + + } + + public interface MpClientTwoAsyncError { + + @GET + @Path("requestMpClientError") + public CompletionStage requestMpClientError(@QueryParam("value") String value); } } diff --git a/tracing/tck/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/spi/ExporterSpiTest.java b/tracing/tck/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/spi/ExporterSpiTest.java index b90d6840..cfe42cf5 100644 --- a/tracing/tck/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/spi/ExporterSpiTest.java +++ b/tracing/tck/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/spi/ExporterSpiTest.java @@ -66,9 +66,7 @@ public void testExporter() { AttributeKey FOO_KEY = AttributeKey.stringKey("foo"); Span span = tracer.spanBuilder("test span").setAttribute(FOO_KEY, "bar").startSpan(); span.end(); - System.out.println("Hello" + span); List spanItems = exporter.getFinishedSpanItems(1); - System.out.println("Hello" + spanItems); assertEquals(spanItems.size(), 1); SpanData spanData = spanItems.get(0); assertTrue(spanData.getName().contains("test span"));