diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index dac807076..8805fdcf3 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -63,6 +63,16 @@ brave-core ${brave.version} + + com.github.kristofa + brave-http + ${brave.version} + + + com.github.kristofa + brave-spring-web-servlet-interceptor + ${brave.version} + org.aspectj aspectjrt diff --git a/spring-cloud-sleuth-samples/pom.xml b/spring-cloud-sleuth-samples/pom.xml index 62d08837e..02c1d8648 100644 --- a/spring-cloud-sleuth-samples/pom.xml +++ b/spring-cloud-sleuth-samples/pom.xml @@ -46,11 +46,48 @@ + + org.springframework.cloud + spring-cloud-sleuth-dependencies + ${project.version} + pom + import + org.springframework.cloud spring-cloud-sleuth-sample-test-core ${project.version} + + org.testcontainers + docker-compose + 0.9.8 + 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 + + + io.zipkin + zipkin-java-core + 0.1.2 + compile + diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml index 20b9f92a4..aebe8ec36 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-messaging/pom.xml @@ -118,6 +118,11 @@ ${testcontainers.jackson.version} compile + + org.testcontainers + docker-compose + test + 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 2e571eb9c..996e0e1c2 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 @@ -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(); } 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 113b330bc..d8b452d8e 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 @@ -16,6 +16,11 @@ .. + + 1.8 + 1.8 + + @@ -28,6 +33,21 @@ + + + + org.codehaus.mojo + animal-sniffer-maven-plugin + + true + + org.codehaus.mojo.signature + java17 + 1.0 + + + + @@ -35,10 +55,6 @@ org.springframework.boot spring-boot-starter-web - - org.springframework.boot - spring-boot-starter-integration - org.springframework.cloud spring-cloud-sleuth-core @@ -69,11 +85,6 @@ 1.3 compile - - org.springframework.boot - spring-boot-starter-test - test - org.assertj assertj-core @@ -84,34 +95,25 @@ awaitility compile - - org.testcontainers - docker-compose - 0.9.8 - compile - 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 + + + org.testcontainers + docker-compose io.zipkin zipkin-java-core - compile diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/src/main/java/tools/AbstractDockerIntegrationTest.java b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/src/main/java/tools/AbstractDockerIntegrationTest.java new file mode 100644 index 000000000..156199c99 --- /dev/null +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/src/main/java/tools/AbstractDockerIntegrationTest.java @@ -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 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 exchange(); + } + + protected ResponseEntity endpointToCheckZipkinQueryHealth() { + URI uri = URI.create(getZipkinServicesQueryUrl()); + log.info("Sending request to the Zipkin query service [{}]", uri); + return exchangeRequest(uri); + } + + protected ResponseEntity endpointToCheckZipkinServerHealth() { + URI uri = URI.create("http://localhost:9411/health"); + log.info("Sending request to the Zipkin Server [{}]", 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 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 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!"); + }; + } + + protected 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(getAppName()) ? Collections.EMPTY_LIST : names; + } + + protected String getAppName() { + return "unknown"; + } + + protected List annotationsNotFoundInZipkin(List spans) { + String binaryAnnotationName = getRequiredBinaryAnnotationName(); + 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); + } + + 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()); + } + } +} 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 deleted file mode 100644 index 581ae5cd0..000000000 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/src/main/java/tools/AbstractIntegrationTest.java +++ /dev/null @@ -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 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 endpointToCheckZipkinQueryHealth() { - URI uri = URI.create(getZipkinServicesQueryUrl()); - log.info("Sending request to the Zipkin query 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"; - } - - @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); - } - } -} diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/src/main/java/tools/RequestSendingRunnable.java b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/src/main/java/tools/RequestSendingRunnable.java index e69ec0139..b16f73fc7 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/src/main/java/tools/RequestSendingRunnable.java +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-test-core/src/main/java/tools/RequestSendingRunnable.java @@ -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 responseEntity = restTemplate.exchange(requestWithTraceId(traceId), String.class); then(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); log.info("Received the following response [{}]", responseEntity); diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml index 5597f4c05..459aa8e95 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/pom.xml @@ -49,6 +49,18 @@ org.springframework.cloud spring-cloud-stream-binder-rabbit + + com.github.kristofa + brave-spancollector-scribe + + + com.github.kristofa + brave-http + + + com.github.kristofa + brave-spring-web-servlet-interceptor + com.h2database h2 @@ -75,6 +87,27 @@ spring-boot-starter-test test + + org.springframework.cloud + spring-cloud-sleuth-sample-test-core + test + + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.core + jackson-core + + + com.fasterxml.jackson.core + jackson-annotations + + + org.testcontainers + docker-compose + diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/src/test/java/integration/SampleApp.java b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/src/test/java/integration/SampleApp.java new file mode 100644 index 000000000..fe6564130 --- /dev/null +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/src/test/java/integration/SampleApp.java @@ -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; + } + } +} diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/src/test/java/integration/ZipkinStreamDockerTests.java b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/src/test/java/integration/ZipkinStreamDockerTests.java new file mode 100644 index 000000000..cae4fce7c --- /dev/null +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/src/test/java/integration/ZipkinStreamDockerTests.java @@ -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"; + } +} diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/src/test/resources/docker-compose.yml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/src/test/resources/docker-compose.yml new file mode 100644 index 000000000..819663b8a --- /dev/null +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/src/test/resources/docker-compose.yml @@ -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 diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/src/test/resources/logback.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/src/test/resources/logback.xml new file mode 100644 index 000000000..3cee99001 --- /dev/null +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin-stream/src/test/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/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-zipkin/pom.xml index 0b69d9ac8..62533c238 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 @@ -100,6 +100,23 @@ ${testcontainers.jackson.version} compile + + org.testcontainers + docker-compose + test + + + com.github.kristofa + brave-spancollector-scribe + + + com.github.kristofa + brave-http + + + com.github.kristofa + brave-spring-web-servlet-interceptor + 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 index 5722b7317..4366b4661 100644 --- 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 @@ -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 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!"); + @Override + protected String getAppName() { + return APP_NAME; } - - private List serviceNamesNotFoundInZipkin(List spans) { - List 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 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 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() - .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(); - } - }