Couchbase instrumentation
MODIFY
.circleci/config.yml
.circleci/config.yml
@@ -64,6 +64,8 @@ jobs:
- image: *default_container
# This is used by spymemcached instrumentation tests
- image: memcached
+ # This is used by couchbase instrumentation tests
+ - image: couchbase/server
steps:
- checkout

ADD
dd-java-agent/instrumentation/couchbase-2.0/couchbase-2.0.gradle
+apply from: "${rootDir}/gradle/java.gradle"
+
+apply plugin: 'org.unbroken-dome.test-sets'
+
+testSets {
+ latestDepTest {
+ dirName = 'test'
+ }
+}
+
+muzzle {
+ pass {
+ group = 'com.couchbase.client'
+ module = 'java-client'
+ versions = "[2.0.0,)"
+ assertInverse = true
+ }
+ fail {
+ group = 'com.couchbase.client'
+ module = 'couchbase-client'
+ versions = "(,)"
+ }
+}
+
+dependencies {
+ compileOnly group: 'com.couchbase.client', name: 'java-client', version: '2.0.0'
+
+ compile project(':dd-java-agent:agent-tooling')
+
+ compile deps.bytebuddy
+ compile deps.opentracing
+ annotationProcessor deps.autoservice
+ implementation deps.autoservice
+
+ testCompile project(':dd-java-agent:testing')
+
+ testCompile group: 'org.testcontainers', name: 'couchbase', version: '1.8.3'
+
+ testCompile group: 'org.springframework.data', name: 'spring-data-couchbase', version: '2.0.0.RELEASE'
+
+ latestDepTestCompile group: 'org.springframework.data', name: 'spring-data-couchbase', version: '3.+'
+ latestDepTestCompile group: 'com.couchbase.client', name: 'java-client', version: '2.6+'
+ latestDepTestCompile group: 'com.couchbase.client', name: 'encryption', version: '+'
+}
+
+testJava8Minimum += "**/CouchbaseSpringRepositoryTest.class"

