diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/src/test/java/integration/MessagingApplicationDockerTests.java b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/src/test/java/integration/MessagingApplicationDockerTests.java
index 0a76e75af..2e571eb9c 100644
--- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/src/test/java/integration/MessagingApplicationDockerTests.java
+++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/src/test/java/integration/MessagingApplicationDockerTests.java
@@ -15,7 +15,6 @@
*/
package integration;
-import com.github.kristofa.brave.SpanCollector;
import com.twitter.zipkin.gen.BinaryAnnotation;
import com.twitter.zipkin.gen.Span;
import lombok.extern.slf4j.Slf4j;
@@ -26,16 +25,13 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.WebIntegrationTest;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.JdkIdGenerator;
import org.springframework.util.StringUtils;
-import org.springframework.web.client.RestTemplate;
import org.testcontainers.containers.DockerComposeContainer;
import sample.SampleMessagingApplication;
import tools.AbstractIntegrationTest;
-import tools.AssertingRestTemplate;
import tools.IntegrationTestSpanCollector;
import tools.RequestSendingRunnable;
@@ -45,14 +41,14 @@ import java.util.Collection;
import static org.assertj.core.api.BDDAssertions.then;
@RunWith(SpringJUnit4ClassRunner.class)
-@SpringApplicationConfiguration(classes = { MessagingApplicationDockerTests.Config.class, SampleMessagingApplication.class })
+@SpringApplicationConfiguration(classes = { AbstractIntegrationTest.Config.class, SampleMessagingApplication.class })
@WebIntegrationTest
+@TestPropertySource(properties="sample.zipkin.enabled=true")
@Slf4j
public class MessagingApplicationDockerTests extends AbstractIntegrationTest {
private static int port = 3381;
private static String sampleAppUrl = "http://localhost:" + port;
- RestTemplate restTemplate = new AssertingRestTemplate();
@Autowired IntegrationTestSpanCollector integrationTestSpanCollector;
@ClassRule
@@ -106,10 +102,4 @@ public class MessagingApplicationDockerTests extends AbstractIntegrationTest {
then(integrationTestSpanCollector.hashedSpans.stream().allMatch(span -> span.getTrace_id() == zipkinHashedTraceId(traceId))).isTrue();
}
- @Configuration
- static class Config {
- @Bean SpanCollector integrationTestSpanCollector() {
- return new IntegrationTestSpanCollector();
- }
- }
}
diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml
index 3d6490461..5899d8fbe 100644
--- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml
+++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/pom.xml
@@ -63,6 +63,12 @@
junit
junit
+
+ org.hamcrest
+ hamcrest-core
+ 1.3
+ compile
+
org.springframework.boot
spring-boot-starter-test
@@ -102,6 +108,12 @@
${testcontainers.jackson.version}
compile
+
+ io.zipkin
+ zipkin-java-core
+ 0.1.1
+ compile
+
\ No newline at end of file
diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/src/main/java/tools/AbstractIntegrationTest.java b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/src/main/java/tools/AbstractIntegrationTest.java
index 80a94c073..23f9e2390 100644
--- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/src/main/java/tools/AbstractIntegrationTest.java
+++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/src/main/java/tools/AbstractIntegrationTest.java
@@ -15,23 +15,35 @@
*/
package tools;
+import com.github.kristofa.brave.SpanCollector;
+import com.github.kristofa.brave.scribe.ScribeSpanCollector;
import com.jayway.awaitility.Awaitility;
import com.jayway.awaitility.core.ConditionFactory;
-import org.junit.experimental.categories.Category;
-import org.springframework.beans.factory.annotation.Value;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.cloud.sleuth.zipkin.ZipkinProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.*;
+import org.springframework.util.StringUtils;
+import org.springframework.web.client.RestTemplate;
+
+import java.net.URI;
import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.assertj.core.api.BDDAssertions.then;
/**
* @author Marcin Grzejszczak
*/
-@Category(DockerTests.class)
-abstract public class AbstractIntegrationTest {
+@Slf4j
+public abstract class AbstractIntegrationTest {
- @Value("${test.pollinterval:1}") protected int pollInterval;
- @Value("${test.timeout:10}") protected int timeout;
+ protected static int pollInterval = 1;
+ protected static int timeout = 120;
+ protected RestTemplate restTemplate = new AssertingRestTemplate();
- protected ConditionFactory await() {
+ protected static ConditionFactory await() {
return Awaitility.await().pollInterval(pollInterval, SECONDS).atMost(timeout, SECONDS);
}
@@ -47,4 +59,120 @@ abstract public class AbstractIntegrationTest {
}
return h;
}
+
+ String zipkinHashedHexStringTraceId(String traceId) {
+ long hashedTraceId = zipkinHashedTraceId(traceId);
+ return Long.toHexString(hashedTraceId);
+ }
+
+ protected static String getDockerUrl() {
+ URI dockerUri = getDockerURI();
+ if (StringUtils.isEmpty(dockerUri.getScheme())) {
+ return "http://localhost";
+ }
+ return "http://" + dockerUri.getHost();
+ }
+
+ protected static URI getDockerURI() {
+ String dockerHost = System.getenv("DOCKER_HOST");
+ if (StringUtils.isEmpty(dockerHost)) {
+ return URI.create("http://localhost");
+ }
+ return URI.create(dockerHost);
+ }
+
+ protected Runnable zipkinQueryServerIsUp() {
+ return new Runnable() {
+ @Override
+ public void run() {
+ ResponseEntity response = endpointToCheckZipkinQueryHealth();
+ log.info("Response from the Zipkin query with current traces [{}]", response);
+ then(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+ log.info("Zipkin query server is up!");
+ }
+ };
+ }
+
+ protected Runnable zipkinCollectorServerIsUp() {
+ return new Runnable() {
+ @Override
+ public void run() {
+ ResponseEntity response = endpointToCheckZipkinCollectorHealth();
+ log.info("Response from the Zipkin collector's health endpoint is [{}]", response);
+ then(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+ log.info("Zipkin collector server is up!");
+ }
+ };
+ }
+
+ protected ResponseEntity endpointToCheckZipkinQueryHealth() {
+ URI uri = URI.create(getZipkinServicesQueryUrl());
+ log.info("Sending request to the Zipkin query service [{}]", uri);
+ return exchangeRequest(uri);
+ }
+
+ protected ResponseEntity endpointToCheckZipkinCollectorHealth() {
+ URI uri = URI.create(getZipkinCollectorHealthUrl());
+ log.info("Sending request to the Zipkin collector service [{}]", uri);
+ return exchangeRequest(uri);
+ }
+
+ protected ResponseEntity checkStateOfTheTraceId(String traceId) {
+ String hexTraceId = zipkinHashedHexStringTraceId(traceId);
+ URI uri = URI.create(getZipkinTraceQueryUrl() + hexTraceId);
+ log.info("Sending request to the Zipkin query service [{}]. Checking presence of trace id [{}] and its hex version [{}]", uri, traceId, hexTraceId);
+ return exchangeRequest(uri);
+ }
+
+ protected ResponseEntity exchangeRequest(URI uri) {
+ return restTemplate.exchange(
+ new RequestEntity<>(new HttpHeaders(), HttpMethod.GET, uri), String.class
+ );
+ }
+
+ protected String getZipkinTraceQueryUrl() {
+ return getDockerUrl() + ":9411/api/v1/trace/";
+ }
+
+ protected String getZipkinServicesQueryUrl() {
+ return getDockerUrl() + ":9411/api/v1/services";
+ }
+
+ protected String getZipkinCollectorHealthUrl() {
+ return getDockerUrl() + ":9900/health";
+ }
+
+ @Configuration
+ public static class Config {
+ @Bean
+ SpanCollector integrationTestSpanCollector() {
+ return new IntegrationTestSpanCollector();
+ }
+ }
+
+ @Configuration
+ @Slf4j
+ public static class ZipkinConfig {
+ @Bean
+ @SneakyThrows
+ public ScribeSpanCollector spanCollector(final ZipkinProperties zipkin) {
+ await().until(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ ZipkinConfig.this.getSpanCollector(zipkin);
+ } catch (Exception e) {
+ log.error("Exception occurred while trying to connect to zipkin [" + e.getCause() + "]");
+ throw new AssertionError(e);
+ }
+ }
+ });
+ return getSpanCollector(zipkin);
+ }
+
+ private ScribeSpanCollector getSpanCollector(ZipkinProperties zipkin) {
+ return new ScribeSpanCollector(getDockerURI().getHost(),
+ zipkin.getPort(), zipkin.getCollector());
+ }
+ }
}
diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/src/main/java/tools/AssertingRestTemplate.java b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/src/main/java/tools/AssertingRestTemplate.java
index 1d868b08a..b7142c9b3 100644
--- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/src/main/java/tools/AssertingRestTemplate.java
+++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/src/main/java/tools/AssertingRestTemplate.java
@@ -48,7 +48,7 @@ public class AssertingRestTemplate extends RestTemplate {
try {
return super.doExecute(url, method, requestCallback, responseExtractor);
} catch (Exception e) {
- log.error("Exception occurred while sending the message", e);
+ log.error("Exception occurred while sending the message to uri [" + url +"]. Exception [" + e.getCause() + "]");
throw new AssertionError(e);
}
}
diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/src/main/java/tools/DockerTests.java b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/src/main/java/tools/DockerTests.java
deleted file mode 100644
index c53a9fb99..000000000
--- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/src/main/java/tools/DockerTests.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package tools;
-
-/**
- * @author Marcin Grzejszczak
- */
-interface DockerTests {
-}
diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml
index 8e4a9d787..580055ee0 100644
--- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml
+++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml
@@ -69,6 +69,29 @@
spring-boot-starter-test
test
+
+ org.springframework.cloud
+ spring-cloud-sleuth-sample-test-core
+ test
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ ${testcontainers.jackson.version}
+ compile
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+ ${testcontainers.jackson.version}
+ compile
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+ ${testcontainers.jackson.version}
+ compile
+
diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/src/main/resources/logback.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/src/main/resources/logback.xml
new file mode 100644
index 000000000..3cee99001
--- /dev/null
+++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/src/main/resources/logback.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/src/test/java/integration/ZipkinDockerTests.java b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/src/test/java/integration/ZipkinDockerTests.java
new file mode 100644
index 000000000..867fa19d7
--- /dev/null
+++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/src/test/java/integration/ZipkinDockerTests.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2013-2015 the original author or 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 integration;
+
+import io.zipkin.Codec;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.SpringApplicationConfiguration;
+import org.springframework.boot.test.WebIntegrationTest;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.util.JdkIdGenerator;
+import org.testcontainers.containers.DockerComposeContainer;
+import sample.SampleZipkinApplication;
+import tools.AbstractIntegrationTest;
+import tools.RequestSendingRunnable;
+
+import java.io.File;
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static org.assertj.core.api.BDDAssertions.then;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@SpringApplicationConfiguration(classes = { AbstractIntegrationTest.ZipkinConfig.class, SampleZipkinApplication.class })
+@WebIntegrationTest
+@TestPropertySource(properties="sample.zipkin.enabled=true")
+@Slf4j
+public class ZipkinDockerTests extends AbstractIntegrationTest {
+
+ private static final String APP_NAME = "testsleuthzipkin";
+ private static int port = 3380;
+ private static String sampleAppUrl = "http://localhost:" + port;
+
+ @ClassRule
+ public static DockerComposeContainer environment =
+ new DockerComposeContainer(new File("src/test/resources/docker-compose.yml"))
+ .withExposedService("rabbitmq_1", 5672)
+ .withExposedService("collector_1", 9410)
+ .withExposedService("collector_1", 9900)
+ .withExposedService("mysql_1", 3306)
+ .withExposedService("query_1", 9411)
+ .withExposedService("query_1", 9901);
+
+ @Before
+ public void setup() {
+ await().until(zipkinQueryServerIsUp());
+ await().until(zipkinCollectorServerIsUp());
+ }
+
+ @Test
+ @SneakyThrows
+ public void should_propagate_spans_to_zipkin() {
+ String traceId = new JdkIdGenerator().generateId().toString();
+
+ httpMessageWithTraceIdInHeadersIsSuccessfullySent(sampleAppUrl + "/hi2", traceId);
+
+ await().until(() -> {
+ allSpansWereRegisteredInZipkinWithTraceIdEqualTo(traceId);
+ });
+ }
+
+ private void allSpansWereRegisteredInZipkinWithTraceIdEqualTo(String traceId) {
+ ResponseEntity response = checkStateOfTheTraceId(traceId);
+ log.info("Response from the Zipkin query service about the trace id [{}] for trace with id [{}]", response, traceId);
+ then(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+ then(response.hasBody()).isTrue();
+ List spans = Codec.JSON.readSpans(response.getBody().getBytes());
+ List serviceNamesNotFoundInZipkin = serviceNamesNotFoundInZipkin(spans);
+ List spanNamesNotFoundInZipkin = annotationsNotFoundInZipkin(spans);
+ log.info("The following services were not found in Zipkin {}", serviceNamesNotFoundInZipkin);
+ log.info("The following spans were not found in Zipkin {}", spanNamesNotFoundInZipkin);
+ then(serviceNamesNotFoundInZipkin).isEmpty();
+ then(spanNamesNotFoundInZipkin).isEmpty();
+ log.info("Zipkin tracing is working! Sleuth is working! Let's be happy!");
+ }
+
+ private List serviceNamesNotFoundInZipkin(List spans) {
+ List serviceNamesFoundInAnnotations = spans.stream()
+ .filter(span -> span.annotations != null)
+ .map(span -> span.annotations)
+ .flatMap(Collection::stream)
+ .filter(span -> span.endpoint != null)
+ .map(annotation -> annotation.endpoint)
+ .map(endpoint -> endpoint.serviceName)
+ .distinct()
+ .collect(Collectors.toList());
+ List serviceNamesFoundInBinaryAnnotations = spans.stream()
+ .filter(span -> span.binaryAnnotations != null)
+ .map(span -> span.binaryAnnotations)
+ .flatMap(Collection::stream)
+ .filter(span -> span.endpoint != null)
+ .map(annotation -> annotation.endpoint)
+ .map(endpoint -> endpoint.serviceName)
+ .distinct()
+ .collect(Collectors.toList());
+ List names = new ArrayList<>();
+ names.addAll(serviceNamesFoundInAnnotations);
+ names.addAll(serviceNamesFoundInBinaryAnnotations);
+ return names.contains(APP_NAME) ? Collections.EMPTY_LIST : names;
+ }
+
+ private List annotationsNotFoundInZipkin(List spans) {
+ String binaryAnnotationName = "random-sleep-millis";
+ Optional names = spans.stream()
+ .filter(span -> span.binaryAnnotations != null)
+ .map(span -> span.binaryAnnotations)
+ .flatMap(Collection::stream)
+ .filter(span -> span.endpoint != null)
+ .map(annotation -> annotation.key)
+ .filter(binaryAnnotationName::equals)
+ .findFirst();
+ return names.isPresent() ? Collections.EMPTY_LIST : Collections.singletonList(binaryAnnotationName);
+ }
+
+ private void httpMessageWithTraceIdInHeadersIsSuccessfullySent(String endpoint, String traceId) {
+ new RequestSendingRunnable(restTemplate, endpoint, traceId).run();
+ }
+
+}
diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/src/test/resources/docker-compose.yml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/src/test/resources/docker-compose.yml
new file mode 100644
index 000000000..22c392f76
--- /dev/null
+++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/src/test/resources/docker-compose.yml
@@ -0,0 +1,33 @@
+collector:
+ image: openzipkin/zipkin-collector:1.25.0
+ environment:
+ - TRANSPORT_TYPE=scribe
+ - STORAGE_TYPE=mysql
+ ports:
+ - 9410:9410
+ - 9900:9900
+ links:
+ - mysql:storage
+
+query:
+ image: openzipkin/zipkin-query:1.25.0
+ environment:
+ # Remove TRANSPORT_TYPE to disable tracing
+ - TRANSPORT_TYPE=http
+ - STORAGE_TYPE=mysql
+ ports:
+ - 9411:9411
+ - 9901:9901
+ links:
+ - mysql:storage
+
+rabbitmq:
+ image: rabbitmq:management
+ ports:
+ - 5672
+ - 15672
+
+mysql:
+ image: openzipkin/zipkin-mysql:1.25.0
+ ports:
+ - 3306:3306
\ No newline at end of file