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