[#89] Added test for zipkin integration
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,12 @@
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-core</artifactId>
|
||||
<version>1.3</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
@@ -102,6 +108,12 @@
|
||||
<version>${testcontainers.jackson.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.zipkin</groupId>
|
||||
<artifactId>zipkin-java-core</artifactId>
|
||||
<version>0.1.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -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<String> 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<String> 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<String> endpointToCheckZipkinQueryHealth() {
|
||||
URI uri = URI.create(getZipkinServicesQueryUrl());
|
||||
log.info("Sending request to the Zipkin query service [{}]", uri);
|
||||
return exchangeRequest(uri);
|
||||
}
|
||||
|
||||
protected ResponseEntity<String> endpointToCheckZipkinCollectorHealth() {
|
||||
URI uri = URI.create(getZipkinCollectorHealthUrl());
|
||||
log.info("Sending request to the Zipkin collector service [{}]", uri);
|
||||
return exchangeRequest(uri);
|
||||
}
|
||||
|
||||
protected ResponseEntity<String> 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<String> 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
package tools;
|
||||
|
||||
/**
|
||||
* @author Marcin Grzejszczak
|
||||
*/
|
||||
interface DockerTests {
|
||||
}
|
||||
@@ -69,6 +69,29 @@
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-sleuth-sample-test-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>${testcontainers.jackson.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
<version>${testcontainers.jackson.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
<version>${testcontainers.jackson.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<include resource="org/springframework/boot/logging/logback/base.xml"/>
|
||||
<logger name="org.springframework.cloud.sleuth" level="DEBUG"/>
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE" />
|
||||
<appender-ref ref="FILE" />
|
||||
</root>
|
||||
</configuration>
|
||||
@@ -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<String> 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<io.zipkin.Span> spans = Codec.JSON.readSpans(response.getBody().getBytes());
|
||||
List<String> serviceNamesNotFoundInZipkin = serviceNamesNotFoundInZipkin(spans);
|
||||
List<String> 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<String> serviceNamesNotFoundInZipkin(List<io.zipkin.Span> spans) {
|
||||
List<String> 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<String> 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<String> names = new ArrayList<>();
|
||||
names.addAll(serviceNamesFoundInAnnotations);
|
||||
names.addAll(serviceNamesFoundInBinaryAnnotations);
|
||||
return names.contains(APP_NAME) ? Collections.EMPTY_LIST : names;
|
||||
}
|
||||
|
||||
private List<String> annotationsNotFoundInZipkin(List<io.zipkin.Span> spans) {
|
||||
String binaryAnnotationName = "random-sleep-millis";
|
||||
Optional<String> 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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user