Home | History | Annotate | Download | only in jaeger
      1 /*
      2  * Copyright 2018, OpenCensus Authors
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *     http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package io.opencensus.exporter.trace.jaeger;
     18 
     19 import static com.google.common.truth.Truth.assertThat;
     20 import static com.google.common.truth.Truth.assertWithMessage;
     21 import static java.lang.String.format;
     22 import static java.lang.System.currentTimeMillis;
     23 import static java.util.concurrent.TimeUnit.MILLISECONDS;
     24 
     25 import com.google.api.client.http.GenericUrl;
     26 import com.google.api.client.http.HttpRequest;
     27 import com.google.api.client.http.HttpRequestFactory;
     28 import com.google.api.client.http.HttpResponse;
     29 import com.google.api.client.http.javanet.NetHttpTransport;
     30 import com.google.gson.GsonBuilder;
     31 import com.google.gson.JsonArray;
     32 import com.google.gson.JsonNull;
     33 import com.google.gson.JsonObject;
     34 import com.google.gson.JsonParser;
     35 import io.opencensus.common.Scope;
     36 import io.opencensus.trace.AttributeValue;
     37 import io.opencensus.trace.SpanBuilder;
     38 import io.opencensus.trace.Status;
     39 import io.opencensus.trace.Tracer;
     40 import io.opencensus.trace.Tracing;
     41 import io.opencensus.trace.samplers.Samplers;
     42 import java.io.IOException;
     43 import java.util.Random;
     44 import org.junit.AfterClass;
     45 import org.junit.AssumptionViolatedException;
     46 import org.junit.Before;
     47 import org.junit.BeforeClass;
     48 import org.junit.Test;
     49 import org.slf4j.Logger;
     50 import org.slf4j.LoggerFactory;
     51 import org.testcontainers.containers.GenericContainer;
     52 import org.testcontainers.containers.wait.strategy.HttpWaitStrategy;
     53 
     54 public class JaegerExporterHandlerIntegrationTest {
     55   private static final String JAEGER_IMAGE = "jaegertracing/all-in-one:1.3";
     56   private static final int JAEGER_HTTP_PORT = 16686;
     57   private static final int JAEGER_HTTP_PORT_THRIFT = 14268;
     58   private static final String SERVICE_NAME = "test";
     59   private static final String SPAN_NAME = "my.org/ProcessVideo";
     60   private static final String START_PROCESSING_VIDEO = "Start processing video.";
     61   private static final String FINISHED_PROCESSING_VIDEO = "Finished processing video.";
     62 
     63   private static final Logger logger =
     64       LoggerFactory.getLogger(JaegerExporterHandlerIntegrationTest.class);
     65 
     66   private final HttpRequestFactory httpRequestFactory =
     67       new NetHttpTransport().createRequestFactory();
     68 
     69   private static GenericContainer<?> container;
     70 
     71   /** Starts a docker container optionally. For example, skips if Docker is unavailable. */
     72   @SuppressWarnings("rawtypes")
     73   @BeforeClass
     74   public static void startContainer() {
     75     try {
     76       container =
     77           new GenericContainer(JAEGER_IMAGE)
     78               .withExposedPorts(JAEGER_HTTP_PORT, JAEGER_HTTP_PORT_THRIFT)
     79               .waitingFor(new HttpWaitStrategy());
     80       container.start();
     81     } catch (RuntimeException e) {
     82       throw new AssumptionViolatedException("could not start docker container", e);
     83     }
     84   }
     85 
     86   @AfterClass
     87   public static void stopContainer() {
     88     if (container != null) {
     89       container.stop();
     90     }
     91   }
     92 
     93   @Before
     94   public void before() {
     95     JaegerTraceExporter.createAndRegister(thriftTracesEndpoint(), SERVICE_NAME);
     96   }
     97 
     98   @Test
     99   public void exportToJaeger() throws InterruptedException, IOException {
    100     Tracer tracer = Tracing.getTracer();
    101     final long startTimeInMillis = currentTimeMillis();
    102 
    103     SpanBuilder spanBuilder =
    104         tracer.spanBuilder(SPAN_NAME).setRecordEvents(true).setSampler(Samplers.alwaysSample());
    105     int spanDurationInMillis = new Random().nextInt(10) + 1;
    106 
    107     Scope scopedSpan = spanBuilder.startScopedSpan();
    108     try {
    109       tracer.getCurrentSpan().addAnnotation(START_PROCESSING_VIDEO);
    110       Thread.sleep(spanDurationInMillis); // Fake work.
    111       tracer.getCurrentSpan().putAttribute("foo", AttributeValue.stringAttributeValue("bar"));
    112       tracer.getCurrentSpan().addAnnotation(FINISHED_PROCESSING_VIDEO);
    113     } catch (Exception e) {
    114       tracer.getCurrentSpan().addAnnotation("Exception thrown when processing video.");
    115       tracer.getCurrentSpan().setStatus(Status.UNKNOWN);
    116       logger.error(e.getMessage());
    117     } finally {
    118       scopedSpan.close();
    119     }
    120 
    121     logger.info("Wait longer than the reporting duration...");
    122     // Wait for a duration longer than reporting duration (5s) to ensure spans are exported.
    123     long timeWaitingForSpansToBeExportedInMillis = 5100L;
    124     Thread.sleep(timeWaitingForSpansToBeExportedInMillis);
    125     JaegerTraceExporter.unregister();
    126     final long endTimeInMillis = currentTimeMillis();
    127 
    128     // Get traces recorded by Jaeger:
    129     HttpRequest request =
    130         httpRequestFactory.buildGetRequest(new GenericUrl(tracesForServiceEndpoint(SERVICE_NAME)));
    131     HttpResponse response = request.execute();
    132     String body = response.parseAsString();
    133     assertWithMessage("Response was: " + body).that(response.getStatusCode()).isEqualTo(200);
    134 
    135     JsonObject result = new JsonParser().parse(body).getAsJsonObject();
    136     // Pretty-print for debugging purposes:
    137     logger.debug(new GsonBuilder().setPrettyPrinting().create().toJson(result));
    138 
    139     assertThat(result).isNotNull();
    140     assertThat(result.get("total").getAsInt()).isEqualTo(0);
    141     assertThat(result.get("limit").getAsInt()).isEqualTo(0);
    142     assertThat(result.get("offset").getAsInt()).isEqualTo(0);
    143     assertThat(result.get("errors").getAsJsonNull()).isEqualTo(JsonNull.INSTANCE);
    144     JsonArray data = result.get("data").getAsJsonArray();
    145     assertThat(data).isNotNull();
    146     assertThat(data.size()).isEqualTo(1);
    147     JsonObject trace = data.get(0).getAsJsonObject();
    148     assertThat(trace).isNotNull();
    149     assertThat(trace.get("traceID").getAsString()).matches("[a-z0-9]{1,32}");
    150 
    151     JsonArray spans = trace.get("spans").getAsJsonArray();
    152     assertThat(spans).isNotNull();
    153     assertThat(spans.size()).isEqualTo(1);
    154 
    155     JsonObject span = spans.get(0).getAsJsonObject();
    156     assertThat(span).isNotNull();
    157     assertThat(span.get("traceID").getAsString()).matches("[a-z0-9]{1,32}");
    158     assertThat(span.get("spanID").getAsString()).matches("[a-z0-9]{1,16}");
    159     assertThat(span.get("flags").getAsInt()).isEqualTo(1);
    160     assertThat(span.get("operationName").getAsString()).isEqualTo(SPAN_NAME);
    161     assertThat(span.get("references").getAsJsonArray()).isEmpty();
    162     assertThat(span.get("startTime").getAsLong())
    163         .isAtLeast(MILLISECONDS.toMicros(startTimeInMillis));
    164     assertThat(span.get("startTime").getAsLong()).isAtMost(MILLISECONDS.toMicros(endTimeInMillis));
    165     assertThat(span.get("duration").getAsLong())
    166         .isAtLeast(MILLISECONDS.toMicros(spanDurationInMillis));
    167     assertThat(span.get("duration").getAsLong())
    168         .isAtMost(
    169             MILLISECONDS.toMicros(spanDurationInMillis + timeWaitingForSpansToBeExportedInMillis));
    170 
    171     JsonArray tags = span.get("tags").getAsJsonArray();
    172     assertThat(tags.size()).isEqualTo(1);
    173     JsonObject tag = tags.get(0).getAsJsonObject();
    174     assertThat(tag.get("key").getAsString()).isEqualTo("foo");
    175     assertThat(tag.get("type").getAsString()).isEqualTo("string");
    176     assertThat(tag.get("value").getAsString()).isEqualTo("bar");
    177 
    178     JsonArray logs = span.get("logs").getAsJsonArray();
    179     assertThat(logs.size()).isEqualTo(2);
    180 
    181     JsonObject log1 = logs.get(0).getAsJsonObject();
    182     long ts1 = log1.get("timestamp").getAsLong();
    183     assertThat(ts1).isAtLeast(MILLISECONDS.toMicros(startTimeInMillis));
    184     assertThat(ts1).isAtMost(MILLISECONDS.toMicros(endTimeInMillis));
    185     JsonArray fields1 = log1.get("fields").getAsJsonArray();
    186     assertThat(fields1.size()).isEqualTo(1);
    187     JsonObject field1 = fields1.get(0).getAsJsonObject();
    188     assertThat(field1.get("key").getAsString()).isEqualTo("description");
    189     assertThat(field1.get("type").getAsString()).isEqualTo("string");
    190     assertThat(field1.get("value").getAsString()).isEqualTo(START_PROCESSING_VIDEO);
    191 
    192     JsonObject log2 = logs.get(1).getAsJsonObject();
    193     long ts2 = log2.get("timestamp").getAsLong();
    194     assertThat(ts2).isAtLeast(MILLISECONDS.toMicros(startTimeInMillis));
    195     assertThat(ts2).isAtMost(MILLISECONDS.toMicros(endTimeInMillis));
    196     assertThat(ts2).isAtLeast(ts1);
    197     JsonArray fields2 = log2.get("fields").getAsJsonArray();
    198     assertThat(fields2.size()).isEqualTo(1);
    199     JsonObject field2 = fields2.get(0).getAsJsonObject();
    200     assertThat(field2.get("key").getAsString()).isEqualTo("description");
    201     assertThat(field2.get("type").getAsString()).isEqualTo("string");
    202     assertThat(field2.get("value").getAsString()).isEqualTo(FINISHED_PROCESSING_VIDEO);
    203 
    204     assertThat(span.get("processID").getAsString()).isEqualTo("p1");
    205     assertThat(span.get("warnings").getAsJsonNull()).isEqualTo(JsonNull.INSTANCE);
    206 
    207     JsonObject processes = trace.get("processes").getAsJsonObject();
    208     assertThat(processes.size()).isEqualTo(1);
    209     JsonObject p1 = processes.get("p1").getAsJsonObject();
    210     assertThat(p1.get("serviceName").getAsString()).isEqualTo(SERVICE_NAME);
    211     assertThat(p1.get("tags").getAsJsonArray().size()).isEqualTo(0);
    212     assertThat(trace.get("warnings").getAsJsonNull()).isEqualTo(JsonNull.INSTANCE);
    213   }
    214 
    215   private static String thriftTracesEndpoint() {
    216     return format(
    217         "http://%s:%s/api/traces",
    218         container.getContainerIpAddress(), container.getMappedPort(JAEGER_HTTP_PORT_THRIFT));
    219   }
    220 
    221   private static String tracesForServiceEndpoint(String service) {
    222     return format(
    223         "http://%s:%s/api/traces?service=%s",
    224         container.getContainerIpAddress(), container.getMappedPort(JAEGER_HTTP_PORT), service);
    225   }
    226 }
    227