ADD
dd-java-agent/instrumentation/couchbase-2.0/src/main/java/datadog/trace/instrumentation/couchbase/client/CouchbaseClientInstrumentation.java
+package datadog.trace.instrumentation.couchbase.client;
+
+import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType;
+import static io.opentracing.log.Fields.ERROR_OBJECT;
+import static net.bytebuddy.matcher.ElementMatchers.isInterface;
+import static net.bytebuddy.matcher.ElementMatchers.isMethod;
+import static net.bytebuddy.matcher.ElementMatchers.isPublic;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.not;
+import static net.bytebuddy.matcher.ElementMatchers.returns;
+
+import com.couchbase.client.java.CouchbaseCluster;
+import com.google.auto.service.AutoService;
+import datadog.trace.agent.tooling.Instrumenter;
+import datadog.trace.api.DDTags;
+import datadog.trace.bootstrap.CallDepthThreadLocalMap;
+import io.opentracing.Span;
+import io.opentracing.tag.Tags;
+import io.opentracing.util.GlobalTracer;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+import rx.Observable;
+import rx.functions.Action0;
+import rx.functions.Action1;
+
+@AutoService(Instrumenter.class)
+public class CouchbaseClientInstrumentation extends Instrumenter.Default {
+
+ public CouchbaseClientInstrumentation() {
+ super("couchbase");
+ }
+
+ @Override
+ protected boolean defaultEnabled() {
+ return false;
+ }
+
+ @Override
+ public ElementMatcher<TypeDescription> typeMatcher() {
+ return not(isInterface())
+ .and(
+ safeHasSuperType(
+ named("com.couchbase.client.java.cluster.AsyncClusterManager")
+ .or(named("com.couchbase.client.java.bucket.AsyncBucketManager"))
+ .or(named("com.couchbase.client.java.AsyncCluster"))
+ .or(named("com.couchbase.client.java.AsyncBucket"))));
+ }
+
+ @Override
+ public String[] helperClassNames() {
+ return new String[] {
+ getClass().getName() + "$TraceSpanStart",
+ getClass().getName() + "$TraceSpanFinish",
+ getClass().getName() + "$TraceSpanError",
+ };
+ }
+
+ @Override
+ public Map<ElementMatcher, String> transformers() {
+ return Collections.<ElementMatcher, String>singletonMap(
+ isMethod().and(isPublic()).and(returns(named("rx.Observable"))),
+ CouchbaseClientAdvice.class.getName());
+ }
+
+ public static class CouchbaseClientAdvice {
+
+ @Advice.OnMethodEnter
+ public static int trackCallDepth() {
+ return CallDepthThreadLocalMap.incrementCallDepth(CouchbaseCluster.class);
+ }
+
+ @Advice.OnMethodExit
+ public static void subscribeResult(
+ @Advice.Enter final int callDepth,
+ @Advice.Origin final Method method,
+ @Advice.Return(readOnly = false) Observable result) {
+ if (callDepth > 0) {
+ return;
+ }
+ CallDepthThreadLocalMap.reset(CouchbaseCluster.class);
+ final AtomicReference<Span> spanRef = new AtomicReference<>();
+ result =
+ result
+ .doOnSubscribe(new TraceSpanStart(method, spanRef))
+ .doOnCompleted(new TraceSpanFinish(spanRef))
+ .doOnError(new TraceSpanError(spanRef));
+ }
+ }
+
+ public static class TraceSpanStart implements Action0 {
+ private final Method method;
+ private final AtomicReference<Span> spanRef;
+
+ public TraceSpanStart(final Method method, final AtomicReference<Span> spanRef) {
+ this.method = method;
+ this.spanRef = spanRef;
+ }
+
+ @Override
+ public void call() {
+ final Class<?> declaringClass = method.getDeclaringClass();
+ final String className =
+ declaringClass.getSimpleName().replace("CouchbaseAsync", "").replace("DefaultAsync", "");
+ final String resourceName = className + "." + method.getName();
+
+ spanRef.set(
+ GlobalTracer.get()
+ .buildSpan("couchbase.call")
+ .withTag(DDTags.SERVICE_NAME, "couchbase")
+ .withTag(DDTags.RESOURCE_NAME, resourceName)
+ .start());
+ }
+ }
+
+ public static class TraceSpanFinish implements Action0 {
+ private final AtomicReference<Span> spanRef;
+
+ public TraceSpanFinish(final AtomicReference<Span> spanRef) {
+ this.spanRef = spanRef;
+ }
+
+ @Override
+ public void call() {
+ final Span span = spanRef.getAndSet(null);
+
+ if (span != null) {
+ span.finish();
+ }
+ }
+ }
+
+ public static class TraceSpanError implements Action1<Throwable> {
+ private final AtomicReference<Span> spanRef;
+
+ public TraceSpanError(final AtomicReference<Span> spanRef) {
+ this.spanRef = spanRef;
+ }
+
+ @Override
+ public void call(final Throwable throwable) {
+ final Span span = spanRef.getAndSet(null);
+ if (span != null) {
+ Tags.ERROR.set(span, true);
+ span.log(Collections.singletonMap(ERROR_OBJECT, throwable));
+ span.finish();
+ }
+ }
+ }
+}

ADD
dd-java-agent/instrumentation/couchbase-2.0/src/test/groovy/CouchbaseClientTest.groovy
+import com.couchbase.client.java.Bucket
+import com.couchbase.client.java.document.JsonDocument
+import com.couchbase.client.java.document.json.JsonObject
+import util.AbstractCouchbaseTest
+
+import static datadog.trace.agent.test.asserts.ListWriterAssert.assertTraces
+
+class CouchbaseClientTest extends AbstractCouchbaseTest {
+
+ def "test client #type"() {
+ setup:
+ // Connect to the bucket and open it
+ Bucket bkt = cluster.openBucket(bucketSettings.name())
+
+ // Create a JSON document and store it with the ID "helloworld"
+ JsonObject content = JsonObject.create().put("hello", "world")
+
+ when:
+ def inserted = bkt.upsert(JsonDocument.create("helloworld", content))
+ def found = bkt.get("helloworld")
+
+ then:
+ found == inserted
+ found.content().getString("hello") == "world"
+
+ and:
+ assertTraces(TEST_WRITER, 2) {
+ trace(0, 1) {
+ span(0) {
+ serviceName "couchbase"
+ resourceName "Bucket.upsert"
+ operationName "couchbase.call"
+ errored false
+ parent()
+ tags {
+ defaultTags()
+ }
+ }
+ }
+ trace(1, 1) {
+ span(0) {
+ serviceName "couchbase"
+ resourceName "Bucket.get"
+ operationName "couchbase.call"
+ errored false
+ parent()
+ tags {
+ defaultTags()
+ }
+ }
+ }
+ }
+
+ where:
+ bucketSettings << [BUCKET_COUCHBASE, BUCKET_MEMCACHE, BUCKET_EPHEMERAL]
+ type = bucketSettings.type().name()
+ }
+}

