Working Zipkin Stream tests

This commit is contained in:
Marcin Grzejszczak
2016-01-12 15:30:23 +01:00
parent e9f307a0be
commit 091a4fc218
15 changed files with 597 additions and 261 deletions

View File

@@ -63,6 +63,16 @@
<artifactId>brave-core</artifactId>
<version>${brave.version}</version>
</dependency>
<dependency>
<groupId>com.github.kristofa</groupId>
<artifactId>brave-http</artifactId>
<version>${brave.version}</version>
</dependency>
<dependency>
<groupId>com.github.kristofa</groupId>
<artifactId>brave-spring-web-servlet-interceptor</artifactId>
<version>${brave.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>

View File

@@ -46,11 +46,48 @@
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-dependencies</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-sample-test-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>docker-compose</artifactId>
<version>0.9.8</version>
<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>
<dependency>
<groupId>io.zipkin</groupId>
<artifactId>zipkin-java-core</artifactId>
<version>0.1.2</version>
<scope>compile</scope>
</dependency>
</dependencies>
</dependencyManagement>

View File

@@ -118,6 +118,11 @@
<version>${testcontainers.jackson.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>docker-compose</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -31,9 +31,8 @@ import org.springframework.util.JdkIdGenerator;
import org.springframework.util.StringUtils;
import org.testcontainers.containers.DockerComposeContainer;
import sample.SampleMessagingApplication;
import tools.AbstractIntegrationTest;
import tools.AbstractDockerIntegrationTest;
import tools.IntegrationTestSpanCollector;
import tools.RequestSendingRunnable;
import java.io.File;
import java.util.Collection;
@@ -41,11 +40,11 @@ import java.util.Collection;
import static org.assertj.core.api.BDDAssertions.then;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = { AbstractIntegrationTest.Config.class, SampleMessagingApplication.class })
@SpringApplicationConfiguration(classes = { AbstractDockerIntegrationTest.Config.class, SampleMessagingApplication.class })
@WebIntegrationTest
@TestPropertySource(properties="sample.zipkin.enabled=true")
@Slf4j
public class MessagingApplicationDockerTests extends AbstractIntegrationTest {
public class MessagingApplicationDockerTests extends AbstractDockerIntegrationTest {
private static int port = 3381;
private static String sampleAppUrl = "http://localhost:" + port;
@@ -94,10 +93,6 @@ public class MessagingApplicationDockerTests extends AbstractIntegrationTest {
.anyMatch(binaryAnnotationKey::equals)).isTrue();
}
private RequestSendingRunnable httpMessageWithTraceIdInHeadersIsSuccessfullySent(String endpoint, String traceId) {
return new RequestSendingRunnable(restTemplate, endpoint, traceId);
}
private void thenAllSpansHaveTraceIdEqualTo(String traceId) {
then(integrationTestSpanCollector.hashedSpans.stream().allMatch(span -> span.getTrace_id() == zipkinHashedTraceId(traceId))).isTrue();
}

View File

@@ -16,6 +16,11 @@
<relativePath>..</relativePath>
</parent>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<build>
<pluginManagement>
<plugins>
@@ -28,6 +33,21 @@
</plugin>
</plugins>
</pluginManagement>
<plugins>
<!-- This is a test jar - it can have jdk8 -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>animal-sniffer-maven-plugin</artifactId>
<configuration>
<skip>true</skip>
<signature>
<groupId>org.codehaus.mojo.signature</groupId>
<artifactId>java17</artifactId>
<version>1.0</version>
</signature>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
@@ -35,10 +55,6 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-core</artifactId>
@@ -69,11 +85,6 @@
<version>1.3</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
@@ -84,34 +95,25 @@
<artifactId>awaitility</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>docker-compose</artifactId>
<version>0.9.8</version>
<scope>compile</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>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>docker-compose</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin</groupId>
<artifactId>zipkin-java-core</artifactId>
<scope>compile</scope>
</dependency>
</dependencies>

View File

@@ -0,0 +1,261 @@
/*
* 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 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 io.zipkin.Codec;
import io.zipkin.Span;
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 java.util.*;
import java.util.stream.Collectors;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.assertj.core.api.BDDAssertions.then;
/**
* @author Marcin Grzejszczak
*/
@Slf4j
public abstract class AbstractDockerIntegrationTest {
protected static int pollInterval = 1;
protected static int timeout = 120;
protected RestTemplate restTemplate = new AssertingRestTemplate();
public static ConditionFactory await() {
return Awaitility.await().pollInterval(pollInterval, SECONDS).atMost(timeout, SECONDS);
}
protected long zipkinHashedTraceId(String string) {
long h = 1125899906842597L;
if (string == null) {
return h;
}
int len = string.length();
for (int i = 0; i < len; i++) {
h = 31 * h + string.charAt(i);
}
return h;
}
protected String zipkinHashedHexStringTraceId(String traceId) {
long hashedTraceId = zipkinHashedTraceId(traceId);
return Long.toHexString(hashedTraceId);
}
public static String getDockerUrl() {
URI dockerUri = getDockerURI();
if (StringUtils.isEmpty(dockerUri.getScheme())) {
return "http://localhost";
}
return "http://" + dockerUri.getHost();
}
public 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 checkServerHealth("Zipkin Query Server", this::endpointToCheckZipkinQueryHealth);
}
protected Runnable zipkinServerIsUp() {
return checkServerHealth("Zipkin Stream Server", this::endpointToCheckZipkinServerHealth);
}
protected Runnable zipkinCollectorServerIsUp() {
return checkServerHealth("Zipkin collector", this::endpointToCheckZipkinCollectorHealth);
}
protected Runnable checkServerHealth(String appName, RequestExchanger requestExchanger) {
return () -> {
ResponseEntity<String> response = requestExchanger.exchange();
log.info("Response from the [{}] health endpoint is [{}]", appName, response);
then(response.getStatusCode()).isEqualTo(HttpStatus.OK);
log.info("[{}] is up!", appName);
};
}
private interface RequestExchanger {
ResponseEntity<String> exchange();
}
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> endpointToCheckZipkinServerHealth() {
URI uri = URI.create("http://localhost:9411/health");
log.info("Sending request to the Zipkin Server [{}]", 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 getZipkinRootUrl() + ":9411/api/v1/trace/";
}
protected String getZipkinRootUrl() {
return getDockerUrl();
}
protected String getZipkinServicesQueryUrl() {
return getDockerUrl() + ":9411/api/v1/services";
}
protected String getZipkinServerHealthUrl() {
return getDockerUrl() + ":9411/health";
}
protected String getZipkinCollectorHealthUrl() {
return getDockerUrl() + ":9900/health";
}
protected Runnable httpMessageWithTraceIdInHeadersIsSuccessfullySent(String endpoint, String traceId) {
return new RequestSendingRunnable(restTemplate, endpoint, traceId);
}
protected Runnable allSpansWereRegisteredInZipkinWithTraceIdEqualTo(String traceId) {
return () -> {
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<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!");
};
}
protected 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(getAppName()) ? Collections.EMPTY_LIST : names;
}
protected String getAppName() {
return "unknown";
}
protected List<String> annotationsNotFoundInZipkin(List<io.zipkin.Span> spans) {
String binaryAnnotationName = getRequiredBinaryAnnotationName();
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);
}
protected String getRequiredBinaryAnnotationName() {
return "random-sleep-millis";
}
@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(() -> {
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());
}
}
}

