diff --git a/.circleci/config.yml b/.circleci/config.yml index 2239a93f47..d6d9838b64 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -84,6 +84,38 @@ jobs: docker: - image: circleci/openjdk:9-jdk + agent_integration_tests: + <<: *defaults + docker: + - image: circleci/openjdk:8-jdk + - image: datadog/docker-dd-agent + env: + - DD_APM_ENABLED=true + - DD_BIND_HOST=0.0.0.0 + - DD_API_KEY=invalid_key_but_this_is_fine + steps: + - checkout + + - restore_cache: + # Reset the cache approx every release + keys: + - dd-trace-java-{{ checksum "dd-trace-java.gradle" }}-{{ .Branch }}-{{ .Revision }} + - dd-trace-java-{{ checksum "dd-trace-java.gradle" }}-{{ .Branch }} + - dd-trace-java-{{ checksum "dd-trace-java.gradle" }} + - dd-trace-java + + - run: + name: Run Trace Agent Tests + command: ./gradlew traceAgentTest --parallel --stacktrace --no-daemon + + - run: + name: Save Artifacts to (project-root)/build + when: always + command: .circleci/save_artifacts.sh + + - store_test_results: + path: build/test-results + scan_versions: <<: *defaults steps: @@ -170,6 +202,13 @@ workflows: tags: only: /.*/ + - agent_integration_tests: + requires: + - build + filters: + tags: + only: /.*/ + - scan_versions: requires: - build @@ -182,6 +221,7 @@ workflows: - test_7 - test_8 - test_9 + - agent_integration_tests filters: branches: only: master @@ -193,6 +233,7 @@ workflows: - test_7 - test_8 - test_9 + - agent_integration_tests filters: branches: ignore: /.*/ diff --git a/dd-trace-java.gradle b/dd-trace-java.gradle index 9255026d3f..3b19628aea 100644 --- a/dd-trace-java.gradle +++ b/dd-trace-java.gradle @@ -27,6 +27,8 @@ repositories { description = 'dd-trace-java' +task traceAgentTest {} + // Applied here to allow publishing of artifactory build info apply from: "${rootDir}/gradle/publish.gradle" diff --git a/dd-trace-ot/dd-trace-ot.gradle b/dd-trace-ot/dd-trace-ot.gradle index d4618352a1..e836fe82af 100644 --- a/dd-trace-ot/dd-trace-ot.gradle +++ b/dd-trace-ot/dd-trace-ot.gradle @@ -1,5 +1,6 @@ plugins { id "me.champeau.gradle.jmh" version "0.4.4" + id 'org.unbroken-dome.test-sets' version '1.4.4' } description = 'dd-trace-ot' @@ -29,6 +30,10 @@ if (JavaVersion.current() != JavaVersion.VERSION_1_8) { } } +testSets { + traceAgentTest +} + dependencies { compile project(':dd-trace-api') compile deps.opentracing diff --git a/dd-trace-ot/src/main/java/datadog/trace/common/writer/DDApi.java b/dd-trace-ot/src/main/java/datadog/trace/common/writer/DDApi.java index ca455a95aa..203c1507b3 100644 --- a/dd-trace-ot/src/main/java/datadog/trace/common/writer/DDApi.java +++ b/dd-trace-ot/src/main/java/datadog/trace/common/writer/DDApi.java @@ -37,11 +37,18 @@ public class DDApi { private final RateLimiter loggingRateLimiter = RateLimiter.create(1.0 / SECONDS_BETWEEN_ERROR_LOG); - private final ObjectMapper objectMapper = new ObjectMapper(new MessagePackFactory()); + private static final ObjectMapper objectMapper = new ObjectMapper(new MessagePackFactory()); public DDApi(final String host, final int port) { - if (traceEndpointAvailable("http://" + host + ":" + port + TRACES_ENDPOINT_V4) - && serviceEndpointAvailable("http://" + host + ":" + port + SERVICES_ENDPOINT_V4)) { + this( + host, + port, + traceEndpointAvailable("http://" + host + ":" + port + TRACES_ENDPOINT_V4) + && serviceEndpointAvailable("http://" + host + ":" + port + SERVICES_ENDPOINT_V4)); + } + + DDApi(final String host, final int port, final boolean v4EndpointsAvailable) { + if (v4EndpointsAvailable) { this.tracesEndpoint = "http://" + host + ":" + port + TRACES_ENDPOINT_V4; this.servicesEndpoint = "http://" + host + ":" + port + SERVICES_ENDPOINT_V4; } else { @@ -163,15 +170,16 @@ public class DDApi { } } - private boolean traceEndpointAvailable(final String endpoint) { + private static boolean traceEndpointAvailable(final String endpoint) { return endpointAvailable(endpoint, Collections.emptyList(), true); } - private boolean serviceEndpointAvailable(final String endpoint) { + private static boolean serviceEndpointAvailable(final String endpoint) { return endpointAvailable(endpoint, Collections.emptyMap(), true); } - private boolean endpointAvailable(final String endpoint, final Object data, final boolean retry) { + private static boolean endpointAvailable( + final String endpoint, final Object data, final boolean retry) { try { final HttpURLConnection httpCon = getHttpURLConnection(endpoint); @@ -192,7 +200,7 @@ public class DDApi { return false; } - private HttpURLConnection getHttpURLConnection(final String endpoint) throws IOException { + private static HttpURLConnection getHttpURLConnection(final String endpoint) throws IOException { final HttpURLConnection httpCon; final URL url = new URL(endpoint); httpCon = (HttpURLConnection) url.openConnection(); diff --git a/dd-trace-ot/src/test/groovy/datadog/trace/api/writer/DDApiTest.groovy b/dd-trace-ot/src/test/groovy/datadog/trace/api/writer/DDApiTest.groovy index a548d5988e..90f996e426 100644 --- a/dd-trace-ot/src/test/groovy/datadog/trace/api/writer/DDApiTest.groovy +++ b/dd-trace-ot/src/test/groovy/datadog/trace/api/writer/DDApiTest.groovy @@ -241,11 +241,8 @@ class DDApiTest extends Specification { def "Api ResponseListeners see 200 responses"() { setup: def agentResponse = new AtomicReference(null) - ResponseListener responseListener = new ResponseListener() { - @Override - void onResponse(String endpoint, JsonNode responseJson) { - agentResponse.set(responseJson.toString()) - } + ResponseListener responseListener = { String endpoint, JsonNode responseJson -> + agentResponse.set(responseJson.toString()) } boolean servicesAvailable = true def agent = ratpack { diff --git a/dd-trace-ot/src/traceAgentTest/groovy/DDApiIntegrationTest.groovy b/dd-trace-ot/src/traceAgentTest/groovy/DDApiIntegrationTest.groovy new file mode 100644 index 0000000000..3d02a8059a --- /dev/null +++ b/dd-trace-ot/src/traceAgentTest/groovy/DDApiIntegrationTest.groovy @@ -0,0 +1,107 @@ +import com.fasterxml.jackson.databind.JsonNode +import datadog.opentracing.DDSpan +import datadog.opentracing.DDSpanContext +import datadog.opentracing.DDTracer +import datadog.opentracing.PendingTrace +import datadog.trace.common.Service +import datadog.trace.common.sampling.PrioritySampling +import datadog.trace.common.writer.DDAgentWriter +import datadog.trace.common.writer.DDApi +import datadog.trace.common.writer.ListWriter +import spock.lang.Specification +import spock.lang.Unroll + +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicReference + +class DDApiIntegrationTest { + static class DDApiIntegrationV4Test extends Specification { + static final WRITER = new ListWriter() + static final TRACER = new DDTracer(WRITER) + static final CONTEXT = new DDSpanContext( + 1L, + 1L, + 0L, + "fakeService", + "fakeOperation", + "fakeResource", + PrioritySampling.UNSET, + Collections.emptyMap(), + false, + "fakeType", + Collections.emptyMap(), + new PendingTrace(TRACER, 1L), + TRACER) + + def api = new DDApi(DDAgentWriter.DEFAULT_HOSTNAME, DDAgentWriter.DEFAULT_PORT, v4()) + + def endpoint = new AtomicReference(null) + def agentResponse = new AtomicReference(null) + + DDApi.ResponseListener responseListener = { String receivedEndpoint, JsonNode responseJson -> + endpoint.set(receivedEndpoint) + agentResponse.set(responseJson.toString()) + } + + def setup() { + api.addResponseListener(responseListener) + } + + boolean v4() { + return true + } + + @Unroll + def "Sending traces succeeds (test #test)"() { + expect: + api.sendTraces(traces) + if (v4()) { + endpoint.get() == "http://localhost:8126/v0.4/traces" + agentResponse.get() == '{"rate_by_service":{"service:,env:":1}}' + } + + where: + traces | test + [] | 1 + [[], []] | 2 + [[new DDSpan(1, CONTEXT)]] | 3 + [[new DDSpan(TimeUnit.MILLISECONDS.toMicros(System.currentTimeMillis()), CONTEXT)]] | 4 + } + + def "Sending services succeeds"() { + expect: + api.sendServices(services) + endpoint.get() == null + agentResponse.get() == null + + where: + services | _ + [:] | _ + ['app': new Service("name", "appName", Service.AppType.WEB)] | _ + } + + @Unroll + def "Sending bad trace fails (test #test)"() { + expect: + api.sendTraces(traces) == false + + where: + traces | test + [""] | 1 + ["", 123] | 2 + [[:]] | 3 + [new Object()] | 4 + } + } + + static class DDApiIntegrationV3Test extends DDApiIntegrationV4Test { + boolean v4() { + return false + } + + def cleanup() { + assert endpoint.get() == null + assert agentResponse.get() == null + } + } +}