ADD
dd-java-agent/instrumentation/couchbase-2.0/src/test/groovy/springdata/CouchbaseConfig.groovy
+package springdata
+
+
+import org.springframework.context.annotation.ComponentScan
+import org.springframework.context.annotation.Configuration
+import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration
+import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories
+import util.AbstractCouchbaseTest
+
+@Configuration
+@EnableCouchbaseRepositories(basePackages = "springdata")
+@ComponentScan(basePackages = "springdata")
+class CouchbaseConfig extends AbstractCouchbaseConfiguration {
+
+ @Override
+ protected List<String> getBootstrapHosts() {
+ return Collections.singletonList("127.0.0.1")
+ }
+
+ @Override
+ protected String getBucketName() {
+ return AbstractCouchbaseTest.BUCKET_COUCHBASE.name()
+ }
+
+ @Override
+ protected String getBucketPassword() {
+ return AbstractCouchbaseTest.BUCKET_COUCHBASE.password()
+ }
+}

ADD
dd-java-agent/instrumentation/couchbase-2.0/src/test/groovy/springdata/CouchbaseSpringRepositoryTest.groovy
+package springdata
+
+import org.springframework.context.ApplicationContext
+import org.springframework.context.annotation.AnnotationConfigApplicationContext
+import org.springframework.data.repository.CrudRepository
+import spock.lang.Shared
+import util.AbstractCouchbaseTest
+
+import static datadog.trace.agent.test.asserts.ListWriterAssert.assertTraces
+
+class CouchbaseSpringRepositoryTest extends AbstractCouchbaseTest {
+ private static final Closure<Doc> FIND
+ static {
+ // This method is different in Spring Data 2+
+ try {
+ CrudRepository.getMethod("findOne", Serializable)
+ FIND = { DocRepository repo, String id ->
+ repo.findOne(id)
+ }
+ } catch (NoSuchMethodException e) {
+ FIND = { DocRepository repo, String id ->
+ repo.findById(id).get()
+ }
+ }
+ }
+ @Shared
+ ApplicationContext applicationContext
+ @Shared
+ DocRepository repo
+
+ def setupSpec() {
+ // Close all buckets and disconnect
+ cluster.disconnect()
+
+ applicationContext = new AnnotationConfigApplicationContext(CouchbaseConfig)
+ repo = applicationContext.getBean(DocRepository)
+ }
+
+ def setup() {
+ repo.deleteAll()
+// TEST_WRITER.waitForTraces(4)
+ TEST_WRITER.clear()
+ }
+
+ def "test empty repo"() {
+ when:
+ def result = repo.findAll()
+
+ then:
+ !result.iterator().hasNext()
+
+ and:
+ assertTraces(TEST_WRITER, 1) {
+ trace(0, 1) {
+ span(0) {
+ serviceName "couchbase"
+ resourceName "Bucket.query"
+ operationName "couchbase.call"
+ errored false
+ parent()
+ tags {
+ defaultTags()
+ }
+ }
+ }
+ }
+
+ where:
+ indexName = "test-index"
+ }
+
+ def "test CRUD"() {
+ when:
+ def doc = new Doc()
+
+ then:
+ repo.save(doc) == doc
+
+ and:
+ assertTraces(TEST_WRITER, 1) {
+ trace(0, 1) {
+ span(0) {
+ serviceName "couchbase"
+ resourceName "Bucket.upsert"
+ operationName "couchbase.call"
+ errored false
+ parent()
+ tags {
+ defaultTags()
+ }
+ }
+ }
+ }
+ TEST_WRITER.clear()
+
+ and:
+ FIND(repo, "1") == doc
+
+ and:
+ assertTraces(TEST_WRITER, 1) {
+ trace(0, 1) {
+ span(0) {
+ serviceName "couchbase"
+ resourceName "Bucket.get"
+ operationName "couchbase.call"
+ errored false
+ parent()
+ tags {
+ defaultTags()
+ }
+ }
+ }
+ }
+ TEST_WRITER.clear()
+
+ when:
+ doc.data == "other data"
+
+ then:
+ repo.save(doc) == doc
+ FIND(repo, "1") == doc
+
+ and:
+ assertTraces(TEST_WRITER, 2) {
+ trace(0, 1) {
+ span(0) {
+ serviceName "couchbase"
+ resourceName "Bucket.upsert"
+ operationName "couchbase.call"
+ errored false
+ parent()
+ tags {
+ defaultTags()
+ }
+ }
+ }
+ trace(1, 1) {
+ span(0) {
+ serviceName "couchbase"
+ resourceName "Bucket.get"
+ operationName "couchbase.call"
+ errored false
+ parent()
+ tags {
+ defaultTags()
+ }
+ }
+ }
+ }
+ TEST_WRITER.clear()
+
+ when:
+ repo.delete("1")
+
+ then:
+ !repo.findAll().iterator().hasNext()
+
+ and:
+ assertTraces(TEST_WRITER, 2) {
+ trace(0, 1) {
+ span(0) {
+ serviceName "couchbase"
+ resourceName "Bucket.remove"
+ operationName "couchbase.call"
+ errored false
+ parent()
+ tags {
+ defaultTags()
+ }
+ }
+ }
+ trace(1, 1) {
+ span(0) {
+ serviceName "couchbase"
+ resourceName "Bucket.query"
+ operationName "couchbase.call"
+ errored false
+ parent()
+ tags {
+ defaultTags()
+ }
+ }
+ }
+ }
+ }
+}