View File

@@ -1,163 +0,0 @@
/*
* 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 tools;
import com.github.kristofa.brave.EmptySpanCollectorMetricsHandler;
import com.github.kristofa.brave.HttpSpanCollector;
import com.github.kristofa.brave.SpanCollector;
import com.github.kristofa.brave.SpanCollectorMetricsHandler;
import com.jayway.awaitility.Awaitility;
import com.jayway.awaitility.core.ConditionFactory;
import java.net.URI;
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.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.assertj.core.api.BDDAssertions.then;
/**
* @author Marcin Grzejszczak
*/
@Slf4j
public abstract class AbstractIntegrationTest {
protected static int pollInterval = 1;
protected static int timeout = 120;
protected RestTemplate restTemplate = new AssertingRestTemplate();
protected static ConditionFactory await() {
return Awaitility.await().pollInterval(pollInterval, SECONDS).atMost(timeout, SECONDS);
}
protected long zipkinHashedTraceId(String string) {
long h = 1125899906842597L;
if (string == null) {
return h;
}
int len = string.length();
for (int i = 0; i < len; i++) {
h = 31 * h + string.charAt(i);
}
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 ResponseEntity<String> endpointToCheckZipkinQueryHealth() {
URI uri = URI.create(getZipkinServicesQueryUrl());
log.info("Sending request to the Zipkin query 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";
}
@Configuration
public static class Config {
@Bean
SpanCollector integrationTestSpanCollector() {
return new IntegrationTestSpanCollector();
}
}
@Configuration
@Slf4j
public static class ZipkinConfig {
@Bean
@SneakyThrows
public SpanCollector 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 SpanCollector getSpanCollector(ZipkinProperties zipkin) {
String url = "http://" + getDockerURI().getHost() + ":" + zipkin.getPort();
// TODO: parameterize this
SpanCollectorMetricsHandler metrics = new EmptySpanCollectorMetricsHandler();
return HttpSpanCollector.create(url, zipkin.getHttpConfig(), metrics);
}
}
}

View File

@@ -44,6 +44,7 @@ public class RequestSendingRunnable implements Runnable {
@Override
public void run() {
log.info("Sending the request to url [{}] with trace id in headers [{}]", url, traceId);
ResponseEntity<String> responseEntity = restTemplate.exchange(requestWithTraceId(traceId), String.class);
then(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
log.info("Received the following response [{}]", responseEntity);

View File

@@ -49,6 +49,18 @@
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>
<dependency>
<groupId>com.github.kristofa</groupId>
<artifactId>brave-spancollector-scribe</artifactId>
</dependency>
<dependency>
<groupId>com.github.kristofa</groupId>
<artifactId>brave-http</artifactId>
</dependency>
<dependency>
<groupId>com.github.kristofa</groupId>
<artifactId>brave-spring-web-servlet-interceptor</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
@@ -75,6 +87,27 @@
<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>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>docker-compose</artifactId>
</dependency>
</dependencies>
<build>

View File

@@ -0,0 +1,111 @@
package integration;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.connection.RabbitConnectionFactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.amqp.RabbitProperties;
import org.springframework.cloud.sleuth.TraceManager;
import org.springframework.cloud.sleuth.trace.TraceContextHolder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tools.AbstractDockerIntegrationTest;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.util.Random;
/**
* @author Marcin Grzejszczak
*/
@RestController
@Slf4j
public class SampleApp {
@Autowired
private TraceManager traceManager;
@SneakyThrows
@RequestMapping("/hi2")
public String hi2() {
log.info("I'm in the sample app");
final Random random = new Random();
int millis = random.nextInt(1000);
Thread.sleep(millis);
this.traceManager.addAnnotation("random-sleep-millis", String.valueOf(millis));
log.info("Current span is [{}]", TraceContextHolder.getCurrentSpan());
return "hi2";
}
@Configuration
@EnableAutoConfiguration
@Slf4j
public static class Config {
public static final int RABBITMQ_PORT = 5672;
@Bean SampleApp sampleApp() {
return new SampleApp();
}
@Bean
@SneakyThrows
ConnectionFactory connectionFactory() {
RabbitProperties config = rabbitProperties();
AbstractDockerIntegrationTest.await().until(() -> {
try {
ServerSocket serverSocket = new ServerSocket(RABBITMQ_PORT, 50,
InetAddress.getByName(AbstractDockerIntegrationTest.getDockerURI().getHost()));
serverSocket.close();
} catch (IOException e) {
log.info("RabbitMQ is up and running - proceeding");
return true;
}
log.warn("RabbitMQ has not started yet...");
return false;
});
RabbitConnectionFactoryBean factory = new RabbitConnectionFactoryBean();
if (config.getHost() != null) {
factory.setHost(config.getHost());
factory.setPort(config.getPort());
}
if (config.getUsername() != null) {
factory.setUsername(config.getUsername());
}
if (config.getPassword() != null) {
factory.setPassword(config.getPassword());
}
if (config.getVirtualHost() != null) {
factory.setVirtualHost(config.getVirtualHost());
}
if (config.getRequestedHeartbeat() != null) {
factory.setRequestedHeartbeat(config.getRequestedHeartbeat());
}
RabbitProperties.Ssl ssl = config.getSsl();
if (ssl.isEnabled()) {
factory.setUseSSL(true);
factory.setKeyStore(ssl.getKeyStore());
factory.setKeyStorePassphrase(ssl.getKeyStorePassword());
factory.setTrustStore(ssl.getTrustStore());
factory.setTrustStorePassphrase(ssl.getTrustStorePassword());
}
factory.afterPropertiesSet();
CachingConnectionFactory connectionFactory = new CachingConnectionFactory(
factory.getObject());
connectionFactory.setAddresses(config.getAddresses());
return connectionFactory;
}
RabbitProperties rabbitProperties() {
RabbitProperties rabbitProperties = new RabbitProperties();
rabbitProperties.setHost(AbstractDockerIntegrationTest.getDockerURI().getHost());
return rabbitProperties;
}
}
}

View File

@@ -0,0 +1,65 @@
/*
* 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 example.ZipkinStreamServerApplication;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
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.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.JdkIdGenerator;
import org.testcontainers.containers.DockerComposeContainer;
import tools.AbstractDockerIntegrationTest;
import java.io.File;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = { SampleApp.Config.class,
AbstractDockerIntegrationTest.ZipkinConfig.class, ZipkinStreamServerApplication.class })
@WebIntegrationTest()
@Slf4j
public class ZipkinStreamDockerTests extends AbstractDockerIntegrationTest {
private static int port = 9411;
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("rabbitmq_1", 15672)
.withExposedService("mysql_1", 3306);
@Test
@SneakyThrows
public void should_propagate_spans_to_zipkin() {
await().until(zipkinServerIsUp());
String traceId = new JdkIdGenerator().generateId().toString();
await().until(httpMessageWithTraceIdInHeadersIsSuccessfullySent(sampleAppUrl + "/hi2", traceId));
await().until(allSpansWereRegisteredInZipkinWithTraceIdEqualTo(traceId));
}
@Override
protected String getZipkinRootUrl() {
return "http://localhost";
}
}

View File

@@ -0,0 +1,13 @@
mysql:
image: mysql
ports:
- 3306:3306
environment:
- MYSQL_ROOT_PASSWORD=root
- MYSQL_DATABASE=test
rabbitmq:
image: rabbitmq:management
ports:
- 5672:5672
- 15672:15672

View File

@@ -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>

View File

@@ -100,6 +100,23 @@
<version>${testcontainers.jackson.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>docker-compose</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.kristofa</groupId>
<artifactId>brave-spancollector-scribe</artifactId>
</dependency>
<dependency>
<groupId>com.github.kristofa</groupId>
<artifactId>brave-http</artifactId>
</dependency>
<dependency>
<groupId>com.github.kristofa</groupId>
<artifactId>brave-spring-web-servlet-interceptor</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -15,7 +15,6 @@
*/
package integration;
import io.zipkin.Codec;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.junit.Before;
@@ -24,28 +23,21 @@ 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 tools.AbstractDockerIntegrationTest;
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 })
@SpringApplicationConfiguration(classes = { AbstractDockerIntegrationTest.ZipkinConfig.class, SampleZipkinApplication.class })
@WebIntegrationTest
@TestPropertySource(properties="sample.zipkin.enabled=true")
@Slf4j
public class ZipkinDockerTests extends AbstractIntegrationTest {
public class ZipkinDockerTests extends AbstractDockerIntegrationTest {
private static final String APP_NAME = "testsleuthzipkin";
private static int port = 3380;
@@ -68,65 +60,13 @@ public class ZipkinDockerTests extends AbstractIntegrationTest {
public void should_propagate_spans_to_zipkin() {
String traceId = new JdkIdGenerator().generateId().toString();
httpMessageWithTraceIdInHeadersIsSuccessfullySent(sampleAppUrl + "/hi2", traceId);
await().until(httpMessageWithTraceIdInHeadersIsSuccessfullySent(sampleAppUrl + "/hi2", traceId));
await().until(() -> {
allSpansWereRegisteredInZipkinWithTraceIdEqualTo(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!");
@Override
protected String getAppName() {
return APP_NAME;
}
private List<String> serviceNamesNotFoundInZipkin(List<io.zipkin.Span> spans) {
List<String> serviceNamesFoundInAnnotations = spans.stream()
.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()
.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()
.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();
}
}