From 157d5bef11204c7ded11162333626b04fdb2c9b6 Mon Sep 17 00:00:00 2001 From: Artur Souza Date: Wed, 10 Mar 2021 22:11:06 -0800 Subject: [PATCH] Auto-validation for tracing sample. --- .github/workflows/validate.yml | 4 + examples/pom.xml | 5 + .../dapr/examples/tracing/InvokeClient.java | 9 +- .../java/io/dapr/examples/tracing/README.md | 41 ++++++-- .../io/dapr/examples/tracing/Validation.java | 97 +++++++++++++++++++ 5 files changed, 143 insertions(+), 13 deletions(-) create mode 100644 examples/src/main/java/io/dapr/examples/tracing/Validation.java diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 3f68af189..a43714c12 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -112,6 +112,10 @@ jobs: working-directory: ./examples run: | mm.py ./src/main/java/io/dapr/examples/invoke/grpc/README.md + - name: Validate tracing example + working-directory: ./examples + run: | + mm.py ./src/main/java/io/dapr/examples/tracing/README.md - name: Validate expection handling example working-directory: ./examples run: | diff --git a/examples/pom.xml b/examples/pom.xml index dc6b8562d..db569840c 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -74,6 +74,11 @@ spring-boot-autoconfigure 2.3.5.RELEASE + + com.jayway.jsonpath + json-path + 2.4.0 + io.opentelemetry opentelemetry-sdk diff --git a/examples/src/main/java/io/dapr/examples/tracing/InvokeClient.java b/examples/src/main/java/io/dapr/examples/tracing/InvokeClient.java index b918019ef..5f638199c 100644 --- a/examples/src/main/java/io/dapr/examples/tracing/InvokeClient.java +++ b/examples/src/main/java/io/dapr/examples/tracing/InvokeClient.java @@ -66,18 +66,15 @@ public class InvokeClient { }).subscriberContext(getReactorContext()).block(); } } - - // This is an example, so for simplicity we are just exiting here. - // Normally a dapr app would be a web service and not exit main. - System.out.println("Done"); } span.end(); shutdown(); + System.out.println("Done"); } - private static void shutdown() { + private static void shutdown() throws Exception { OpenTelemetrySdk.getGlobalTracerManagement().shutdown(); + Validation.validate(); } - } diff --git a/examples/src/main/java/io/dapr/examples/tracing/README.md b/examples/src/main/java/io/dapr/examples/tracing/README.md index 10b47ebcc..c8e18bca7 100644 --- a/examples/src/main/java/io/dapr/examples/tracing/README.md +++ b/examples/src/main/java/io/dapr/examples/tracing/README.md @@ -126,10 +126,19 @@ The instrumentation for the service happens via the `OpenTelemetryIterceptor` cl Use the follow command to execute the service: -```sh + + +```bash dapr run --app-id tracingdemo --app-port 3000 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.tracing.TracingDemoService -p 3000 ``` + + Once running, the TracingDemoService is now ready to be invoked by Dapr. ### Running the Demo middle service app @@ -208,10 +217,19 @@ public class OpenTelemetryConfig { Use the follow command to execute the service: -```sh + + +```bash dapr run --app-id tracingdemoproxy --app-port 3001 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.tracing.TracingDemoService -p 3001 ``` + + ### Running the InvokeClient app This sample code uses the Dapr SDK for invoking two remote methods (`proxy_echo` and `proxy_sleep`). It is also instrumented with OpenTelemetry. See the code snippet below: @@ -247,13 +265,10 @@ private static final String SERVICE_APP_ID = "tracingdemoproxy"; }).subscriberContext(getReactorContext()).block(); } } - - // This is an example, so for simplicity we are just exiting here. - // Normally a dapr app would be a web service and not exit main. - System.out.println("Done"); } span.end(); shutdown(); + System.out.println("Done"); } ///... } @@ -262,9 +277,21 @@ private static final String SERVICE_APP_ID = "tracingdemoproxy"; The class knows the app id for the remote application. It uses `invokeMethod` method to invoke API calls on the service endpoint. The request object includes an instance of `io.opentelemetry.context.Context` for the proper tracing headers to be propagated. Execute the follow script in order to run the InvokeClient example, passing two messages for the remote method: -```sh + + + +```bash dapr run -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.tracing.InvokeClient "message one" "message two" ``` + + + Once running, the output should display the messages sent from invoker in the demo service output as follows: ![exposeroutput](https://raw.githubusercontent.com/dapr/java-sdk/master/examples/src/main/resources/img/exposer-service.png) diff --git a/examples/src/main/java/io/dapr/examples/tracing/Validation.java b/examples/src/main/java/io/dapr/examples/tracing/Validation.java new file mode 100644 index 000000000..dea1e848c --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/tracing/Validation.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) Microsoft Corporation and Dapr Contributors. + * Licensed under the MIT License. + */ + +package io.dapr.examples.tracing; + +import com.jayway.jsonpath.DocumentContext; +import com.jayway.jsonpath.JsonPath; +import net.minidev.json.JSONArray; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +/** + * Class used to verify that traces are present as expected. + */ +final class Validation { + + private static final OkHttpClient HTTP_CLIENT = new OkHttpClient(); + + public static final String JSONPATH_PROXY_ECHO_SPAN_ID = + "$..[?(@.parentId=='%s' && @.name=='calllocal/tracingdemoproxy/proxy_echo')]['id']"; + + public static final String JSONPATH_ECHO_SPAN_ID = + "$..[?(@.parentId=='%s' && @.name=='calllocal/tracingdemo/echo')]['id']"; + + public static final String JSONPATH_PROXY_SLEEP_SPAN_ID = + "$..[?(@.parentId=='%s' && @.duration > 1000000 && @.name=='calllocal/tracingdemoproxy/proxy_sleep')]['id']"; + + public static final String JSONPATH_SLEEP_SPAN_ID = + "$..[?(@.parentId=='%s' && @.duration > 1000000 && @.name=='calllocal/tracingdemo/sleep')]['id']"; + + static void validate() throws Exception { + // Must wait for some time to make sure Zipkin receives all spans. + Thread.sleep(5000); + HttpUrl.Builder urlBuilder = new HttpUrl.Builder(); + urlBuilder.scheme("http") + .host("localhost") + .port(9411); + urlBuilder.addPathSegments("api/v2/traces"); + Request.Builder requestBuilder = new Request.Builder() + .url(urlBuilder.build()); + requestBuilder.method("GET", null); + + Request request = requestBuilder.build(); + + Response response = HTTP_CLIENT.newCall(request).execute(); + DocumentContext documentContext = JsonPath.parse(response.body().string()); + String mainSpanId = readOne(documentContext, "$..[?(@.name == \"example's main\")]['id']").toString(); + + // Validate echo + assertCount(documentContext, + String.format(JSONPATH_PROXY_ECHO_SPAN_ID, mainSpanId), + 2); + String proxyEchoSpanId = readOne(documentContext, + String.format(JSONPATH_PROXY_ECHO_SPAN_ID, mainSpanId)) + .toString(); + String proxyEchoSpanId2 = readOne(documentContext, + String.format(JSONPATH_PROXY_ECHO_SPAN_ID, proxyEchoSpanId)) + .toString(); + readOne(documentContext, + String.format(JSONPATH_ECHO_SPAN_ID, proxyEchoSpanId2)); + + // Validate sleep + assertCount(documentContext, + String.format(JSONPATH_PROXY_SLEEP_SPAN_ID, mainSpanId), + 2); + String proxySleepSpanId = readOne(documentContext, + String.format(JSONPATH_PROXY_SLEEP_SPAN_ID, mainSpanId)) + .toString(); + String proxySleepSpanId2 = readOne(documentContext, + String.format(JSONPATH_PROXY_SLEEP_SPAN_ID, proxySleepSpanId)) + .toString(); + readOne(documentContext, + String.format(JSONPATH_SLEEP_SPAN_ID, proxySleepSpanId2)); + } + + private static Object readOne(DocumentContext documentContext, String path) { + JSONArray arr = documentContext.read(path); + if (arr.size() == 0) { + throw new RuntimeException("No record found for " + path); + } + + return arr.get(0); + } + + private static void assertCount(DocumentContext documentContext, String path, int expectedCount) { + JSONArray arr = documentContext.read(path); + if (arr.size() != expectedCount) { + throw new RuntimeException( + String.format("Unexpected count %d vs expected %d for %s", arr.size(), expectedCount, path)); + } + } + +}