ADD
dd-java-agent/instrumentation/couchbase-2.0/src/test/groovy/springdata/CouchbaseSpringTemplateTest.groovy
+package springdata
+
+import com.couchbase.client.java.Bucket
+import org.springframework.data.couchbase.core.CouchbaseTemplate
+import spock.lang.Shared
+import util.AbstractCouchbaseTest
+
+import static datadog.trace.agent.test.asserts.ListWriterAssert.assertTraces
+
+class CouchbaseSpringTemplateTest extends AbstractCouchbaseTest {
+
+ @Shared
+ List<CouchbaseTemplate> templates
+
+ def setupSpec() {
+ Bucket bucketCouchbase = cluster.openBucket(BUCKET_COUCHBASE.name())
+ Bucket bucketMemcache = cluster.openBucket(BUCKET_MEMCACHE.name())
+ Bucket bucketEphemeral = cluster.openBucket(BUCKET_EPHEMERAL.name())
+ def info = manager.info()
+
+ templates = [new CouchbaseTemplate(info, bucketCouchbase),
+ new CouchbaseTemplate(info, bucketMemcache),
+ new CouchbaseTemplate(info, bucketEphemeral)]
+ }
+
+ def "test write/read #name"() {
+ setup:
+ def doc = new Doc()
+
+ when:
+ template.save(doc)
+
+ then:
+ template.findById("1", Doc) != null
+
+ when:
+ template.remove(doc)
+
+ then:
+ template.findById("1", Doc) == null
+
+ and:
+ assertTraces(TEST_WRITER, 4) {
+ trace(0, 1) {
+ span(0) {
+ serviceName "couchbase"
+ resourceName "Bucket.upsert"
+ operationName "couchbase.call"
+ errored false
+ parent()
+ tags {
+ defaultTags()
+ }
+ }
+ }
+ trace(1, 1) {
+ span(0) {
+ serviceName "couchbase"
+ resourceName "Bucket.get"
+ operationName "couchbase.call"
+ errored false
+ parent()
+ tags {
+ defaultTags()
+ }
+ }
+ }
+ trace(2, 1) {
+ span(0) {
+ serviceName "couchbase"
+ resourceName "Bucket.remove"
+ operationName "couchbase.call"
+ errored false
+ parent()
+ tags {
+ defaultTags()
+ }
+ }
+ }
+ trace(3, 1) {
+ span(0) {
+ serviceName "couchbase"
+ resourceName "Bucket.get"
+ operationName "couchbase.call"
+ errored false
+ parent()
+ tags {
+ defaultTags()
+ }
+ }
+ }
+ }
+
+ where:
+ template << templates
+ name = template.couchbaseBucket.name()
+ }
+}

