/** * 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.integration; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.TimeZone; import okhttp3.Call; import okhttp3.HttpUrl; import okhttp3.OkHttpClient; import okhttp3.Request; import org.junit.Before; import org.junit.Test; import zipkin.Annotation; import zipkin.Span; import zipkin.internal.CallbackCaptor; import zipkin.internal.Util; import zipkin.storage.elasticsearch.http.ElasticsearchHttpStorage; import zipkin.storage.elasticsearch.http.InternalForTests; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static zipkin.Constants.SERVER_RECV; import static zipkin.Constants.SERVER_SEND; import static zipkin.TestObjects.DAY; import static zipkin.TestObjects.TODAY; import static zipkin.TestObjects.WEB_ENDPOINT; import static zipkin.storage.elasticsearch.http.integration.LazyElasticsearchHttpStorage.INDEX; abstract class ElasticsearchHttpSpanConsumerTest { // we assume that buckets use a simple daily format. SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); ElasticsearchHttpSpanConsumerTest() { dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); } /** Should maintain state between multiple calls within a test. */ abstract ElasticsearchHttpStorage storage(); /** Clears store between tests. */ @Before public void clear() throws IOException { InternalForTests.clear(storage()); } @Test public void spanGoesIntoADailyIndex_whenTimestampIsDerived() throws Exception { long twoDaysAgo = (TODAY - 2 * DAY); Span span = Span.builder().traceId(20L).id(20L).name("get") .addAnnotation(Annotation.create(twoDaysAgo * 1000, SERVER_RECV, WEB_ENDPOINT)) .addAnnotation(Annotation.create(TODAY * 1000, SERVER_SEND, WEB_ENDPOINT)) .build(); accept(span); // make sure the span went into an index corresponding to its first annotation timestamp assertThat(findSpans(twoDaysAgo, span.traceId)) .contains("\"hits\":{\"total\":1"); } String findSpans(long endTs, long traceId) throws IOException { return new OkHttpClient().newCall(new Request.Builder().url( HttpUrl.parse(baseUrl()).newBuilder() .addPathSegment(INDEX + "-" + dateFormat.format(new Date(endTs))) .addPathSegment("span") .addPathSegment("_search") .addQueryParameter("q", "traceId:" + Util.toLowerHex(traceId)).build()) .get().build()).execute().body().string(); } @Test public void serviceSpanGoesIntoADailyIndex_whenTimestampIsDerived() throws Exception { long twoDaysAgo = (TODAY - 2 * DAY); Span span = Span.builder().traceId(20L).id(20L).name("get") .addAnnotation(Annotation.create(twoDaysAgo * 1000, SERVER_RECV, WEB_ENDPOINT)) .addAnnotation(Annotation.create(TODAY * 1000, SERVER_SEND, WEB_ENDPOINT)) .build(); accept(span); // make sure the servicespan went into an index corresponding to its first annotation timestamp assertThat(findServiceSpan(twoDaysAgo, WEB_ENDPOINT.serviceName)) .contains("\"hits\":{\"total\":1"); } String findServiceSpan(long endTs, String serviceName) throws IOException { return new OkHttpClient().newCall(new Request.Builder().url( HttpUrl.parse(baseUrl()).newBuilder() .addPathSegment(INDEX + "-" + dateFormat.format(new Date(endTs))) .addPathSegment("servicespan") .addPathSegment("_search") .addQueryParameter("q", "serviceName:" + serviceName).build()) .get().build()).execute().body().string(); } @Test public void spanGoesIntoADailyIndex_whenTimestampIsExplicit() throws Exception { long twoDaysAgo = (TODAY - 2 * DAY); Span span = Span.builder().traceId(20L).id(20L).name("get") .timestamp(twoDaysAgo * 1000).build(); accept(span); // make sure the span went into an index corresponding to its timestamp, not collection time assertThat(findSpans(twoDaysAgo, span.traceId)) .contains("\"hits\":{\"total\":1"); } @Test public void spanGoesIntoADailyIndex_fallsBackToTodayWhenNoTimestamps() throws Exception { Span span = Span.builder().traceId(20L).id(20L).name("get").build(); accept(span); // make sure the span went into an index corresponding to collection time assertThat(findSpans(TODAY, span.traceId)) .contains("\"hits\":{\"total\":1"); } @Test public void searchByTimestampMillis() throws Exception { Span span = Span.builder().timestamp(TODAY * 1000).traceId(20L).id(20L).name("get").build(); accept(span); Call searchRequest = new OkHttpClient().newCall(new Request.Builder().url( HttpUrl.parse(baseUrl()).newBuilder() .addPathSegment(INDEX + "-*") .addPathSegment("span") .addPathSegment("_search") .addQueryParameter("q", "timestamp_millis:" + TODAY).build()) .get().tag("search-terms").build()); assertThat(searchRequest.execute().body().string()) .contains("\"hits\":{\"total\":1"); } abstract String baseUrl(); void accept(Span span) throws Exception { CallbackCaptor<Void> callback = new CallbackCaptor<>(); storage().asyncSpanConsumer().accept(asList(span), callback); callback.get(); } }