/** * Copyright 2015-2017 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package zipkin.storage.elasticsearch.http; import java.io.IOException; import java.util.concurrent.TimeUnit; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import zipkin.Annotation; import zipkin.BinaryAnnotation; import zipkin.Codec; import zipkin.Span; import zipkin.TestObjects; import zipkin.internal.CallbackCaptor; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static zipkin.Constants.CLIENT_SEND; import static zipkin.Constants.SERVER_RECV; import static zipkin.TestObjects.TODAY; import static zipkin.internal.ApplyTimestampAndDuration.guessTimestamp; import static zipkin.internal.Util.UTF_8; import static zipkin.storage.elasticsearch.http.ElasticsearchHttpSpanConsumer.prefixWithTimestampMillis; public class ElasticsearchHttpSpanConsumerTest { @Rule public ExpectedException thrown = ExpectedException.none(); @Rule public MockWebServer es = new MockWebServer(); ElasticsearchHttpStorage storage = ElasticsearchHttpStorage.builder() .hosts(asList(es.url("").toString())) .build(); /** gets the index template so that each test doesn't have to */ @Before public void ensureIndexTemplate() throws IOException, InterruptedException { es.enqueue(new MockResponse().setBody("{\"version\":{\"number\":\"2.4.0\"}}")); es.enqueue(new MockResponse()); // get template storage.ensureIndexTemplate(); es.takeRequest(); // get version es.takeRequest(); // get template } @After public void close() throws IOException { storage.close(); } @Test public void addsTimestamp_millisIntoJson() throws Exception { es.enqueue(new MockResponse()); Span span = Span.builder().traceId(20L).id(20L).name("get") .timestamp(TODAY * 1000).build(); accept(span); assertThat(es.takeRequest().getBody().readUtf8()) .contains("\n{\"timestamp_millis\":" + Long.toString(TODAY) + ",\"traceId\":"); } @Test public void prefixWithTimestampMillis_readable() throws Exception { Span span = Span.builder().traceId(20L).id(20L).name("get") .timestamp(TODAY * 1000).build(); byte[] document = prefixWithTimestampMillis(Codec.JSON.writeSpan(span), span.timestamp); assertThat(Codec.JSON.readSpan(document)) .isEqualTo(span); // ignores timestamp_millis field } @Test public void doesntWriteSpanId() throws Exception { es.enqueue(new MockResponse()); accept(TestObjects.LOTS_OF_SPANS[0]); RecordedRequest request = es.takeRequest(); assertThat(request.getBody().readByteString().utf8()) .doesNotContain("\"_type\":\"span\",\"_id\""); } @Test public void writesSpanNaturallyWhenNoTimestamp() throws Exception { es.enqueue(new MockResponse()); Span span = Span.builder().traceId(1L).id(1L).name("foo").build(); accept(span); assertThat(es.takeRequest().getBody().readByteString().utf8()) .contains("\n" + new String(Codec.JSON.writeSpan(span), UTF_8) + "\n"); } @Test public void indexesServiceSpan_explicitTimestamp() throws Exception { es.enqueue(new MockResponse()); Span span = TestObjects.TRACE.get(0); accept(span); assertThat(es.takeRequest().getBody().readByteString().utf8()).endsWith( "\"_type\":\"servicespan\",\"_id\":\"web|get\"}}\n" + "{\"serviceName\":\"web\",\"spanName\":\"get\"}\n" ); } @Test public void traceIsSearchableBySRServiceName() throws Exception { es.enqueue(new MockResponse()); Span clientSpan = Span.builder().traceId(20L).id(22L).name("").parentId(21L).timestamp(0L) .addAnnotation(Annotation.create(0, CLIENT_SEND, TestObjects.WEB_ENDPOINT)) .build(); Span serverSpan = Span.builder().traceId(20L).id(22L).name("get").parentId(21L) .addAnnotation(Annotation.create(1000, SERVER_RECV, TestObjects.APP_ENDPOINT)) .build(); accept(serverSpan, clientSpan); // make sure that both timestamps are in the index assertThat(es.takeRequest().getBody().readByteString().utf8()) .contains("{\"timestamp_millis\":1") .contains("{\"timestamp_millis\":0"); } @Test public void indexesServiceSpan_multipleServices() throws Exception { es.enqueue(new MockResponse()); Span span = TestObjects.TRACE.get(1); accept(span); assertThat(es.takeRequest().getBody().readByteString().utf8()) .contains( "\"_type\":\"servicespan\",\"_id\":\"app|get\"}}\n" + "{\"serviceName\":\"app\",\"spanName\":\"get\"}\n" ) .contains( "\"_type\":\"servicespan\",\"_id\":\"web|get\"}}\n" + "{\"serviceName\":\"web\",\"spanName\":\"get\"}\n" ); } @Test public void indexesServiceSpan_basedOnGuessTimestamp() throws Exception { es.enqueue(new MockResponse()); Annotation cs = Annotation.create( TimeUnit.DAYS.toMicros(365), // 1971-01-01 CLIENT_SEND, TestObjects.APP_ENDPOINT ); Span span = Span.builder().traceId(1L).id(1L).name("s").addAnnotation(cs).build(); // sanity check data assertThat(span.timestamp).isNull(); assertThat(guessTimestamp(span)).isNotNull(); accept(span); // index timestamp is the server timestamp, not current time! assertThat(es.takeRequest().getBody().readByteString().utf8()).contains( "{\"index\":{\"_index\":\"zipkin-1971-01-01\",\"_type\":\"span\"}}\n", "{\"index\":{\"_index\":\"zipkin-1971-01-01\",\"_type\":\"servicespan\",\"_id\":\"app|s\"}}\n" ); } @Test public void indexesServiceSpan_basedOnAnnotationTimestamp() throws Exception { es.enqueue(new MockResponse()); Annotation foo = Annotation.create( TimeUnit.DAYS.toMicros(365), // 1971-01-01 "foo", TestObjects.APP_ENDPOINT ); Span span = Span.builder().traceId(1L).id(2L).parentId(1L).name("s").addAnnotation(foo).build(); // sanity check data assertThat(span.timestamp).isNull(); assertThat(guessTimestamp(span)).isNull(); accept(span); // index timestamp is the server timestamp, not current time! assertThat(es.takeRequest().getBody().readByteString().utf8()).contains( "{\"index\":{\"_index\":\"zipkin-1971-01-01\",\"_type\":\"span\"}}\n", "{\"index\":{\"_index\":\"zipkin-1971-01-01\",\"_type\":\"servicespan\",\"_id\":\"app|s\"}}\n" ); } @Test public void indexesServiceSpan_currentTimestamp() throws Exception { es.enqueue(new MockResponse()); Span span = Span.builder().traceId(1L).id(2L).parentId(1L).name("s") .addBinaryAnnotation(BinaryAnnotation.create("f", "", TestObjects.APP_ENDPOINT)) .build(); // sanity check data assertThat(span.timestamp).isNull(); assertThat(guessTimestamp(span)).isNull(); accept(span); String today = storage.indexNameFormatter().indexNameForTimestamp(TODAY); assertThat(es.takeRequest().getBody().readByteString().utf8()).contains( "{\"index\":{\"_index\":\"" + today + "\",\"_type\":\"span\"}}\n", "{\"index\":{\"_index\":\"" + today + "\",\"_type\":\"servicespan\",\"_id\":\"app|s\"}}\n" ); } @Test public void addsPipelineId() throws Exception { close(); storage = ElasticsearchHttpStorage.builder() .hosts(asList(es.url("").toString())) .pipeline("zipkin") .build(); ensureIndexTemplate(); es.enqueue(new MockResponse()); accept(TestObjects.TRACE.get(0)); RecordedRequest request = es.takeRequest(); assertThat(request.getPath()) .isEqualTo("/_bulk?pipeline=zipkin"); } void accept(Span ... spans) throws Exception { CallbackCaptor<Void> callback = new CallbackCaptor<>(); storage.asyncSpanConsumer().accept(asList(spans), callback); callback.get(); } }