ADD
dd-java-agent/instrumentation/couchbase-2.0/src/test/groovy/springdata/Doc.groovy
+package springdata
+
+import groovy.transform.EqualsAndHashCode
+import org.springframework.data.annotation.Id
+import org.springframework.data.couchbase.core.mapping.Document
+
+@Document
+@EqualsAndHashCode
+class Doc {
+ @Id
+ private String id = "1"
+ private String data = "some data"
+
+ String getId() {
+ return id
+ }
+
+ void setId(String id) {
+ this.id = id
+ }
+
+ String getData() {
+ return data
+ }
+
+ void setData(String data) {
+ this.data = data
+ }
+}

ADD
dd-java-agent/instrumentation/couchbase-2.0/src/test/groovy/springdata/DocRepository.groovy
+package springdata
+
+import org.springframework.data.couchbase.repository.CouchbaseRepository
+
+interface DocRepository extends CouchbaseRepository<Doc, String> {}

ADD
dd-java-agent/instrumentation/couchbase-2.0/src/test/groovy/util/AbstractCouchbaseTest.groovy
+package util
+
+import com.couchbase.client.core.env.AbstractServiceConfig
+import com.couchbase.client.core.env.KeyValueServiceConfig
+import com.couchbase.client.core.env.QueryServiceConfig
+import com.couchbase.client.core.env.SearchServiceConfig
+import com.couchbase.client.core.env.ViewServiceConfig
+import com.couchbase.client.core.metrics.DefaultLatencyMetricsCollectorConfig
+import com.couchbase.client.core.metrics.DefaultMetricsCollectorConfig
+import com.couchbase.client.core.utils.Base64
+import com.couchbase.client.java.Bucket
+import com.couchbase.client.java.CouchbaseCluster
+import com.couchbase.client.java.bucket.BucketType
+import com.couchbase.client.java.cluster.AuthDomain
+import com.couchbase.client.java.cluster.BucketSettings
+import com.couchbase.client.java.cluster.ClusterManager
+import com.couchbase.client.java.cluster.DefaultBucketSettings
+import com.couchbase.client.java.cluster.UserRole
+import com.couchbase.client.java.cluster.UserSettings
+import com.couchbase.client.java.env.DefaultCouchbaseEnvironment
+import com.couchbase.client.java.query.Index
+import com.couchbase.client.java.view.DefaultView
+import com.couchbase.client.java.view.DesignDocument
+import datadog.trace.agent.test.AgentTestRunner
+import org.testcontainers.couchbase.CouchbaseContainer
+import spock.lang.Requires
+import spock.lang.Shared
+
+import java.util.concurrent.TimeUnit
+
+// Do not run tests locally on Java7 since testcontainers are not compatible with Java7
+// It is fine to run on CI because CI provides couchbase externally, not through testcontainers
+@Requires({ "true" == System.getenv("CI") || jvm.java8Compatible })
+class AbstractCouchbaseTest extends AgentTestRunner {
+
+ static {
+ System.setProperty("dd.integration.couchbase.enabled", "true")
+ }
+ private static final USERNAME = "Administrator"
+ private static final PASSWORD = "password"
+
+ static final BUCKET_COUCHBASE = DefaultBucketSettings.builder()
+ .enableFlush(true)
+ .name("test-bucket-cb")
+ .password("test-pass")
+ .type(BucketType.COUCHBASE)
+ .quota(100)
+ .build()
+
+ static final BUCKET_MEMCACHE = DefaultBucketSettings.builder()
+ .enableFlush(true)
+ .name("test-bucket-mem")
+ .password("test-pass")
+ .type(BucketType.MEMCACHED)
+ .quota(100)
+ .build()
+
+ static final BUCKET_EPHEMERAL = DefaultBucketSettings.builder()
+ .enableFlush(true)
+ .name("test-bucket-emp")
+ .password("test-pass")
+ .type(BucketType.EPHEMERAL)
+ .quota(100)
+ .build()
+
+ /*
+ Note: type here has to stay undefined, otherwise tests will fail in CI in Java 7 because
+ 'testcontainers' are built for Java 8 and Java 7 cannot load this class.
+ */
+ @Shared
+ def couchbaseContainer
+ @Shared
+ CouchbaseCluster cluster
+ @Shared
+ ClusterManager manager
+
+ def setupSpec() {
+
+ /*
+ CI will provide us with couchbase container running along side our build.
+ When building locally, however, we need to take matters into our own hands
+ and we use 'testcontainers' for this.
+ */
+ if ("true" != System.getenv("CI")) {
+ couchbaseContainer = new CouchbaseContainer(
+ clusterUsername: USERNAME,
+ clusterPassword: PASSWORD)
+ couchbaseContainer.start()
+ cluster = couchbaseContainer.getCouchbaseCluster()
+ println "Couchbase container started"
+ } else {
+ initCluster()
+ cluster = CouchbaseCluster.create(envBuilder().build())
+ println "Using local couchbase"
+ }
+ manager = cluster.clusterManager(USERNAME, PASSWORD)
+
+ resetBucket(cluster, BUCKET_COUCHBASE)
+ resetBucket(cluster, BUCKET_MEMCACHE)
+ resetBucket(cluster, BUCKET_EPHEMERAL)
+ }
+
+ def cleanupSpec() {
+ // Close all buckets and disconnect
+ cluster.disconnect()
+
+ couchbaseContainer?.stop()
+ }
+
+ protected DefaultCouchbaseEnvironment.Builder envBuilder() {
+ def timeout = TimeUnit.MINUTES.toMillis(1)
+ return DefaultCouchbaseEnvironment.builder()
+ // settings to try to reduce variability in the tests:
+ .configPollInterval(0)
+ .configPollFloorInterval(0)
+ .runtimeMetricsCollectorConfig(DefaultMetricsCollectorConfig.create(0, TimeUnit.DAYS))
+ .networkLatencyMetricsCollectorConfig(DefaultLatencyMetricsCollectorConfig.create(0, TimeUnit.DAYS))
+ .queryServiceConfig(QueryServiceConfig.create(10, 10, AbstractServiceConfig.NO_IDLE_TIME))
+ .searchServiceConfig(SearchServiceConfig.create(10, 10, AbstractServiceConfig.NO_IDLE_TIME))
+ .viewServiceConfig(ViewServiceConfig.create(10, 10, AbstractServiceConfig.NO_IDLE_TIME))
+ .keyValueServiceConfig(KeyValueServiceConfig.create(10))
+ .computationPoolSize(1)
+ .analyticsTimeout(timeout)
+ .connectTimeout(timeout)
+ .kvTimeout(timeout)
+ .managementTimeout(timeout)
+ .queryTimeout(timeout)
+ .searchTimeout(timeout)
+ .viewTimeout(timeout)
+ }
+
+
+ protected void initCluster() {
+ assert callCouchbaseRestAPI("/pools/default", "memoryQuota=600&indexMemoryQuota=300") == 200
+ // This one fails if already initialized, so don't assert.
+ callCouchbaseRestAPI("/node/controller/setupServices", "services=kv%2Cn1ql%2Cindex%2Cfts")
+ assert callCouchbaseRestAPI("/settings/web", "username=$USERNAME&password=$PASSWORD&port=8091") == 200
+// callCouchbaseRestAPI(bucketURL, sampleBucketPayloadBuilder.toString())
+ assert callCouchbaseRestAPI("/settings/indexes", "indexerThreads=0&logLevel=info&maxRollbackPoints=5&storageMode=memory_optimized") == 200
+ }
+
+ protected int callCouchbaseRestAPI(String url, String payload) throws IOException {
+ String fullUrl = "http://localhost:8091" + url
+ HttpURLConnection httpConnection = (HttpURLConnection) ((new URL(fullUrl).openConnection()))
+ try {
+ httpConnection.setDoOutput(true)
+ httpConnection.setRequestMethod("POST")
+ httpConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded")
+ String encoded = Base64.encode((USERNAME + ":" + PASSWORD).getBytes("UTF-8"))
+ httpConnection.setRequestProperty("Authorization", "Basic " + encoded)
+ DataOutputStream out = new DataOutputStream(httpConnection.getOutputStream())
+ try {
+ out.writeBytes(payload)
+ out.flush()
+ def code = httpConnection.getResponseCode()
+ return code
+ } finally {
+ if (Collections.singletonList(out).get(0) != null) {
+ out.close()
+ }
+ }
+ } finally {
+ if (Collections.singletonList(httpConnection).get(0) != null) {
+ httpConnection.disconnect()
+ }
+ }
+ }
+
+ protected void resetBucket(CouchbaseCluster cluster, BucketSettings bucketSetting) {
+ ClusterManager clusterManager = cluster.clusterManager(USERNAME, PASSWORD)
+
+ // Remove existing Bucket
+ if (clusterManager.hasBucket(bucketSetting.name())) {
+ clusterManager.removeBucket(bucketSetting.name())
+ }
+ assert !clusterManager.hasBucket(bucketSetting.name())
+
+ // Insert Bucket... This generates a LOT of traces
+ BucketSettings bucketSettings = clusterManager.insertBucket(bucketSetting)
+ // Insert Bucket admin user
+ UserSettings userSettings = UserSettings.build().password(bucketSetting.password())
+ .roles([new UserRole("bucket_full_access", bucketSetting.name())])
+
+ clusterManager.upsertUser(AuthDomain.LOCAL, bucketSetting.name(), userSettings)
+
+ Bucket bucket = cluster.openBucket(bucketSettings.name(), bucketSettings.password())
+
+// boolean queryServiceEnabled = false
+// while (!queryServiceEnabled) {
+// GetClusterConfigResponse clusterConfig = bucket.core().<GetClusterConfigResponse> send(new GetClusterConfigRequest()).toBlocking().single()
+// queryServiceEnabled = clusterConfig.config().bucketConfig(bucket.name()).serviceEnabled(ServiceType.QUERY)
+// }
+ bucket.query(Index.createPrimaryIndex().on(bucketSetting.name()))
+
+ TEST_WRITER.clear() // remove traces generated by insertBucket
+ if (BucketType.COUCHBASE.equals(bucketSettings.type())) {
+ bucket.bucketManager().insertDesignDocument(
+ DesignDocument.create("doc", Collections.singletonList(DefaultView.create("all",
+ '''
+ function (doc, meta) {
+ if (doc._class == "springdata.Doc") {
+ emit(meta.id, null);
+ }
+ }
+ '''
+ )))
+ )
+ }
+ }
+}

RENAME
dd-java-agent/instrumentation/elasticsearch-transport-2/src/test/groovy/Elasticsearch2SpringTemplateTest.groovy
dd-java-agent/instrumentation/elasticsearch-transport-2/src/test/groovy/springdata/Elasticsearch2SpringTemplateTest.groovy
+package springdata
+
import datadog.trace.agent.test.AgentTestRunner
import datadog.trace.agent.test.TestUtils
import datadog.trace.api.DDSpanTypes
@@ -17,7 +19,6 @@ import org.springframework.data.elasticsearch.core.query.IndexQueryBuilder
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder
import spock.lang.Shared
-import springdata.Doc
import java.util.concurrent.atomic.AtomicLong

MODIFY
dd-java-agent/instrumentation/spymemcached-2.12/spymemcached-2.12.gradle
dd-java-agent/instrumentation/spymemcached-2.12/spymemcached-2.12.gradle
@@ -33,7 +33,6 @@ dependencies {
testCompile project(':dd-java-agent:testing')
testCompile group: 'net.spy', name: 'spymemcached', version: '2.12.0'
- testCompile group: 'org.spockframework', name: 'spock-core', version: '1.1-groovy-2.4'
testCompile group: 'org.testcontainers', name: 'testcontainers', version: '1.7.3'
}

MODIFY
dd-java-agent/testing/src/main/java/datadog/trace/agent/test/AgentTestRunner.java
dd-java-agent/testing/src/main/java/datadog/trace/agent/test/AgentTestRunner.java
@@ -14,7 +14,6 @@ import io.opentracing.Tracer;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.util.List;
-import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
@@ -124,7 +123,7 @@ public abstract class AgentTestRunner extends Specification {
final ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(AgentTestRunner.class.getClassLoader());
- assert ServiceLoader.load(Instrumenter.class).iterator().hasNext();
+ // assert ServiceLoader.load(Instrumenter.class).iterator().hasNext();
activeTransformer = AgentInstaller.installBytebuddyAgent(instrumentation, ERROR_LISTENER);
} finally {
Thread.currentThread().setContextClassLoader(contextLoader);

MODIFY
settings.gradle
settings.gradle
@@ -13,6 +13,7 @@ include ':dd-java-agent:instrumentation:akka-http-10.0'
include ':dd-java-agent:instrumentation:apache-httpclient-4.3'
include ':dd-java-agent:instrumentation:aws-java-sdk-1.11.0'
include ':dd-java-agent:instrumentation:aws-java-sdk-1.11.106'
+include ':dd-java-agent:instrumentation:couchbase-2.0'
include ':dd-java-agent:instrumentation:datastax-cassandra-3.2'
include ':dd-java-agent:instrumentation:elasticsearch-rest-5'
include ':dd-java-agent:instrumentation:elasticsearch-transport-2'

Couchbase instrumentation