diff --git a/instrumentation/mongo/mongo-3.1/javaagent/src/test/groovy/MongoClientTest.groovy b/instrumentation/mongo/mongo-3.1/javaagent/src/test/groovy/MongoClientTest.groovy deleted file mode 100644 index 8f8289d56dbc..000000000000 --- a/instrumentation/mongo/mongo-3.1/javaagent/src/test/groovy/MongoClientTest.groovy +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import com.mongodb.MongoClientOptions -import io.opentelemetry.instrumentation.mongo.v3_1.AbstractMongo31ClientTest -import io.opentelemetry.instrumentation.test.AgentTestTrait - -class MongoClientTest extends AbstractMongo31ClientTest implements AgentTestTrait { - @Override - void configureMongoClientOptions(MongoClientOptions.Builder options) { - } -} diff --git a/instrumentation/mongo/mongo-3.1/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/mongo/v3_1/MongoClientTest.java b/instrumentation/mongo/mongo-3.1/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/mongo/v3_1/MongoClientTest.java new file mode 100644 index 000000000000..b3cacf89ac32 --- /dev/null +++ b/instrumentation/mongo/mongo-3.1/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/mongo/v3_1/MongoClientTest.java @@ -0,0 +1,26 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.mongo.v3_1; + +import com.mongodb.MongoClientOptions; +import io.opentelemetry.instrumentation.mongo.v3_1.AbstractMongo31ClientTest; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import org.junit.jupiter.api.extension.RegisterExtension; + +class MongoClientTest extends AbstractMongo31ClientTest { + + @RegisterExtension + static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); + + @Override + protected void configureMongoClientOptions(MongoClientOptions.Builder options) {} + + @Override + protected InstrumentationExtension testing() { + return testing; + } +} diff --git a/instrumentation/mongo/mongo-3.1/library/src/test/groovy/io/opentelemetry/instrumentation/mongo/v3_1/MongoClientTest.groovy b/instrumentation/mongo/mongo-3.1/library/src/test/groovy/io/opentelemetry/instrumentation/mongo/v3_1/MongoClientTest.groovy deleted file mode 100644 index 47799519ced4..000000000000 --- a/instrumentation/mongo/mongo-3.1/library/src/test/groovy/io/opentelemetry/instrumentation/mongo/v3_1/MongoClientTest.groovy +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.mongo.v3_1 - -import com.mongodb.MongoClientOptions -import io.opentelemetry.instrumentation.test.LibraryTestTrait - -class MongoClientTest extends AbstractMongo31ClientTest implements LibraryTestTrait { - @Override - void configureMongoClientOptions(MongoClientOptions.Builder options) { - options.addCommandListener(MongoTelemetry.create(openTelemetry).newCommandListener()) - } -} diff --git a/instrumentation/mongo/mongo-3.1/library/src/test/groovy/io/opentelemetry/instrumentation/mongo/v3_1/MongoDbAttributesGetterTest.groovy b/instrumentation/mongo/mongo-3.1/library/src/test/groovy/io/opentelemetry/instrumentation/mongo/v3_1/MongoDbAttributesGetterTest.groovy deleted file mode 100644 index 85a887b14c5a..000000000000 --- a/instrumentation/mongo/mongo-3.1/library/src/test/groovy/io/opentelemetry/instrumentation/mongo/v3_1/MongoDbAttributesGetterTest.groovy +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.mongo.v3_1 - -import org.bson.BsonArray -import org.bson.BsonDocument -import org.bson.BsonInt32 -import org.bson.BsonString -import spock.lang.Specification - -import static MongoTelemetryBuilder.DEFAULT_MAX_NORMALIZED_QUERY_LENGTH -import static java.util.Arrays.asList - -class MongoDbAttributesGetterTest extends Specification { - - def 'should sanitize statements to json'() { - setup: - def extractor = new MongoDbAttributesGetter(true, DEFAULT_MAX_NORMALIZED_QUERY_LENGTH) - - expect: - sanitizeStatementAcrossVersions(extractor, - new BsonDocument("cmd", new BsonInt32(1))) == - '{"cmd": "?"}' - - sanitizeStatementAcrossVersions(extractor, - new BsonDocument("cmd", new BsonInt32(1)) - .append("sub", new BsonDocument("a", new BsonInt32(1)))) == - '{"cmd": "?", "sub": {"a": "?"}}' - - sanitizeStatementAcrossVersions(extractor, - new BsonDocument("cmd", new BsonInt32(1)) - .append("sub", new BsonArray(asList(new BsonInt32(1))))) == - '{"cmd": "?", "sub": ["?"]}' - } - - def 'should only preserve string value if it is the value of the first top-level key'() { - setup: - def extractor = new MongoDbAttributesGetter(true, DEFAULT_MAX_NORMALIZED_QUERY_LENGTH) - - expect: - sanitizeStatementAcrossVersions(extractor, - new BsonDocument("cmd", new BsonString("c")) - .append("f", new BsonString("c")) - .append("sub", new BsonString("c"))) == - '{"cmd": "c", "f": "?", "sub": "?"}' - } - - def 'should truncate simple command'() { - setup: - def extractor = new MongoDbAttributesGetter(true, 20) - - def normalized = sanitizeStatementAcrossVersions(extractor, - new BsonDocument("cmd", new BsonString("c")) - .append("f1", new BsonString("c1")) - .append("f2", new BsonString("c2"))) - expect: - // this can vary because of different whitespace for different mongo versions - normalized == '{"cmd": "c", "f1": "' || normalized == '{"cmd": "c", "f1" ' - } - - def 'should truncate array'() { - setup: - def extractor = new MongoDbAttributesGetter(true, 27) - - def normalized = sanitizeStatementAcrossVersions(extractor, - new BsonDocument("cmd", new BsonString("c")) - .append("f1", new BsonArray(asList(new BsonString("c1"), new BsonString("c2")))) - .append("f2", new BsonString("c3"))) - expect: - // this can vary because of different whitespace for different mongo versions - normalized == '{"cmd": "c", "f1": ["?", "?' || normalized == '{"cmd": "c", "f1": ["?",' - } - - def sanitizeStatementAcrossVersions(MongoDbAttributesGetter extractor, BsonDocument query) { - return sanitizeAcrossVersions(extractor.sanitizeStatement(query)) - } - - def sanitizeAcrossVersions(String json) { - json = json.replaceAll('\\{ ', '{') - json = json.replaceAll(' }', '}') - json = json.replaceAll(' :', ':') - return json - } - -} diff --git a/instrumentation/mongo/mongo-3.1/library/src/test/groovy/io/opentelemetry/instrumentation/mongo/v3_1/MongoSpanNameExtractorTest.groovy b/instrumentation/mongo/mongo-3.1/library/src/test/groovy/io/opentelemetry/instrumentation/mongo/v3_1/MongoSpanNameExtractorTest.groovy deleted file mode 100644 index 368e41337e6e..000000000000 --- a/instrumentation/mongo/mongo-3.1/library/src/test/groovy/io/opentelemetry/instrumentation/mongo/v3_1/MongoSpanNameExtractorTest.groovy +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.mongo.v3_1 - -import com.mongodb.event.CommandStartedEvent -import org.bson.BsonDocument -import org.bson.BsonInt32 -import spock.lang.Specification - -import static MongoTelemetryBuilder.DEFAULT_MAX_NORMALIZED_QUERY_LENGTH - -class MongoSpanNameExtractorTest extends Specification { - - def 'test span name with no dbName'() { - setup: - def nameExtractor = new MongoSpanNameExtractor(new MongoDbAttributesGetter(true, DEFAULT_MAX_NORMALIZED_QUERY_LENGTH), new MongoAttributesExtractor()) - def event = new CommandStartedEvent( - 0, null, null, command, new BsonDocument(command, new BsonInt32(1))) - - when: - def spanName = nameExtractor.extract(event) - - then: - spanName == command - - where: - command = "listDatabases" - } - -} diff --git a/instrumentation/mongo/mongo-3.1/library/src/test/java/io/opentelemetry/instrumentation/mongo/v3_1/MongoClientTest.java b/instrumentation/mongo/mongo-3.1/library/src/test/java/io/opentelemetry/instrumentation/mongo/v3_1/MongoClientTest.java new file mode 100644 index 000000000000..7124b410a2f6 --- /dev/null +++ b/instrumentation/mongo/mongo-3.1/library/src/test/java/io/opentelemetry/instrumentation/mongo/v3_1/MongoClientTest.java @@ -0,0 +1,28 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.mongo.v3_1; + +import com.mongodb.MongoClientOptions; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import org.junit.jupiter.api.extension.RegisterExtension; + +class MongoClientTest extends AbstractMongo31ClientTest { + + @RegisterExtension + private static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @Override + protected void configureMongoClientOptions(MongoClientOptions.Builder options) { + options.addCommandListener( + MongoTelemetry.create(testing().getOpenTelemetry()).newCommandListener()); + } + + @Override + protected InstrumentationExtension testing() { + return testing; + } +} diff --git a/instrumentation/mongo/mongo-3.1/library/src/test/java/io/opentelemetry/instrumentation/mongo/v3_1/MongoDbAttributesGetterTest.java b/instrumentation/mongo/mongo-3.1/library/src/test/java/io/opentelemetry/instrumentation/mongo/v3_1/MongoDbAttributesGetterTest.java new file mode 100644 index 000000000000..fd3ecd71819d --- /dev/null +++ b/instrumentation/mongo/mongo-3.1/library/src/test/java/io/opentelemetry/instrumentation/mongo/v3_1/MongoDbAttributesGetterTest.java @@ -0,0 +1,106 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.mongo.v3_1; + +import static io.opentelemetry.instrumentation.mongo.v3_1.MongoTelemetryBuilder.DEFAULT_MAX_NORMALIZED_QUERY_LENGTH; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; + +import org.bson.BsonArray; +import org.bson.BsonDocument; +import org.bson.BsonInt32; +import org.bson.BsonString; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class MongoDbAttributesGetterTest { + + @Test + @DisplayName("should sanitize statements to json") + void shouldSanitizeStatementsToJson() { + MongoDbAttributesGetter extractor = + new MongoDbAttributesGetter(true, DEFAULT_MAX_NORMALIZED_QUERY_LENGTH); + + assertThat( + sanitizeStatementAcrossVersions(extractor, new BsonDocument("cmd", new BsonInt32(1)))) + .isEqualTo("{\"cmd\": \"?\"}"); + + assertThat( + sanitizeStatementAcrossVersions( + extractor, + new BsonDocument("cmd", new BsonInt32(1)) + .append("sub", new BsonDocument("a", new BsonInt32(1))))) + .isEqualTo("{\"cmd\": \"?\", \"sub\": {\"a\": \"?\"}}"); + + assertThat( + sanitizeStatementAcrossVersions( + extractor, + new BsonDocument("cmd", new BsonInt32(1)) + .append("sub", new BsonArray(singletonList(new BsonInt32(1)))))) + .isEqualTo("{\"cmd\": \"?\", \"sub\": [\"?\"]}"); + } + + @Test + @DisplayName("should only preserve string value if it is the value of the first top-level key") + void shouldOnlyPreserveStringValueIfItIsTheValueOfTheFirstTopLevelKey() { + MongoDbAttributesGetter extractor = + new MongoDbAttributesGetter(true, DEFAULT_MAX_NORMALIZED_QUERY_LENGTH); + + assertThat( + sanitizeStatementAcrossVersions( + extractor, + new BsonDocument("cmd", new BsonString("c")) + .append("f", new BsonString("c")) + .append("sub", new BsonString("c")))) + .isEqualTo("{\"cmd\": \"c\", \"f\": \"?\", \"sub\": \"?\"}"); + } + + @Test + @DisplayName("should truncate simple command") + void shouldTruncateSimpleCommand() { + MongoDbAttributesGetter extractor = new MongoDbAttributesGetter(true, 20); + + String normalized = + sanitizeStatementAcrossVersions( + extractor, + new BsonDocument("cmd", new BsonString("c")) + .append("f1", new BsonString("c1")) + .append("f2", new BsonString("c2"))); + + // This can vary because of different whitespace for different MongoDB versions + assertThat(normalized).isIn("{\"cmd\": \"c\", \"f1\": \"", "{\"cmd\": \"c\", \"f1\" "); + } + + @Test + @DisplayName("should truncate array") + void shouldTruncateArray() { + MongoDbAttributesGetter extractor = new MongoDbAttributesGetter(true, 27); + + String normalized = + sanitizeStatementAcrossVersions( + extractor, + new BsonDocument("cmd", new BsonString("c")) + .append("f1", new BsonArray(asList(new BsonString("c1"), new BsonString("c2")))) + .append("f2", new BsonString("c3"))); + + // This can vary because of different whitespace for different MongoDB versions + assertThat(normalized) + .isIn("{\"cmd\": \"c\", \"f1\": [\"?\", \"?", "{\"cmd\": \"c\", \"f1\": [\"?\","); + } + + static String sanitizeStatementAcrossVersions( + MongoDbAttributesGetter extractor, BsonDocument query) { + return sanitizeAcrossVersions(extractor.sanitizeStatement(query)); + } + + static String sanitizeAcrossVersions(String json) { + json = json.replaceAll("\\{ ", "{"); + json = json.replaceAll(" }", "}"); + json = json.replaceAll(" :", ":"); + return json; + } +} diff --git a/instrumentation/mongo/mongo-3.1/library/src/test/java/io/opentelemetry/instrumentation/mongo/v3_1/MongoSpanNameExtractorTest.java b/instrumentation/mongo/mongo-3.1/library/src/test/java/io/opentelemetry/instrumentation/mongo/v3_1/MongoSpanNameExtractorTest.java new file mode 100644 index 000000000000..fce0cbb1fdb9 --- /dev/null +++ b/instrumentation/mongo/mongo-3.1/library/src/test/java/io/opentelemetry/instrumentation/mongo/v3_1/MongoSpanNameExtractorTest.java @@ -0,0 +1,36 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.mongo.v3_1; + +import static io.opentelemetry.instrumentation.mongo.v3_1.MongoTelemetryBuilder.DEFAULT_MAX_NORMALIZED_QUERY_LENGTH; +import static org.assertj.core.api.Assertions.assertThat; + +import com.mongodb.event.CommandStartedEvent; +import org.bson.BsonDocument; +import org.bson.BsonInt32; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class MongoSpanNameExtractorTest { + + @Test + @DisplayName("test span name with no dbName") + void testSpanNameWithNoDbName() { + MongoSpanNameExtractor nameExtractor = + new MongoSpanNameExtractor( + new MongoDbAttributesGetter(true, DEFAULT_MAX_NORMALIZED_QUERY_LENGTH), + new MongoAttributesExtractor()); + + String command = "listDatabases"; + CommandStartedEvent event = + new CommandStartedEvent( + 0, null, null, command, new BsonDocument(command, new BsonInt32(1))); + + String spanName = nameExtractor.extract(event); + + assertThat(spanName).isEqualTo(command); + } +} diff --git a/instrumentation/mongo/mongo-3.1/testing/build.gradle.kts b/instrumentation/mongo/mongo-3.1/testing/build.gradle.kts index 458c5f0b8ae8..949d4ed4ef66 100644 --- a/instrumentation/mongo/mongo-3.1/testing/build.gradle.kts +++ b/instrumentation/mongo/mongo-3.1/testing/build.gradle.kts @@ -7,7 +7,5 @@ dependencies { compileOnly("org.mongodb:mongo-java-driver:3.1.0") - implementation("org.apache.groovy:groovy") implementation("io.opentelemetry:opentelemetry-api") - implementation("org.spockframework:spock-core") } diff --git a/instrumentation/mongo/mongo-3.1/testing/src/main/groovy/io/opentelemetry/instrumentation/mongo/v3_1/AbstractMongo31ClientTest.groovy b/instrumentation/mongo/mongo-3.1/testing/src/main/groovy/io/opentelemetry/instrumentation/mongo/v3_1/AbstractMongo31ClientTest.groovy deleted file mode 100644 index 63d74fc90341..000000000000 --- a/instrumentation/mongo/mongo-3.1/testing/src/main/groovy/io/opentelemetry/instrumentation/mongo/v3_1/AbstractMongo31ClientTest.groovy +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.mongo.v3_1 - - -import com.mongodb.MongoClient -import com.mongodb.MongoClientOptions -import com.mongodb.MongoTimeoutException -import com.mongodb.ServerAddress -import com.mongodb.client.MongoCollection -import com.mongodb.client.MongoDatabase -import io.opentelemetry.instrumentation.mongo.testing.AbstractMongoClientTest -import io.opentelemetry.instrumentation.test.utils.PortUtils -import org.bson.BsonDocument -import org.bson.BsonString -import org.bson.Document -import spock.lang.Shared - -abstract class AbstractMongo31ClientTest extends AbstractMongoClientTest> { - - abstract void configureMongoClientOptions(MongoClientOptions.Builder options); - - @Shared - MongoClient client - - def setupSpec() throws Exception { - def options = MongoClientOptions.builder().description("some-description") - configureMongoClientOptions(options) - client = new MongoClient(new ServerAddress(host, port), options.build()) - } - - def cleanupSpec() throws Exception { - client?.close() - client = null - } - - @Override - void createCollection(String dbName, String collectionName) { - MongoDatabase db = client.getDatabase(dbName) - db.createCollection(collectionName) - } - - @Override - void createCollectionNoDescription(String dbName, String collectionName) { - def options = MongoClientOptions.builder() - configureMongoClientOptions(options) - MongoDatabase db = new MongoClient(new ServerAddress(host, port), options.build()).getDatabase(dbName) - db.createCollection(collectionName) - } - - @Override - void createCollectionWithAlreadyBuiltClientOptions(String dbName, String collectionName) { - def clientOptions = client.mongoClientOptions - def newClientOptions = MongoClientOptions.builder(clientOptions).build() - MongoDatabase db = new MongoClient(new ServerAddress(host, port), newClientOptions).getDatabase(dbName) - db.createCollection(collectionName) - } - - @Override - void createCollectionCallingBuildTwice(String dbName, String collectionName) { - def options = MongoClientOptions.builder().description("some-description") - configureMongoClientOptions(options) - options.build() - MongoDatabase db = new MongoClient(new ServerAddress(host, port), options.build()).getDatabase(dbName) - db.createCollection(collectionName) - } - - @Override - int getCollection(String dbName, String collectionName) { - MongoDatabase db = client.getDatabase(dbName) - return db.getCollection(collectionName).count() - } - - @Override - MongoCollection setupInsert(String dbName, String collectionName) { - MongoCollection collection = runWithSpan("setup") { - MongoDatabase db = client.getDatabase(dbName) - db.createCollection(collectionName) - return db.getCollection(collectionName) - } - ignoreTracesAndClear(1) - return collection - } - - @Override - int insert(MongoCollection collection) { - collection.insertOne(new Document("password", "SECRET")) - return collection.count() - } - - @Override - MongoCollection setupUpdate(String dbName, String collectionName) { - MongoCollection collection = runWithSpan("setup") { - MongoDatabase db = client.getDatabase(dbName) - db.createCollection(collectionName) - def coll = db.getCollection(collectionName) - coll.insertOne(new Document("password", "OLDPW")) - return coll - } - ignoreTracesAndClear(1) - return collection - } - - @Override - int update(MongoCollection collection) { - def result = collection.updateOne( - new BsonDocument("password", new BsonString("OLDPW")), - new BsonDocument('$set', new BsonDocument("password", new BsonString("NEWPW")))) - collection.count() - return result.modifiedCount - } - - @Override - MongoCollection setupDelete(String dbName, String collectionName) { - MongoCollection collection = runWithSpan("setup") { - MongoDatabase db = client.getDatabase(dbName) - db.createCollection(collectionName) - def coll = db.getCollection(collectionName) - coll.insertOne(new Document("password", "SECRET")) - return coll - } - ignoreTracesAndClear(1) - return collection - } - - @Override - int delete(MongoCollection collection) { - def result = collection.deleteOne(new BsonDocument("password", new BsonString("SECRET"))) - collection.count() - return result.deletedCount - } - - @Override - MongoCollection setupGetMore(String dbName, String collectionName) { - MongoCollection collection = runWithSpan("setup") { - MongoDatabase db = client.getDatabase(dbName) - def coll = db.getCollection(collectionName) - coll.insertMany([new Document("_id", 0), new Document("_id", 1), new Document("_id", 2)]) - return coll - } - ignoreTracesAndClear(1) - return collection - } - - @Override - void getMore(MongoCollection collection) { - collection.find().filter(new Document("_id", new Document('$gte', 0))) - .batchSize(2).into(new ArrayList()) - } - - @Override - void error(String dbName, String collectionName) { - MongoCollection collection = runWithSpan("setup") { - MongoDatabase db = client.getDatabase(dbName) - db.createCollection(collectionName) - return db.getCollection(collectionName) - } - ignoreTracesAndClear(1) - collection.updateOne(new BsonDocument(), new BsonDocument()) - } - - def "test client failure"() { - setup: - def options = MongoClientOptions.builder().serverSelectionTimeout(10).build() - def client = new MongoClient(new ServerAddress(host, PortUtils.UNUSABLE_PORT), [], options) - - when: - MongoDatabase db = client.getDatabase(dbName) - db.createCollection(collectionName) - - then: - thrown(MongoTimeoutException) - // Unfortunately not caught by our instrumentation. - assertTraces(0) {} - - where: - dbName = "test_db" - collectionName = createCollectionName() - } -} diff --git a/instrumentation/mongo/mongo-3.1/testing/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/AbstractMongo31ClientTest.java b/instrumentation/mongo/mongo-3.1/testing/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/AbstractMongo31ClientTest.java new file mode 100644 index 000000000000..21944f73425b --- /dev/null +++ b/instrumentation/mongo/mongo-3.1/testing/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/AbstractMongo31ClientTest.java @@ -0,0 +1,223 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.mongo.v3_1; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +import com.mongodb.MongoClient; +import com.mongodb.MongoClientOptions; +import com.mongodb.MongoTimeoutException; +import com.mongodb.ServerAddress; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.result.DeleteResult; +import com.mongodb.client.result.UpdateResult; +import io.opentelemetry.instrumentation.mongo.testing.AbstractMongoClientTest; +import io.opentelemetry.instrumentation.test.utils.PortUtils; +import java.util.ArrayList; +import org.bson.BsonDocument; +import org.bson.BsonString; +import org.bson.Document; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public abstract class AbstractMongo31ClientTest + extends AbstractMongoClientTest> { + + protected abstract void configureMongoClientOptions(MongoClientOptions.Builder options); + + private MongoClient client; + + @BeforeAll + void setup() { + MongoClientOptions.Builder options = + MongoClientOptions.builder().description("some-description"); + configureMongoClientOptions(options); + client = new MongoClient(new ServerAddress(host, port), options.build()); + } + + @AfterAll + void cleanup() { + if (client != null) { + client.close(); + } + } + + @Override + protected void createCollection(String dbName, String collectionName) { + MongoDatabase db = client.getDatabase(dbName); + db.createCollection(collectionName); + } + + @Override + protected void createCollectionNoDescription(String dbName, String collectionName) { + MongoClientOptions.Builder options = MongoClientOptions.builder(); + configureMongoClientOptions(options); + MongoDatabase db = + new MongoClient(new ServerAddress(host, port), options.build()).getDatabase(dbName); + db.createCollection(collectionName); + } + + @Override + protected void createCollectionWithAlreadyBuiltClientOptions( + String dbName, String collectionName) { + MongoClientOptions clientOptions = client.getMongoClientOptions(); + MongoClientOptions newClientOptions = MongoClientOptions.builder(clientOptions).build(); + MongoDatabase db = + new MongoClient(new ServerAddress(host, port), newClientOptions).getDatabase(dbName); + db.createCollection(collectionName); + } + + @Override + protected void createCollectionCallingBuildTwice(String dbName, String collectionName) { + MongoClientOptions.Builder options = + MongoClientOptions.builder().description("some-description"); + configureMongoClientOptions(options); + options.build(); + MongoDatabase db = + new MongoClient(new ServerAddress(host, port), options.build()).getDatabase(dbName); + db.createCollection(collectionName); + } + + @Override + protected long getCollection(String dbName, String collectionName) { + MongoDatabase db = client.getDatabase(dbName); + return db.getCollection(collectionName).count(); + } + + @Override + protected MongoCollection setupInsert(String dbName, String collectionName) { + MongoCollection collection = + testing() + .runWithSpan( + "setup", + () -> { + MongoDatabase db = client.getDatabase(dbName); + db.createCollection(collectionName); + return db.getCollection(collectionName); + }); + ignoreTracesAndClear(1); + return collection; + } + + @Override + protected long insert(MongoCollection collection) { + collection.insertOne(new Document("password", "SECRET")); + return collection.count(); + } + + @Override + protected MongoCollection setupUpdate(String dbName, String collectionName) { + MongoCollection collection = + testing() + .runWithSpan( + "setup", + () -> { + MongoDatabase db = client.getDatabase(dbName); + db.createCollection(collectionName); + MongoCollection coll = db.getCollection(collectionName); + coll.insertOne(new Document("password", "OLDPW")); + return coll; + }); + ignoreTracesAndClear(1); + return collection; + } + + @Override + protected long update(MongoCollection collection) { + UpdateResult result = + collection.updateOne( + new BsonDocument("password", new BsonString("OLDPW")), + new BsonDocument("$set", new BsonDocument("password", new BsonString("NEWPW")))); + collection.count(); + return result.getModifiedCount(); + } + + @Override + protected MongoCollection setupDelete(String dbName, String collectionName) { + MongoCollection collection = + testing() + .runWithSpan( + "setup", + () -> { + MongoDatabase db = client.getDatabase(dbName); + db.createCollection(collectionName); + MongoCollection coll = db.getCollection(collectionName); + coll.insertOne(new Document("password", "SECRET")); + return coll; + }); + ignoreTracesAndClear(1); + return collection; + } + + @Override + protected long delete(MongoCollection collection) { + DeleteResult result = + collection.deleteOne(new BsonDocument("password", new BsonString("SECRET"))); + collection.count(); + return result.getDeletedCount(); + } + + @Override + protected MongoCollection setupGetMore(String dbName, String collectionName) { + MongoCollection collection = + testing() + .runWithSpan( + "setup", + () -> { + MongoDatabase db = client.getDatabase(dbName); + MongoCollection coll = db.getCollection(collectionName); + coll.insertMany( + asList( + new Document("_id", 0), new Document("_id", 1), new Document("_id", 2))); + return coll; + }); + ignoreTracesAndClear(1); + return collection; + } + + @Override + protected void getMore(MongoCollection collection) { + collection + .find() + .filter(new Document("_id", new Document("$gte", 0))) + .batchSize(2) + .into(new ArrayList<>()); + } + + @Override + protected void error(String dbName, String collectionName) { + MongoCollection collection = + testing() + .runWithSpan( + "setup", + () -> { + MongoDatabase db = client.getDatabase(dbName); + db.createCollection(collectionName); + return db.getCollection(collectionName); + }); + ignoreTracesAndClear(1); + collection.updateOne(new BsonDocument(), new BsonDocument()); + } + + @Test + void testClientFailure() { + MongoClientOptions options = MongoClientOptions.builder().serverSelectionTimeout(10).build(); + MongoClient client = new MongoClient(new ServerAddress(host, PortUtils.UNUSABLE_PORT), options); + + assertThatExceptionOfType(MongoTimeoutException.class) + .isThrownBy( + () -> { + MongoDatabase db = client.getDatabase("test_db"); + db.createCollection(createCollectionName()); + }); + // Unfortunately not caught by our instrumentation. + assertThat(testing().spans()).isEmpty(); + } +} diff --git a/instrumentation/mongo/mongo-3.7/javaagent/src/test/groovy/MongoClientTest.groovy b/instrumentation/mongo/mongo-3.7/javaagent/src/test/groovy/MongoClientTest.groovy deleted file mode 100644 index 188262e4faa2..000000000000 --- a/instrumentation/mongo/mongo-3.7/javaagent/src/test/groovy/MongoClientTest.groovy +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import com.mongodb.MongoClientSettings -import com.mongodb.MongoTimeoutException -import com.mongodb.ServerAddress -import com.mongodb.client.MongoClient -import com.mongodb.client.MongoClients -import com.mongodb.client.MongoCollection -import com.mongodb.client.MongoDatabase -import io.opentelemetry.instrumentation.mongo.testing.AbstractMongoClientTest -import io.opentelemetry.instrumentation.test.AgentTestTrait -import org.bson.BsonDocument -import org.bson.BsonString -import org.bson.Document -import spock.lang.Shared - -import static io.opentelemetry.instrumentation.test.utils.PortUtils.UNUSABLE_PORT - -class MongoClientTest extends AbstractMongoClientTest> implements AgentTestTrait { - - @Shared - MongoClient client - - def setupSpec() throws Exception { - client = MongoClients.create(MongoClientSettings.builder() - .applyToClusterSettings({ builder -> - builder.hosts(Arrays.asList( - new ServerAddress(host, port))) - .description("some-description") - }) - .build()) - } - - def cleanupSpec() throws Exception { - client?.close() - client = null - } - - @Override - void createCollection(String dbName, String collectionName) { - MongoDatabase db = client.getDatabase(dbName) - db.createCollection(collectionName) - } - - @Override - void createCollectionNoDescription(String dbName, String collectionName) { - MongoDatabase db = MongoClients.create("mongodb://$host:${port}").getDatabase(dbName) - db.createCollection(collectionName) - } - - @Override - void createCollectionWithAlreadyBuiltClientOptions(String dbName, String collectionName) { - def clientSettings = MongoClientSettings.builder() - .applyToClusterSettings({ builder -> - builder.hosts(Arrays.asList( - new ServerAddress(host, port))) - .description("some-description") - }) - .build() - def newClientSettings = MongoClientSettings.builder(clientSettings).build() - MongoDatabase db = MongoClients.create(newClientSettings).getDatabase(dbName) - db.createCollection(collectionName) - } - - @Override - void createCollectionCallingBuildTwice(String dbName, String collectionName) { - def clientSettings = MongoClientSettings.builder() - .applyToClusterSettings({ builder -> - builder.hosts(Arrays.asList( - new ServerAddress(host, port))) - .description("some-description") - }) - clientSettings.build() - MongoDatabase db = MongoClients.create(clientSettings.build()).getDatabase(dbName) - db.createCollection(collectionName) - } - - @Override - int getCollection(String dbName, String collectionName) { - MongoDatabase db = client.getDatabase(dbName) - return db.getCollection(collectionName).count() - } - - @Override - MongoCollection setupInsert(String dbName, String collectionName) { - MongoCollection collection = runWithSpan("setup") { - MongoDatabase db = client.getDatabase(dbName) - db.createCollection(collectionName) - return db.getCollection(collectionName) - } - ignoreTracesAndClear(1) - return collection - } - - @Override - int insert(MongoCollection collection) { - collection.insertOne(new Document("password", "SECRET")) - return collection.count() - } - - @Override - MongoCollection setupUpdate(String dbName, String collectionName) { - MongoCollection collection = runWithSpan("setup") { - MongoDatabase db = client.getDatabase(dbName) - db.createCollection(collectionName) - def coll = db.getCollection(collectionName) - coll.insertOne(new Document("password", "OLDPW")) - return coll - } - ignoreTracesAndClear(1) - return collection - } - - @Override - int update(MongoCollection collection) { - def result = collection.updateOne( - new BsonDocument("password", new BsonString("OLDPW")), - new BsonDocument('$set', new BsonDocument("password", new BsonString("NEWPW")))) - collection.count() - return result.modifiedCount - } - - @Override - MongoCollection setupDelete(String dbName, String collectionName) { - MongoCollection collection = runWithSpan("setup") { - MongoDatabase db = client.getDatabase(dbName) - db.createCollection(collectionName) - def coll = db.getCollection(collectionName) - coll.insertOne(new Document("password", "SECRET")) - return coll - } - ignoreTracesAndClear(1) - return collection - } - - @Override - int delete(MongoCollection collection) { - def result = collection.deleteOne(new BsonDocument("password", new BsonString("SECRET"))) - collection.count() - return result.deletedCount - } - - @Override - MongoCollection setupGetMore(String dbName, String collectionName) { - MongoCollection collection = runWithSpan("setup") { - MongoDatabase db = client.getDatabase(dbName) - def coll = db.getCollection(collectionName) - coll.insertMany([new Document("_id", 0), new Document("_id", 1), new Document("_id", 2)]) - return coll - } - ignoreTracesAndClear(1) - return collection - } - - @Override - void getMore(MongoCollection collection) { - collection.find().filter(new Document("_id", new Document('$gte', 0))) - .batchSize(2).into(new ArrayList()) - } - - @Override - void error(String dbName, String collectionName) { - MongoCollection collection = runWithSpan("setup") { - MongoDatabase db = client.getDatabase(dbName) - db.createCollection(collectionName) - return db.getCollection(collectionName) - } - ignoreTracesAndClear(1) - collection.updateOne(new BsonDocument(), new BsonDocument()) - } - - def "test client failure"() { - setup: - def client = MongoClients.create("mongodb://" + host + ":" + UNUSABLE_PORT + "/?connectTimeoutMS=10") - - when: - MongoDatabase db = client.getDatabase(dbName) - db.createCollection(collectionName) - - then: - thrown(MongoTimeoutException) - // Unfortunately not caught by our instrumentation. - assertTraces(0) {} - - where: - dbName = "test_db" - collectionName = createCollectionName() - } -} diff --git a/instrumentation/mongo/mongo-3.7/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/mongo/v3_7/MongoClientTest.java b/instrumentation/mongo/mongo-3.7/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/mongo/v3_7/MongoClientTest.java new file mode 100644 index 000000000000..a8614bce27ef --- /dev/null +++ b/instrumentation/mongo/mongo-3.7/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/mongo/v3_7/MongoClientTest.java @@ -0,0 +1,244 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.mongo.v3_7; + +import static io.opentelemetry.instrumentation.test.utils.PortUtils.UNUSABLE_PORT; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.mongodb.MongoClientSettings; +import com.mongodb.MongoTimeoutException; +import com.mongodb.ServerAddress; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.result.DeleteResult; +import com.mongodb.client.result.UpdateResult; +import io.opentelemetry.instrumentation.mongo.testing.AbstractMongoClientTest; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import java.util.ArrayList; +import org.bson.BsonDocument; +import org.bson.BsonString; +import org.bson.Document; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +class MongoClientTest extends AbstractMongoClientTest> { + + @RegisterExtension + static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); + + private MongoClient client; + + @BeforeAll + void setup() { + client = + MongoClients.create( + MongoClientSettings.builder() + .applyToClusterSettings( + builder -> + builder + .hosts(singletonList(new ServerAddress(host, port))) + .description("some-description")) + .build()); + } + + @AfterAll + void cleanup() { + if (client != null) { + client.close(); + } + } + + @Override + protected InstrumentationExtension testing() { + return testing; + } + + @Override + protected void createCollection(String dbName, String collectionName) { + MongoDatabase db = client.getDatabase(dbName); + db.createCollection(collectionName); + } + + @Override + protected void createCollectionNoDescription(String dbName, String collectionName) { + MongoDatabase db = MongoClients.create("mongodb://" + host + ":" + port).getDatabase(dbName); + db.createCollection(collectionName); + } + + @Override + protected void createCollectionWithAlreadyBuiltClientOptions( + String dbName, String collectionName) { + MongoClientSettings clientSettings = + MongoClientSettings.builder() + .applyToClusterSettings( + builder -> + builder + .hosts(singletonList(new ServerAddress(host, port))) + .description("some-description")) + .build(); + MongoClientSettings newClientSettings = MongoClientSettings.builder(clientSettings).build(); + MongoDatabase db = MongoClients.create(newClientSettings).getDatabase(dbName); + db.createCollection(collectionName); + } + + @Override + protected void createCollectionCallingBuildTwice(String dbName, String collectionName) { + MongoClientSettings.Builder clientSettings = + MongoClientSettings.builder() + .applyToClusterSettings( + builder -> + builder + .hosts(singletonList(new ServerAddress(host, port))) + .description("some-description")); + clientSettings.build(); + MongoDatabase db = MongoClients.create(clientSettings.build()).getDatabase(dbName); + db.createCollection(collectionName); + } + + @Override + protected long getCollection(String dbName, String collectionName) { + MongoDatabase db = client.getDatabase(dbName); + return db.getCollection(collectionName).estimatedDocumentCount(); + } + + @Override + protected MongoCollection setupInsert(String dbName, String collectionName) { + MongoCollection collection = + testing() + .runWithSpan( + "setup", + () -> { + MongoDatabase db = client.getDatabase(dbName); + db.createCollection(collectionName); + return db.getCollection(collectionName); + }); + ignoreTracesAndClear(1); + return collection; + } + + @Override + protected long insert(MongoCollection collection) { + collection.insertOne(new Document("password", "SECRET")); + return collection.estimatedDocumentCount(); + } + + @Override + protected MongoCollection setupUpdate(String dbName, String collectionName) { + MongoCollection collection = + testing() + .runWithSpan( + "setup", + () -> { + MongoDatabase db = client.getDatabase(dbName); + db.createCollection(collectionName); + MongoCollection coll = db.getCollection(collectionName); + coll.insertOne(new Document("password", "OLDPW")); + return coll; + }); + ignoreTracesAndClear(1); + return collection; + } + + @Override + protected long update(MongoCollection collection) { + UpdateResult result = + collection.updateOne( + new BsonDocument("password", new BsonString("OLDPW")), + new BsonDocument("$set", new BsonDocument("password", new BsonString("NEWPW")))); + collection.estimatedDocumentCount(); + return result.getModifiedCount(); + } + + @Override + protected MongoCollection setupDelete(String dbName, String collectionName) { + MongoCollection collection = + testing() + .runWithSpan( + "setup", + () -> { + MongoDatabase db = client.getDatabase(dbName); + db.createCollection(collectionName); + MongoCollection coll = db.getCollection(collectionName); + coll.insertOne(new Document("password", "SECRET")); + return coll; + }); + ignoreTracesAndClear(1); + return collection; + } + + @Override + protected long delete(MongoCollection collection) { + DeleteResult result = + collection.deleteOne(new BsonDocument("password", new BsonString("SECRET"))); + collection.estimatedDocumentCount(); + return result.getDeletedCount(); + } + + @Override + protected MongoCollection setupGetMore(String dbName, String collectionName) { + MongoCollection collection = + testing() + .runWithSpan( + "setup", + () -> { + MongoDatabase db = client.getDatabase(dbName); + MongoCollection coll = db.getCollection(collectionName); + coll.insertMany( + asList( + new Document("_id", 0), new Document("_id", 1), new Document("_id", 2))); + return coll; + }); + ignoreTracesAndClear(1); + return collection; + } + + @Override + protected void getMore(MongoCollection collection) { + collection + .find() + .filter(new Document("_id", new Document("$gte", 0))) + .batchSize(2) + .into(new ArrayList<>()); + } + + @Override + protected void error(String dbName, String collectionName) { + MongoCollection collection = + testing() + .runWithSpan( + "setup", + () -> { + MongoDatabase db = client.getDatabase(dbName); + db.createCollection(collectionName); + return db.getCollection(collectionName); + }); + ignoreTracesAndClear(1); + collection.updateOne(new BsonDocument(), new BsonDocument()); + } + + @Test + void testClientFailure() { + MongoClient client = + MongoClients.create("mongodb://" + host + ":" + UNUSABLE_PORT + "/?connectTimeoutMS=10"); + + assertThatThrownBy( + () -> { + MongoDatabase db = client.getDatabase("test_db"); + db.createCollection(createCollectionName()); + }) + .isInstanceOf(MongoTimeoutException.class); + // Unfortunately not caught by our instrumentation. + assertThat(testing().spans()).isEmpty(); + } +} diff --git a/instrumentation/mongo/mongo-4.0/javaagent/src/test/groovy/Mongo4ReactiveClientTest.groovy b/instrumentation/mongo/mongo-4.0/javaagent/src/test/groovy/Mongo4ReactiveClientTest.groovy deleted file mode 100644 index c9afc1d5ca03..000000000000 --- a/instrumentation/mongo/mongo-4.0/javaagent/src/test/groovy/Mongo4ReactiveClientTest.groovy +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import com.mongodb.MongoClientSettings -import com.mongodb.ServerAddress -import com.mongodb.client.result.DeleteResult -import com.mongodb.client.result.UpdateResult -import com.mongodb.reactivestreams.client.MongoClient -import com.mongodb.reactivestreams.client.MongoClients -import com.mongodb.reactivestreams.client.MongoCollection -import com.mongodb.reactivestreams.client.MongoDatabase -import io.opentelemetry.instrumentation.mongo.testing.AbstractMongoClientTest -import io.opentelemetry.instrumentation.test.AgentTestTrait -import org.bson.BsonDocument -import org.bson.BsonString -import org.bson.Document -import org.opentest4j.TestAbortedException -import org.reactivestreams.Subscriber -import org.reactivestreams.Subscription -import spock.lang.Shared - -import java.util.concurrent.CompletableFuture -import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit - -class Mongo4ReactiveClientTest extends AbstractMongoClientTest> implements AgentTestTrait { - - @Shared - MongoClient client - @Shared - List cleanup = [] - - def setupSpec() throws Exception { - client = MongoClients.create("mongodb://$host:$port") - } - - def cleanupSpec() throws Exception { - client?.close() - client = null - cleanup.forEach { - it.close() - } - } - - @Override - void createCollection(String dbName, String collectionName) { - MongoDatabase db = client.getDatabase(dbName) - def latch = new CountDownLatch(1) - db.createCollection(collectionName).subscribe(toSubscriber { latch.countDown() }) - latch.await(30, TimeUnit.SECONDS) - } - - @Override - void createCollectionNoDescription(String dbName, String collectionName) { - def tmpClient = MongoClients.create("mongodb://$host:${port}") - cleanup.add(tmpClient) - MongoDatabase db = tmpClient.getDatabase(dbName) - def latch = new CountDownLatch(1) - db.createCollection(collectionName).subscribe(toSubscriber { latch.countDown() }) - latch.await(30, TimeUnit.SECONDS) - } - - @Override - void createCollectionWithAlreadyBuiltClientOptions(String dbName, String collectionName) { - throw new TestAbortedException("not tested on 4.0") - } - - @Override - void createCollectionCallingBuildTwice(String dbName, String collectionName) { - def settings = MongoClientSettings.builder() - .applyToClusterSettings({ builder -> - builder.hosts(Arrays.asList( - new ServerAddress(host, port))) - }) - settings.build() - def tmpClient = MongoClients.create(settings.build()) - cleanup.add(tmpClient) - MongoDatabase db = tmpClient.getDatabase(dbName) - def latch = new CountDownLatch(1) - db.createCollection(collectionName).subscribe(toSubscriber { latch.countDown() }) - latch.await(30, TimeUnit.SECONDS) - } - - @Override - int getCollection(String dbName, String collectionName) { - MongoDatabase db = client.getDatabase(dbName) - def count = new CompletableFuture() - db.getCollection(collectionName).estimatedDocumentCount().subscribe(toSubscriber { count.complete(it) }) - return count.get(30, TimeUnit.SECONDS) - } - - @Override - MongoCollection setupInsert(String dbName, String collectionName) { - MongoCollection collection = runWithSpan("setup") { - MongoDatabase db = client.getDatabase(dbName) - def latch1 = new CountDownLatch(1) - db.createCollection(collectionName).subscribe(toSubscriber { latch1.countDown() }) - latch1.await(30, TimeUnit.SECONDS) - return db.getCollection(collectionName) - } - ignoreTracesAndClear(1) - return collection - } - - @Override - int insert(MongoCollection collection) { - def count = new CompletableFuture() - collection.insertOne(new Document("password", "SECRET")).subscribe(toSubscriber { - collection.estimatedDocumentCount().subscribe(toSubscriber { count.complete(it) }) - }) - return count.get(30, TimeUnit.SECONDS) - } - - @Override - MongoCollection setupUpdate(String dbName, String collectionName) { - MongoCollection collection = runWithSpan("setup") { - MongoDatabase db = client.getDatabase(dbName) - def latch1 = new CountDownLatch(1) - db.createCollection(collectionName).subscribe(toSubscriber { latch1.countDown() }) - latch1.await(30, TimeUnit.SECONDS) - def coll = db.getCollection(collectionName) - def latch2 = new CountDownLatch(1) - coll.insertOne(new Document("password", "OLDPW")).subscribe(toSubscriber { latch2.countDown() }) - latch2.await(30, TimeUnit.SECONDS) - return coll - } - ignoreTracesAndClear(1) - return collection - } - - @Override - int update(MongoCollection collection) { - def result = new CompletableFuture() - def count = new CompletableFuture() - collection.updateOne( - new BsonDocument("password", new BsonString("OLDPW")), - new BsonDocument('$set', new BsonDocument("password", new BsonString("NEWPW")))).subscribe(toSubscriber { - result.complete(it) - collection.estimatedDocumentCount().subscribe(toSubscriber { count.complete(it) }) - }) - return result.get(30, TimeUnit.SECONDS).modifiedCount - } - - @Override - MongoCollection setupDelete(String dbName, String collectionName) { - MongoCollection collection = runWithSpan("setup") { - MongoDatabase db = client.getDatabase(dbName) - def latch1 = new CountDownLatch(1) - db.createCollection(collectionName).subscribe(toSubscriber { latch1.countDown() }) - latch1.await(30, TimeUnit.SECONDS) - def coll = db.getCollection(collectionName) - def latch2 = new CountDownLatch(1) - coll.insertOne(new Document("password", "SECRET")).subscribe(toSubscriber { latch2.countDown() }) - latch2.await(30, TimeUnit.SECONDS) - return coll - } - ignoreTracesAndClear(1) - return collection - } - - @Override - int delete(MongoCollection collection) { - def result = new CompletableFuture() - def count = new CompletableFuture() - collection.deleteOne(new BsonDocument("password", new BsonString("SECRET"))).subscribe(toSubscriber { - result.complete(it) - collection.estimatedDocumentCount().subscribe(toSubscriber { count.complete(it) }) - }) - return result.get(30, TimeUnit.SECONDS).deletedCount - } - - @Override - MongoCollection setupGetMore(String dbName, String collectionName) { - throw new TestAbortedException("not tested on reactive") - } - - @Override - void getMore(MongoCollection collection) { - throw new TestAbortedException("not tested on reactive") - } - - @Override - void error(String dbName, String collectionName) { - MongoCollection collection = runWithSpan("setup") { - MongoDatabase db = client.getDatabase(dbName) - def latch = new CountDownLatch(1) - db.createCollection(collectionName).subscribe(toSubscriber { - latch.countDown() - }) - latch.await(30, TimeUnit.SECONDS) - return db.getCollection(collectionName) - } - ignoreTracesAndClear(1) - def result = new CompletableFuture() - collection.updateOne(new BsonDocument(), new BsonDocument()).subscribe(toSubscriber { - result.complete(it) - }) - throw result.get(30, TimeUnit.SECONDS) - } - - Subscriber toSubscriber(Closure closure) { - return new Subscriber() { - boolean hasResult - - @Override - void onSubscribe(Subscription s) { - s.request(1) // must request 1 value to trigger async call - } - - @Override - void onNext(Object o) { hasResult = true; closure.call(o) } - - @Override - void onError(Throwable t) { hasResult = true; closure.call(t) } - - @Override - void onComplete() { - if (!hasResult) { - hasResult = true - closure.call() - } - } - } - } -} diff --git a/instrumentation/mongo/mongo-4.0/javaagent/src/test/groovy/MongoClientTest.groovy b/instrumentation/mongo/mongo-4.0/javaagent/src/test/groovy/MongoClientTest.groovy deleted file mode 100644 index 5daec24039fe..000000000000 --- a/instrumentation/mongo/mongo-4.0/javaagent/src/test/groovy/MongoClientTest.groovy +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import com.mongodb.MongoClientSettings -import com.mongodb.ServerAddress -import com.mongodb.client.MongoClient -import com.mongodb.client.MongoClients -import com.mongodb.client.MongoCollection -import com.mongodb.client.MongoDatabase -import io.opentelemetry.instrumentation.mongo.testing.AbstractMongoClientTest -import io.opentelemetry.instrumentation.test.AgentTestTrait -import org.bson.BsonDocument -import org.bson.BsonString -import org.bson.Document -import org.opentest4j.TestAbortedException -import spock.lang.Shared - -class MongoClientTest extends AbstractMongoClientTest> implements AgentTestTrait { - - @Shared - MongoClient client - - def setupSpec() throws Exception { - client = MongoClients.create("mongodb://$host:$port") - } - - def cleanupSpec() throws Exception { - client?.close() - client = null - } - - @Override - void createCollection(String dbName, String collectionName) { - MongoDatabase db = client.getDatabase(dbName) - db.createCollection(collectionName) - } - - @Override - void createCollectionNoDescription(String dbName, String collectionName) { - MongoDatabase db = MongoClients.create("mongodb://$host:${port}").getDatabase(dbName) - db.createCollection(collectionName) - } - - @Override - void createCollectionWithAlreadyBuiltClientOptions(String dbName, String collectionName) { - throw new TestAbortedException("not tested on 4.0") - } - - @Override - void createCollectionCallingBuildTwice(String dbName, String collectionName) { - def settings = MongoClientSettings.builder() - .applyToClusterSettings({ builder -> - builder.hosts(Arrays.asList( - new ServerAddress(host, port))) - }) - settings.build() - MongoDatabase db = MongoClients.create(settings.build()).getDatabase(dbName) - db.createCollection(collectionName) - } - - @Override - int getCollection(String dbName, String collectionName) { - MongoDatabase db = client.getDatabase(dbName) - return db.getCollection(collectionName).estimatedDocumentCount() - } - - @Override - MongoCollection setupInsert(String dbName, String collectionName) { - MongoCollection collection = runWithSpan("setup") { - MongoDatabase db = client.getDatabase(dbName) - db.createCollection(collectionName) - return db.getCollection(collectionName) - } - ignoreTracesAndClear(1) - return collection - } - - @Override - int insert(MongoCollection collection) { - collection.insertOne(new Document("password", "SECRET")) - return collection.estimatedDocumentCount() - } - - @Override - MongoCollection setupUpdate(String dbName, String collectionName) { - MongoCollection collection = runWithSpan("setup") { - MongoDatabase db = client.getDatabase(dbName) - db.createCollection(collectionName) - def coll = db.getCollection(collectionName) - coll.insertOne(new Document("password", "OLDPW")) - return coll - } - ignoreTracesAndClear(1) - return collection - } - - @Override - int update(MongoCollection collection) { - def result = collection.updateOne( - new BsonDocument("password", new BsonString("OLDPW")), - new BsonDocument('$set', new BsonDocument("password", new BsonString("NEWPW")))) - collection.estimatedDocumentCount() - return result.modifiedCount - } - - @Override - MongoCollection setupDelete(String dbName, String collectionName) { - MongoCollection collection = runWithSpan("setup") { - MongoDatabase db = client.getDatabase(dbName) - db.createCollection(collectionName) - def coll = db.getCollection(collectionName) - coll.insertOne(new Document("password", "SECRET")) - return coll - } - ignoreTracesAndClear(1) - return collection - } - - @Override - int delete(MongoCollection collection) { - def result = collection.deleteOne(new BsonDocument("password", new BsonString("SECRET"))) - collection.estimatedDocumentCount() - return result.deletedCount - } - - @Override - MongoCollection setupGetMore(String dbName, String collectionName) { - MongoCollection collection = runWithSpan("setup") { - MongoDatabase db = client.getDatabase(dbName) - def coll = db.getCollection(collectionName) - coll.insertMany([new Document("_id", 0), new Document("_id", 1), new Document("_id", 2)]) - return coll - } - ignoreTracesAndClear(1) - return collection - } - - @Override - void getMore(MongoCollection collection) { - collection.find().filter(new Document("_id", new Document('$gte', 0))) - .batchSize(2).into(new ArrayList()) - } - - @Override - void error(String dbName, String collectionName) { - MongoCollection collection = runWithSpan("setup") { - MongoDatabase db = client.getDatabase(dbName) - db.createCollection(collectionName) - return db.getCollection(collectionName) - } - ignoreTracesAndClear(1) - collection.updateOne(new BsonDocument(), new BsonDocument()) - } -} diff --git a/instrumentation/mongo/mongo-4.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/Mongo4ReactiveClientTest.java b/instrumentation/mongo/mongo-4.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/Mongo4ReactiveClientTest.java new file mode 100644 index 000000000000..f5fb03e54294 --- /dev/null +++ b/instrumentation/mongo/mongo-4.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/Mongo4ReactiveClientTest.java @@ -0,0 +1,295 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.mongo.v4_0; + +import static java.util.Collections.singletonList; + +import com.mongodb.MongoClientSettings; +import com.mongodb.ServerAddress; +import com.mongodb.client.result.DeleteResult; +import com.mongodb.client.result.UpdateResult; +import com.mongodb.reactivestreams.client.MongoClient; +import com.mongodb.reactivestreams.client.MongoClients; +import com.mongodb.reactivestreams.client.MongoCollection; +import com.mongodb.reactivestreams.client.MongoDatabase; +import io.opentelemetry.instrumentation.mongo.testing.AbstractMongoClientTest; +import io.opentelemetry.instrumentation.testing.internal.AutoCleanupExtension; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.Consumer; +import org.bson.BsonDocument; +import org.bson.BsonString; +import org.bson.Document; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.opentest4j.TestAbortedException; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +class Mongo4ReactiveClientTest extends AbstractMongoClientTest> { + + @RegisterExtension + static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); + + @RegisterExtension static final AutoCleanupExtension cleanup = AutoCleanupExtension.create(); + + private MongoClient client; + + @BeforeAll + void setup() { + client = MongoClients.create("mongodb://" + host + ":" + port); + } + + @AfterAll + void cleanup() { + if (client != null) { + client.close(); + } + } + + @Override + protected InstrumentationExtension testing() { + return testing; + } + + @Override + public void createCollection(String dbName, String collectionName) throws InterruptedException { + MongoDatabase db = client.getDatabase(dbName); + CountDownLatch latch = new CountDownLatch(1); + db.createCollection(collectionName).subscribe(toSubscriber(o -> latch.countDown())); + latch.await(30, TimeUnit.SECONDS); + } + + @Override + public void createCollectionNoDescription(String dbName, String collectionName) + throws InterruptedException { + MongoClient tmpClient = MongoClients.create("mongodb://" + host + ":" + port); + cleanup.deferCleanup(tmpClient); + MongoDatabase db = tmpClient.getDatabase(dbName); + CountDownLatch latch = new CountDownLatch(1); + db.createCollection(collectionName).subscribe(toSubscriber(o -> latch.countDown())); + latch.await(30, TimeUnit.SECONDS); + } + + @Override + public void createCollectionWithAlreadyBuiltClientOptions(String dbName, String collectionName) { + throw new TestAbortedException("not tested on 4.0"); + } + + @Override + public void createCollectionCallingBuildTwice(String dbName, String collectionName) + throws InterruptedException { + MongoClientSettings.Builder settings = + MongoClientSettings.builder() + .applyToClusterSettings( + builder -> builder.hosts(singletonList(new ServerAddress(host, port)))); + settings.build(); + MongoClient tmpClient = MongoClients.create(settings.build()); + cleanup.deferCleanup(tmpClient); + MongoDatabase db = tmpClient.getDatabase(dbName); + CountDownLatch latch = new CountDownLatch(1); + db.createCollection(collectionName).subscribe(toSubscriber(o -> latch.countDown())); + latch.await(30, TimeUnit.SECONDS); + } + + @Override + public long getCollection(String dbName, String collectionName) + throws ExecutionException, InterruptedException, TimeoutException { + MongoDatabase db = client.getDatabase(dbName); + CompletableFuture count = new CompletableFuture<>(); + db.getCollection(collectionName) + .estimatedDocumentCount() + .subscribe(toSubscriber(o -> count.complete(((Long) o)))); + return count.get(30, TimeUnit.SECONDS); + } + + @Override + public MongoCollection setupInsert(String dbName, String collectionName) + throws InterruptedException { + MongoCollection collection = + testing() + .runWithSpan( + "setup", + () -> { + MongoDatabase db = client.getDatabase(dbName); + CountDownLatch latch = new CountDownLatch(1); + db.createCollection(collectionName) + .subscribe(toSubscriber(o -> latch.countDown())); + latch.await(30, TimeUnit.SECONDS); + return db.getCollection(collectionName); + }); + ignoreTracesAndClear(1); + return collection; + } + + @Override + public long insert(MongoCollection collection) throws Exception { + CompletableFuture count = new CompletableFuture<>(); + collection + .insertOne(new Document("password", "SECRET")) + .subscribe( + toSubscriber( + result -> + collection + .estimatedDocumentCount() + .subscribe(toSubscriber(c -> count.complete(((Long) c)))))); + return count.get(30, TimeUnit.SECONDS); + } + + @Override + public MongoCollection setupUpdate(String dbName, String collectionName) + throws InterruptedException { + MongoCollection collection = + testing() + .runWithSpan( + "setup", + () -> { + MongoDatabase db = client.getDatabase(dbName); + CountDownLatch latch1 = new CountDownLatch(1); + db.createCollection(collectionName) + .subscribe(toSubscriber(o -> latch1.countDown())); + latch1.await(30, TimeUnit.SECONDS); + MongoCollection coll = db.getCollection(collectionName); + CountDownLatch latch2 = new CountDownLatch(1); + coll.insertOne(new Document("password", "OLDPW")) + .subscribe(toSubscriber(o -> latch2.countDown())); + latch2.await(30, TimeUnit.SECONDS); + return coll; + }); + ignoreTracesAndClear(1); + return collection; + } + + @Override + public long update(MongoCollection collection) throws Exception { + CompletableFuture result = new CompletableFuture<>(); + CompletableFuture count = new CompletableFuture<>(); + collection + .updateOne( + new BsonDocument("password", new BsonString("OLDPW")), + new BsonDocument("$set", new BsonDocument("password", new BsonString("NEWPW")))) + .subscribe( + toSubscriber( + updateResult -> { + result.complete(((UpdateResult) updateResult)); + collection + .estimatedDocumentCount() + .subscribe(toSubscriber(o -> count.complete(((Long) o)))); + })); + return result.get(30, TimeUnit.SECONDS).getModifiedCount(); + } + + @Override + public MongoCollection setupDelete(String dbName, String collectionName) + throws InterruptedException { + MongoCollection collection = + testing() + .runWithSpan( + "setup", + () -> { + MongoDatabase db = client.getDatabase(dbName); + CountDownLatch latch1 = new CountDownLatch(1); + db.createCollection(collectionName) + .subscribe(toSubscriber(o -> latch1.countDown())); + latch1.await(30, TimeUnit.SECONDS); + MongoCollection coll = db.getCollection(collectionName); + CountDownLatch latch2 = new CountDownLatch(1); + coll.insertOne(new Document("password", "SECRET")) + .subscribe(toSubscriber(o -> latch2.countDown())); + latch2.await(30, TimeUnit.SECONDS); + return coll; + }); + ignoreTracesAndClear(1); + return collection; + } + + @Override + public long delete(MongoCollection collection) + throws ExecutionException, InterruptedException, TimeoutException { + CompletableFuture result = new CompletableFuture<>(); + CompletableFuture count = new CompletableFuture<>(); + collection + .deleteOne(new BsonDocument("password", new BsonString("SECRET"))) + .subscribe( + toSubscriber( + deleteResult -> { + result.complete(((DeleteResult) deleteResult)); + collection + .estimatedDocumentCount() + .subscribe(toSubscriber(o -> count.complete(((Long) o)))); + })); + return result.get(30, TimeUnit.SECONDS).getDeletedCount(); + } + + @Override + public MongoCollection setupGetMore(String dbName, String collectionName) { + throw new TestAbortedException("not tested on reactive"); + } + + @Override + public void getMore(MongoCollection collection) { + throw new TestAbortedException("not tested on reactive"); + } + + @Override + public void error(String dbName, String collectionName) throws Throwable { + MongoCollection collection = + testing() + .runWithSpan( + "setup", + () -> { + MongoDatabase db = client.getDatabase(dbName); + CountDownLatch latch = new CountDownLatch(1); + db.createCollection(collectionName) + .subscribe(toSubscriber(o -> latch.countDown())); + latch.await(30, TimeUnit.SECONDS); + return db.getCollection(collectionName); + }); + ignoreTracesAndClear(1); + CompletableFuture result = new CompletableFuture<>(); + collection + .updateOne(new BsonDocument(), new BsonDocument()) + .subscribe(toSubscriber(t -> result.complete(((Throwable) t)))); + throw result.get(30, TimeUnit.SECONDS); + } + + Subscriber toSubscriber(Consumer consumer) { + return new Subscriber() { + private boolean hasResult; + + @Override + public void onSubscribe(Subscription s) { + s.request(1); // must request 1 value to trigger async call + } + + @Override + public void onNext(Object o) { + hasResult = true; + consumer.accept(o); + } + + @Override + public void onError(Throwable t) { + hasResult = true; + consumer.accept(t); + } + + @Override + public void onComplete() { + if (!hasResult) { + hasResult = true; + consumer.accept(null); + } + } + }; + } +} diff --git a/instrumentation/mongo/mongo-4.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/MongoClientTest.java b/instrumentation/mongo/mongo-4.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/MongoClientTest.java new file mode 100644 index 000000000000..864288e9ec6b --- /dev/null +++ b/instrumentation/mongo/mongo-4.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/MongoClientTest.java @@ -0,0 +1,203 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.mongo.v4_0; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; + +import com.mongodb.MongoClientSettings; +import com.mongodb.ServerAddress; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.result.DeleteResult; +import com.mongodb.client.result.UpdateResult; +import io.opentelemetry.instrumentation.mongo.testing.AbstractMongoClientTest; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import java.util.ArrayList; +import org.bson.BsonDocument; +import org.bson.BsonString; +import org.bson.Document; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.opentest4j.TestAbortedException; + +class MongoClientTest extends AbstractMongoClientTest> { + + @RegisterExtension + static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); + + private MongoClient client; + + @BeforeAll + void setup() { + client = MongoClients.create("mongodb://" + host + ":" + port); + } + + @AfterAll + void cleanup() { + if (client != null) { + client.close(); + } + } + + @Override + protected InstrumentationExtension testing() { + return testing; + } + + @Override + public void createCollection(String dbName, String collectionName) { + MongoDatabase db = client.getDatabase(dbName); + db.createCollection(collectionName); + } + + @Override + public void createCollectionNoDescription(String dbName, String collectionName) { + MongoDatabase db = MongoClients.create("mongodb://" + host + ":" + port).getDatabase(dbName); + db.createCollection(collectionName); + } + + @Override + public void createCollectionWithAlreadyBuiltClientOptions(String dbName, String collectionName) { + throw new TestAbortedException("not tested on 4.0"); + } + + @Override + public void createCollectionCallingBuildTwice(String dbName, String collectionName) { + MongoClientSettings.Builder settings = + MongoClientSettings.builder() + .applyToClusterSettings( + builder -> builder.hosts(singletonList(new ServerAddress(host, port)))); + settings.build(); + MongoDatabase db = MongoClients.create(settings.build()).getDatabase(dbName); + db.createCollection(collectionName); + } + + @Override + public long getCollection(String dbName, String collectionName) { + MongoDatabase db = client.getDatabase(dbName); + return db.getCollection(collectionName).estimatedDocumentCount(); + } + + @Override + public MongoCollection setupInsert(String dbName, String collectionName) { + MongoCollection collection = + testing() + .runWithSpan( + "setup", + () -> { + MongoDatabase db = client.getDatabase(dbName); + db.createCollection(collectionName); + return db.getCollection(collectionName); + }); + ignoreTracesAndClear(1); + return collection; + } + + @Override + public long insert(MongoCollection collection) { + collection.insertOne(new Document("password", "SECRET")); + return collection.estimatedDocumentCount(); + } + + @Override + public MongoCollection setupUpdate(String dbName, String collectionName) { + MongoCollection collection = + testing() + .runWithSpan( + "setup", + () -> { + MongoDatabase db = client.getDatabase(dbName); + db.createCollection(collectionName); + MongoCollection coll = db.getCollection(collectionName); + coll.insertOne(new Document("password", "OLDPW")); + return coll; + }); + ignoreTracesAndClear(1); + return collection; + } + + @Override + public long update(MongoCollection collection) { + UpdateResult result = + collection.updateOne( + new BsonDocument("password", new BsonString("OLDPW")), + new BsonDocument("$set", new BsonDocument("password", new BsonString("NEWPW")))); + collection.estimatedDocumentCount(); + return result.getModifiedCount(); + } + + @Override + public MongoCollection setupDelete(String dbName, String collectionName) { + MongoCollection collection = + testing() + .runWithSpan( + "setup", + () -> { + MongoDatabase db = client.getDatabase(dbName); + db.createCollection(collectionName); + MongoCollection coll = db.getCollection(collectionName); + coll.insertOne(new Document("password", "SECRET")); + return coll; + }); + ignoreTracesAndClear(1); + return collection; + } + + @Override + public long delete(MongoCollection collection) { + DeleteResult result = + collection.deleteOne(new BsonDocument("password", new BsonString("SECRET"))); + collection.estimatedDocumentCount(); + return result.getDeletedCount(); + } + + @Override + public MongoCollection setupGetMore(String dbName, String collectionName) { + MongoCollection collection = + testing() + .runWithSpan( + "setup", + () -> { + MongoDatabase db = client.getDatabase(dbName); + MongoCollection coll = db.getCollection(collectionName); + coll.insertMany( + asList( + new Document("_id", 0), new Document("_id", 1), new Document("_id", 2))); + return coll; + }); + ignoreTracesAndClear(1); + return collection; + } + + @Override + public void getMore(MongoCollection collection) { + collection + .find() + .filter(new Document("_id", new Document("$gte", 0))) + .batchSize(2) + .into(new ArrayList<>()); + } + + @Override + public void error(String dbName, String collectionName) { + MongoCollection collection = + testing() + .runWithSpan( + "setup", + () -> { + MongoDatabase db = client.getDatabase(dbName); + db.createCollection(collectionName); + return db.getCollection(collectionName); + }); + ignoreTracesAndClear(1); + collection.updateOne(new BsonDocument(), new BsonDocument()); + } +} diff --git a/instrumentation/mongo/mongo-async-3.3/javaagent/src/test/groovy/MongoAsyncClientTest.groovy b/instrumentation/mongo/mongo-async-3.3/javaagent/src/test/groovy/MongoAsyncClientTest.groovy deleted file mode 100644 index dea675af2c98..000000000000 --- a/instrumentation/mongo/mongo-async-3.3/javaagent/src/test/groovy/MongoAsyncClientTest.groovy +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import com.mongodb.ConnectionString -import com.mongodb.async.SingleResultCallback -import com.mongodb.async.client.MongoClient -import com.mongodb.async.client.MongoClientSettings -import com.mongodb.async.client.MongoClients -import com.mongodb.async.client.MongoCollection -import com.mongodb.async.client.MongoDatabase -import com.mongodb.client.result.DeleteResult -import com.mongodb.client.result.UpdateResult -import com.mongodb.connection.ClusterSettings -import io.opentelemetry.instrumentation.mongo.testing.AbstractMongoClientTest -import io.opentelemetry.instrumentation.test.AgentTestTrait -import org.bson.BsonDocument -import org.bson.BsonString -import org.bson.Document -import org.opentest4j.TestAbortedException -import spock.lang.Shared - -import java.util.concurrent.CompletableFuture -import java.util.concurrent.CountDownLatch - -class MongoAsyncClientTest extends AbstractMongoClientTest> implements AgentTestTrait { - - @Shared - MongoClient client - - def setupSpec() throws Exception { - client = MongoClients.create( - MongoClientSettings.builder() - .clusterSettings( - ClusterSettings.builder() - .description("some-description") - .applyConnectionString(new ConnectionString("mongodb://$host:$port")) - .build()) - .build()) - } - - def cleanupSpec() throws Exception { - client?.close() - client = null - } - - @Override - void createCollection(String dbName, String collectionName) { - MongoDatabase db = client.getDatabase(dbName) - db.createCollection(collectionName, toCallback {}) - } - - @Override - void createCollectionNoDescription(String dbName, String collectionName) { - MongoDatabase db = MongoClients.create("mongodb://$host:$port").getDatabase(dbName) - db.createCollection(collectionName, toCallback {}) - } - - @Override - void createCollectionWithAlreadyBuiltClientOptions(String dbName, String collectionName) { - def clientSettings = client.settings - def newClientSettings = MongoClientSettings.builder(clientSettings).build() - MongoDatabase db = MongoClients.create(newClientSettings).getDatabase(dbName) - db.createCollection(collectionName, toCallback {}) - } - - @Override - void createCollectionCallingBuildTwice(String dbName, String collectionName) { - def settings = MongoClientSettings.builder() - .clusterSettings( - ClusterSettings.builder() - .description("some-description") - .applyConnectionString(new ConnectionString("mongodb://$host:$port")) - .build()) - settings.build() - MongoDatabase db = MongoClients.create(settings.build()).getDatabase(dbName) - db.createCollection(collectionName, toCallback {}) - } - - @Override - int getCollection(String dbName, String collectionName) { - MongoDatabase db = client.getDatabase(dbName) - def count = new CompletableFuture() - db.getCollection(collectionName).count toCallback { count.complete(it) } - return count.join() - } - - @Override - MongoCollection setupInsert(String dbName, String collectionName) { - MongoCollection collection = runWithSpan("setup") { - MongoDatabase db = client.getDatabase(dbName) - def latch1 = new CountDownLatch(1) - db.createCollection(collectionName, toCallback { latch1.countDown() }) - latch1.await() - return db.getCollection(collectionName) - } - ignoreTracesAndClear(1) - return collection - } - - @Override - int insert(MongoCollection collection) { - def count = new CompletableFuture() - collection.insertOne(new Document("password", "SECRET"), toCallback { - collection.count toCallback { count.complete(it) } - }) - return count.get() - } - - @Override - MongoCollection setupUpdate(String dbName, String collectionName) { - MongoCollection collection = runWithSpan("setup") { - MongoDatabase db = client.getDatabase(dbName) - def latch1 = new CountDownLatch(1) - db.createCollection(collectionName, toCallback { latch1.countDown() }) - latch1.await() - def coll = db.getCollection(collectionName) - def latch2 = new CountDownLatch(1) - coll.insertOne(new Document("password", "OLDPW"), toCallback { latch2.countDown() }) - latch2.await() - return coll - } - ignoreTracesAndClear(1) - return collection - } - - @Override - int update(MongoCollection collection) { - def result = new CompletableFuture() - def count = new CompletableFuture() - collection.updateOne( - new BsonDocument("password", new BsonString("OLDPW")), - new BsonDocument('$set', new BsonDocument("password", new BsonString("NEWPW"))), toCallback { - result.complete(it) - collection.count toCallback { count.complete(it) } - }) - return result.get().modifiedCount - } - - @Override - MongoCollection setupDelete(String dbName, String collectionName) { - MongoCollection collection = runWithSpan("setup") { - MongoDatabase db = client.getDatabase(dbName) - def latch1 = new CountDownLatch(1) - db.createCollection(collectionName, toCallback { latch1.countDown() }) - latch1.await() - def coll = db.getCollection(collectionName) - def latch2 = new CountDownLatch(1) - coll.insertOne(new Document("password", "SECRET"), toCallback { latch2.countDown() }) - latch2.await() - return coll - } - ignoreTracesAndClear(1) - return collection - } - - @Override - int delete(MongoCollection collection) { - def result = new CompletableFuture() - def count = new CompletableFuture() - collection.deleteOne(new BsonDocument("password", new BsonString("SECRET")), toCallback { - result.complete(it) - collection.count toCallback { count.complete(it) } - }) - return result.get().deletedCount - } - - @Override - MongoCollection setupGetMore(String dbName, String collectionName) { - throw new TestAbortedException("not tested on async") - } - - @Override - void getMore(MongoCollection collection) { - throw new TestAbortedException("not tested on async") - } - - @Override - void error(String dbName, String collectionName) { - MongoCollection collection = runWithSpan("setup") { - MongoDatabase db = client.getDatabase(dbName) - def latch = new CountDownLatch(1) - db.createCollection(collectionName, toCallback { - latch.countDown() - }) - latch.await() - return db.getCollection(collectionName) - } - ignoreTracesAndClear(1) - def result = new CompletableFuture() - collection.updateOne(new BsonDocument(), new BsonDocument(), toCallback { - result.complete(it) - }) - throw result.join() - } - - SingleResultCallback toCallback(Closure closure) { - return new SingleResultCallback() { - @Override - void onResult(Object result, Throwable t) { - if (t) { - closure.call(t) - } else { - closure.call(result) - } - } - } - } -} diff --git a/instrumentation/mongo/mongo-async-3.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/mongoasync/v3_3/MongoAsyncClientTest.java b/instrumentation/mongo/mongo-async-3.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/mongoasync/v3_3/MongoAsyncClientTest.java new file mode 100644 index 000000000000..80b0807f9402 --- /dev/null +++ b/instrumentation/mongo/mongo-async-3.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/mongoasync/v3_3/MongoAsyncClientTest.java @@ -0,0 +1,251 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.mongoasync.v3_3; + +import com.mongodb.ConnectionString; +import com.mongodb.async.SingleResultCallback; +import com.mongodb.async.client.MongoClient; +import com.mongodb.async.client.MongoClientSettings; +import com.mongodb.async.client.MongoClients; +import com.mongodb.async.client.MongoCollection; +import com.mongodb.async.client.MongoDatabase; +import com.mongodb.client.result.DeleteResult; +import com.mongodb.client.result.UpdateResult; +import com.mongodb.connection.ClusterSettings; +import io.opentelemetry.instrumentation.mongo.testing.AbstractMongoClientTest; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import org.bson.BsonDocument; +import org.bson.BsonString; +import org.bson.Document; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.opentest4j.TestAbortedException; + +class MongoAsyncClientTest extends AbstractMongoClientTest> { + + @RegisterExtension + static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); + + private MongoClient client; + + @BeforeAll + void setup() { + client = + MongoClients.create( + MongoClientSettings.builder() + .clusterSettings( + ClusterSettings.builder() + .description("some-description") + .applyConnectionString( + new ConnectionString("mongodb://" + host + ":" + port)) + .build()) + .build()); + } + + @AfterAll + void cleanup() { + if (client != null) { + client.close(); + } + } + + @Override + protected InstrumentationExtension testing() { + return testing; + } + + @Override + public void createCollection(String dbName, String collectionName) { + MongoDatabase db = client.getDatabase(dbName); + db.createCollection(collectionName, toCallback(result -> {})); + } + + @Override + public void createCollectionNoDescription(String dbName, String collectionName) { + MongoDatabase db = MongoClients.create("mongodb://" + host + ":" + port).getDatabase(dbName); + db.createCollection(collectionName, toCallback(result -> {})); + } + + @Override + public void createCollectionWithAlreadyBuiltClientOptions(String dbName, String collectionName) { + MongoClientSettings clientSettings = client.getSettings(); + MongoClientSettings newClientSettings = MongoClientSettings.builder(clientSettings).build(); + MongoDatabase db = MongoClients.create(newClientSettings).getDatabase(dbName); + db.createCollection(collectionName, toCallback(result -> {})); + } + + @Override + public void createCollectionCallingBuildTwice(String dbName, String collectionName) { + MongoClientSettings.Builder settings = + MongoClientSettings.builder() + .clusterSettings( + ClusterSettings.builder() + .description("some-description") + .applyConnectionString(new ConnectionString("mongodb://" + host + ":" + port)) + .build()); + settings.build(); + MongoDatabase db = MongoClients.create(settings.build()).getDatabase(dbName); + db.createCollection(collectionName, toCallback(result -> {})); + } + + @Override + public long getCollection(String dbName, String collectionName) { + MongoDatabase db = client.getDatabase(dbName); + CompletableFuture count = new CompletableFuture<>(); + db.getCollection(collectionName).count(toCallback(o -> count.complete(((Long) o)))); + return count.join(); + } + + @Override + public MongoCollection setupInsert(String dbName, String collectionName) + throws InterruptedException { + MongoCollection collection = + testing() + .runWithSpan( + "setup", + () -> { + MongoDatabase db = client.getDatabase(dbName); + CountDownLatch latch = new CountDownLatch(1); + db.createCollection(collectionName, toCallback(result -> latch.countDown())); + latch.await(30, TimeUnit.SECONDS); + return db.getCollection(collectionName); + }); + ignoreTracesAndClear(1); + return collection; + } + + @Override + public long insert(MongoCollection collection) { + CompletableFuture count = new CompletableFuture<>(); + collection.insertOne( + new Document("password", "SECRET"), + toCallback(result -> collection.count(toCallback(o -> count.complete(((Long) o)))))); + return count.join(); + } + + @Override + public MongoCollection setupUpdate(String dbName, String collectionName) + throws InterruptedException { + MongoCollection collection = + testing() + .runWithSpan( + "setup", + () -> { + MongoDatabase db = client.getDatabase(dbName); + CountDownLatch latch1 = new CountDownLatch(1); + db.createCollection(collectionName, toCallback(result -> latch1.countDown())); + latch1.await(30, TimeUnit.SECONDS); + MongoCollection coll = db.getCollection(collectionName); + CountDownLatch latch2 = new CountDownLatch(1); + coll.insertOne( + new Document("password", "OLDPW"), toCallback(result -> latch2.countDown())); + latch2.await(30, TimeUnit.SECONDS); + return coll; + }); + ignoreTracesAndClear(1); + return collection; + } + + @Override + public long update(MongoCollection collection) { + CompletableFuture result = new CompletableFuture<>(); + CompletableFuture count = new CompletableFuture<>(); + collection.updateOne( + new BsonDocument("password", new BsonString("OLDPW")), + new BsonDocument("$set", new BsonDocument("password", new BsonString("NEWPW"))), + toCallback( + res -> { + result.complete(((UpdateResult) res)); + collection.count(toCallback(o -> count.complete(((Long) o)))); + })); + return result.join().getModifiedCount(); + } + + @Override + public MongoCollection setupDelete(String dbName, String collectionName) + throws InterruptedException { + MongoCollection collection = + testing() + .runWithSpan( + "setup", + () -> { + MongoDatabase db = client.getDatabase(dbName); + CountDownLatch latch1 = new CountDownLatch(1); + db.createCollection(collectionName, toCallback(result -> latch1.countDown())); + latch1.await(30, TimeUnit.SECONDS); + MongoCollection coll = db.getCollection(collectionName); + CountDownLatch latch2 = new CountDownLatch(1); + coll.insertOne( + new Document("password", "SECRET"), toCallback(result -> latch2.countDown())); + latch2.await(30, TimeUnit.SECONDS); + return coll; + }); + ignoreTracesAndClear(1); + return collection; + } + + @Override + public long delete(MongoCollection collection) { + CompletableFuture result = new CompletableFuture<>(); + CompletableFuture count = new CompletableFuture<>(); + collection.deleteOne( + new BsonDocument("password", new BsonString("SECRET")), + toCallback( + res -> { + result.complete((DeleteResult) res); + collection.count(toCallback(value -> count.complete(((Long) value)))); + })); + return result.join().getDeletedCount(); + } + + @Override + public MongoCollection setupGetMore(String dbName, String collectionName) { + throw new TestAbortedException("not tested on async"); + } + + @Override + public void getMore(MongoCollection collection) { + throw new TestAbortedException("not tested on async"); + } + + @Override + public void error(String dbName, String collectionName) throws Throwable { + MongoCollection collection = + testing() + .runWithSpan( + "setup", + () -> { + MongoDatabase db = client.getDatabase(dbName); + CountDownLatch latch = new CountDownLatch(1); + db.createCollection(collectionName, toCallback(result -> latch.countDown())); + latch.await(30, TimeUnit.SECONDS); + return db.getCollection(collectionName); + }); + ignoreTracesAndClear(1); + CompletableFuture result = new CompletableFuture<>(); + collection.updateOne( + new BsonDocument(), + new BsonDocument(), + toCallback(res -> result.complete((Throwable) res))); + throw result.join(); + } + + SingleResultCallback toCallback(Consumer closure) { + return (result, t) -> { + if (t != null) { + closure.accept(t); + } else { + closure.accept(result); + } + }; + } +} diff --git a/instrumentation/mongo/mongo-common/testing/build.gradle.kts b/instrumentation/mongo/mongo-common/testing/build.gradle.kts index 307ad9abc8f4..777c9b3d9d5e 100644 --- a/instrumentation/mongo/mongo-common/testing/build.gradle.kts +++ b/instrumentation/mongo/mongo-common/testing/build.gradle.kts @@ -6,7 +6,5 @@ dependencies { api(project(":testing-common")) api("org.testcontainers:mongodb") - implementation("org.apache.groovy:groovy") implementation("io.opentelemetry:opentelemetry-api") - implementation("org.spockframework:spock-core") } diff --git a/instrumentation/mongo/mongo-common/testing/src/main/groovy/io/opentelemetry/instrumentation/mongo/testing/AbstractMongoClientTest.groovy b/instrumentation/mongo/mongo-common/testing/src/main/groovy/io/opentelemetry/instrumentation/mongo/testing/AbstractMongoClientTest.groovy deleted file mode 100644 index 3cd9dafc2afe..000000000000 --- a/instrumentation/mongo/mongo-common/testing/src/main/groovy/io/opentelemetry/instrumentation/mongo/testing/AbstractMongoClientTest.groovy +++ /dev/null @@ -1,432 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.mongo.testing - -import io.opentelemetry.api.trace.SpanKind -import io.opentelemetry.instrumentation.test.InstrumentationSpecification -import io.opentelemetry.instrumentation.test.asserts.TraceAssert -import io.opentelemetry.sdk.trace.data.SpanData -import io.opentelemetry.semconv.incubating.DbIncubatingAttributes -import io.opentelemetry.semconv.ServerAttributes -import org.slf4j.LoggerFactory -import org.testcontainers.containers.GenericContainer -import org.testcontainers.containers.output.Slf4jLogConsumer -import spock.lang.Shared - -import java.util.concurrent.atomic.AtomicInteger - -import static io.opentelemetry.api.trace.SpanKind.CLIENT - -abstract class AbstractMongoClientTest extends InstrumentationSpecification { - - @Shared - GenericContainer mongodb - - @Shared - String host - - @Shared - int port - - def setupSpec() { - mongodb = new GenericContainer("mongo:4.0") - .withExposedPorts(27017) - .withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger("mongodb"))) - mongodb.start() - host = mongodb.getHost() - port = mongodb.getMappedPort(27017) - } - - def cleanupSpec() throws Exception { - mongodb.stop() - } - - // Different client versions have different APIs to do these operations. If adding a test for a new - // version, refer to existing ones on how to implement these operations. - - abstract void createCollection(String dbName, String collectionName) - - abstract void createCollectionNoDescription(String dbName, String collectionName) - - // Tests the fix for https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/457 - // TracingCommandListener might get added multiple times if clientOptions are built using existing clientOptions or when calling a build method twice. - // This test asserts that duplicate traces are not created in those cases. - abstract void createCollectionWithAlreadyBuiltClientOptions(String dbName, String collectionName) - - abstract void createCollectionCallingBuildTwice(String dbName, String collectionName) - - abstract int getCollection(String dbName, String collectionName) - - abstract T setupInsert(String dbName, String collectionName) - - abstract int insert(T collection) - - abstract T setupUpdate(String dbName, String collectionName) - - abstract int update(T collection) - - abstract T setupDelete(String dbName, String collectionName) - - abstract int delete(T collection) - - abstract T setupGetMore(String dbName, String collectionName) - - abstract void getMore(T collection) - - abstract void error(String dbName, String collectionName) - - def "test port open"() { - when: - new Socket(host, port) - - then: - noExceptionThrown() - } - - def "test create collection"() { - when: - runWithSpan("parent") { - createCollection(dbName, collectionName) - } - - then: - assertTraces(1) { - trace(0, 2) { - span(0) { - name "parent" - kind SpanKind.INTERNAL - hasNoParent() - } - mongoSpan(it, 1, "create", collectionName, dbName, span(0)) { - assert it == '{"create":"' + collectionName + '","capped":"?"}' || - it == '{"create":"' + collectionName + '","capped":"?","$db":"?"}' || - it == '{"create":"' + collectionName + '","capped":"?","$db":"?","$readPreference":{"mode":"?"}}' || - it == '{"create":"' + collectionName + '","capped":"?","$db":"?","lsid":{"id":"?"}}' - true - } - } - } - - where: - dbName = "test_db" - collectionName = createCollectionName() - } - - def "test create collection no description"() { - when: - runWithSpan("parent") { - createCollectionNoDescription(dbName, collectionName) - } - - then: - assertTraces(1) { - trace(0, 2) { - span(0) { - name "parent" - kind SpanKind.INTERNAL - hasNoParent() - } - mongoSpan(it, 1, "create", collectionName, dbName, span(0), { - assert it == '{"create":"' + collectionName + '","capped":"?"}' || - it == '{"create":"' + collectionName + '","capped":"?","$db":"?"}' || - it == '{"create":"' + collectionName + '","capped":"?","$db":"?","$readPreference":{"mode":"?"}}' || - it == '{"create":"' + collectionName + '","capped":"?","$db":"?","lsid":{"id":"?"}}' - true - }) - } - } - - where: - dbName = "test_db" - collectionName = createCollectionName() - } - - def "test create collection calling build twice"() { - when: - runWithSpan("parent") { - createCollectionCallingBuildTwice(dbName, collectionName) - } - - then: - assertTraces(1) { - trace(0, 2) { - span(0) { - name "parent" - kind SpanKind.INTERNAL - hasNoParent() - } - mongoSpan(it, 1, "create", collectionName, dbName, span(0)) { - assert it == '{"create":"' + collectionName + '","capped":"?"}' || - it == '{"create":"' + collectionName + '","capped":"?","$db":"?"}' || - it == '{"create":"' + collectionName + '","capped":"?","$db":"?","$readPreference":{"mode":"?"}}' || - it == '{"create":"' + collectionName + '","capped":"?","$db":"?","lsid":{"id":"?"}}' - true - } - } - } - - where: - dbName = "test_db" - collectionName = createCollectionName() - } - - def "test get collection"() { - when: - def count = runWithSpan("parent") { - getCollection(dbName, collectionName) - } - - then: - count == 0 - assertTraces(1) { - trace(0, 2) { - span(0) { - name "parent" - kind SpanKind.INTERNAL - hasNoParent() - } - mongoSpan(it, 1, "count", collectionName, dbName, span(0)) { - assert it == '{"count":"' + collectionName + '","query":{}}' || - it == '{"count":"' + collectionName + '","query":{},"$db":"?"}' || - it == '{"count":"' + collectionName + '","query":{},"$db":"?","lsid":{"id":"?"}}' || - it == '{"count":"' + collectionName + '","query":{},"$db":"?","$readPreference":{"mode":"?"}}' || - it == '{"count":"' + collectionName + '","$db":"?","lsid":{"id":"?"}}' - true - } - } - } - - where: - dbName = "test_db" - collectionName = createCollectionName() - } - - def "test insert"() { - when: - def collection = setupInsert(dbName, collectionName) - def count = runWithSpan("parent") { - insert(collection) - } - - then: - count == 1 - assertTraces(1) { - trace(0, 3) { - span(0) { - name "parent" - kind SpanKind.INTERNAL - hasNoParent() - } - mongoSpan(it, 1, "insert", collectionName, dbName, span(0)) { - assert it == '{"insert":"' + collectionName + '","ordered":"?","documents":[{"_id":"?","password":"?"}]}' || - it == '{"insert":"' + collectionName + '","ordered":"?","$db":"?","documents":[{"_id":"?","password":"?"}]}' || - it == '{"insert":"' + collectionName + '","ordered":"?","$db":"?","lsid":{"id":"?"},"documents":[{"_id":"?","password":"?"}]}' - true - } - mongoSpan(it, 2, "count", collectionName, dbName, span(0)) { - assert it == '{"count":"' + collectionName + '","query":{}}' || - it == '{"count":"' + collectionName + '","query":{},"$db":"?"}' || - it == '{"count":"' + collectionName + '","query":{},"$db":"?","lsid":{"id":"?"}}' || - it == '{"count":"' + collectionName + '","query":{},"$db":"?","$readPreference":{"mode":"?"}}' || - it == '{"count":"' + collectionName + '","$db":"?","lsid":{"id":"?"}}' - true - } - } - } - - where: - dbName = "test_db" - collectionName = createCollectionName() - } - - def "test update"() { - when: - def collection = setupUpdate(dbName, collectionName) - int modifiedCount = runWithSpan("parent") { - update(collection) - } - - then: - modifiedCount == 1 - assertTraces(1) { - trace(0, 3) { - span(0) { - name "parent" - kind SpanKind.INTERNAL - hasNoParent() - } - mongoSpan(it, 1, "update", collectionName, dbName, span(0)) { - assert it == '{"update":"' + collectionName + '","ordered":"?","updates":[{"q":{"password":"?"},"u":{"$set":{"password":"?"}}}]}' || - it == '{"update":"' + collectionName + '","ordered":"?","$db":"?","updates":[{"q":{"password":"?"},"u":{"$set":{"password":"?"}}}]}' || - it == '{"update":"' + collectionName + '","ordered":"?","$db":"?","lsid":{"id":"?"},"updates":[{"q":{"password":"?"},"u":{"$set":{"password":"?"}}}]}' - true - } - mongoSpan(it, 2, "count", collectionName, dbName, span(0)) { - assert it == '{"count":"' + collectionName + '","query":{}}' || - it == '{"count":"' + collectionName + '","query":{},"$db":"?"}' || - it == '{"count":"' + collectionName + '","query":{},"$db":"?","lsid":{"id":"?"}}' || - it == '{"count":"' + collectionName + '","query":{},"$db":"?","$readPreference":{"mode":"?"}}' || - it == '{"count":"' + collectionName + '","$db":"?","lsid":{"id":"?"}}' - true - } - } - } - - where: - dbName = "test_db" - collectionName = createCollectionName() - } - - def "test delete"() { - when: - def collection = setupDelete(dbName, collectionName) - int deletedCount = runWithSpan("parent") { - delete(collection) - } - - then: - deletedCount == 1 - assertTraces(1) { - trace(0, 3) { - span(0) { - name "parent" - kind SpanKind.INTERNAL - hasNoParent() - } - mongoSpan(it, 1, "delete", collectionName, dbName, span(0)) { - assert it == '{"delete":"' + collectionName + '","ordered":"?","deletes":[{"q":{"password":"?"},"limit":"?"}]}' || - it == '{"delete":"' + collectionName + '","ordered":"?","$db":"?","deletes":[{"q":{"password":"?"},"limit":"?"}]}' || - it == '{"delete":"' + collectionName + '","ordered":"?","$db":"?","lsid":{"id":"?"},"deletes":[{"q":{"password":"?"},"limit":"?"}]}' - true - } - mongoSpan(it, 2, "count", collectionName, dbName, span(0)) { - assert it == '{"count":"' + collectionName + '","query":{}}' || - it == '{"count":"' + collectionName + '","query":{},"$db":"?"}' || - it == '{"count":"' + collectionName + '","query":{},"$db":"?","lsid":{"id":"?"}}' || - it == '{"count":"' + collectionName + '","query":{},"$db":"?","$readPreference":{"mode":"?"}}' || - it == '{"count":"' + collectionName + '","$db":"?","lsid":{"id":"?"}}' - true - } - } - } - - where: - dbName = "test_db" - collectionName = createCollectionName() - } - - def "test collection name for getMore command"() { - when: - def collection = setupGetMore(dbName, collectionName) - runWithSpan("parent") { - getMore(collection) - } - - then: - assertTraces(1) { - trace(0, 3) { - span(0) { - name "parent" - kind SpanKind.INTERNAL - hasNoParent() - } - mongoSpan(it, 1, "find", collectionName, dbName, span(0)) { - assert it == '{"find":"' + collectionName + '","filter":{"_id":{"$gte":"?"}},"batchSize":"?"}' || - it == '{"find":"' + collectionName + '","filter":{"_id":{"$gte":"?"}},"batchSize":"?","$db":"?"}' || - it == '{"find":"' + collectionName + '","filter":{"_id":{"$gte":"?"}},"batchSize":"?","$db":"?","$readPreference":{"mode":"?"}}' || - it == '{"find":"' + collectionName + '","filter":{"_id":{"$gte":"?"}},"batchSize":"?","$db":"?","lsid":{"id":"?"}}' - true - } - mongoSpan(it, 2, "getMore", collectionName, dbName, span(0)) { - assert it == '{"getMore":"?","collection":"?","batchSize":"?"}' || - it == '{"getMore":"?","collection":"?","batchSize":"?","$db":"?"}' || - it == '{"getMore":"?","collection":"?","batchSize":"?","$db":"?","$readPreference":{"mode":"?"}}' || - it == '{"getMore":"?","collection":"?","batchSize":"?","$db":"?","lsid":{"id":"?"}}' - true - } - } - } - - where: - dbName = "test_db" - collectionName = createCollectionName() - } - - def "test error"() { - when: - error(dbName, collectionName) - - then: - thrown(IllegalArgumentException) - // Unfortunately not caught by our instrumentation. - assertTraces(0) {} - - where: - dbName = "test_db" - collectionName = createCollectionName() - } - - def "test create collection with already built ClientOptions"() { - when: - runWithSpan("parent") { - createCollectionWithAlreadyBuiltClientOptions(dbName, collectionName) - } - - then: - assertTraces(1) { - trace(0, 2) { - span(0) { - name "parent" - kind SpanKind.INTERNAL - hasNoParent() - } - mongoSpan(it, 1, "create", collectionName, dbName, span(0)) { - assert it == '{"create":"' + collectionName + '","capped":"?"}' || - '{"create":"' + collectionName + '","capped":"?","$readPreference":{"mode":"?"}}' - true - } - } - } - - where: - dbName = "test_db" - collectionName = createCollectionName() - } - - private static final AtomicInteger collectionIndex = new AtomicInteger() - - def createCollectionName() { - return "testCollection-${collectionIndex.getAndIncrement()}" - } - - @SuppressWarnings("deprecation") // TODO DbIncubatingAttributes.DB_CONNECTION_STRING deprecation - def mongoSpan(TraceAssert trace, int index, - String operation, String collection, - String dbName, Object parentSpan, - Closure statementEval) { - trace.span(index) { - name operation + " " + dbName + "." + collection - kind CLIENT - if (parentSpan == null) { - hasNoParent() - } else { - childOf((SpanData) parentSpan) - } - attributes { - "$ServerAttributes.SERVER_ADDRESS" host - "$ServerAttributes.SERVER_PORT" port - "$DbIncubatingAttributes.DB_STATEMENT" { - statementEval.call(it.replaceAll(" ", "")) - } - "$DbIncubatingAttributes.DB_SYSTEM" "mongodb" - "$DbIncubatingAttributes.DB_CONNECTION_STRING" "mongodb://localhost:" + port - "$DbIncubatingAttributes.DB_NAME" dbName - "$DbIncubatingAttributes.DB_OPERATION" operation - "$DbIncubatingAttributes.DB_MONGODB_COLLECTION" collection - } - } - } -} diff --git a/instrumentation/mongo/mongo-common/testing/src/main/java/io/opentelemetry/instrumentation/mongo/testing/AbstractMongoClientTest.java b/instrumentation/mongo/mongo-common/testing/src/main/java/io/opentelemetry/instrumentation/mongo/testing/AbstractMongoClientTest.java new file mode 100644 index 000000000000..bda0b04ae63c --- /dev/null +++ b/instrumentation/mongo/mongo-common/testing/src/main/java/io/opentelemetry/instrumentation/mongo/testing/AbstractMongoClientTest.java @@ -0,0 +1,535 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.mongo.testing; + +import static io.opentelemetry.api.trace.SpanKind.CLIENT; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; + +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.semconv.ServerAttributes; +import io.opentelemetry.semconv.incubating.DbIncubatingAttributes; +import java.net.Socket; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.output.Slf4jLogConsumer; + +@TestInstance(PER_CLASS) +public abstract class AbstractMongoClientTest { + + private static final AtomicInteger collectionIndex = new AtomicInteger(); + + private GenericContainer mongodb; + protected String host; + protected int port; + + @BeforeAll + void setup() { + mongodb = + new GenericContainer<>("mongo:4.0") + .withExposedPorts(27017) + .withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger("mongodb"))); + mongodb.start(); + host = mongodb.getHost(); + port = mongodb.getMappedPort(27017); + } + + @AfterAll + void cleanup() { + if (mongodb != null) { + mongodb.stop(); + } + } + + protected abstract InstrumentationExtension testing(); + + // Different client versions have different APIs to do these operations. If adding a test for a + // new version, refer to existing ones on how to implement these operations. + protected abstract void createCollection(String dbName, String collectionName) + throws InterruptedException; + + protected abstract void createCollectionNoDescription(String dbName, String collectionName) + throws InterruptedException; + + // Tests the fix for + // https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/457 + // TracingCommandListener might get added multiple times if clientOptions are built using existing + // clientOptions or when calling a build method twice. + // This test asserts that duplicate traces are not created in those cases. + protected abstract void createCollectionWithAlreadyBuiltClientOptions( + String dbName, String collectionName); + + protected abstract void createCollectionCallingBuildTwice(String dbName, String collectionName) + throws InterruptedException; + + protected abstract long getCollection(String dbName, String collectionName) throws Exception; + + protected abstract T setupInsert(String dbName, String collectionName) + throws InterruptedException; + + protected abstract long insert(T collection) throws Exception; + + protected abstract T setupUpdate(String dbName, String collectionName) + throws InterruptedException; + + protected abstract long update(T collection) throws Exception; + + protected abstract T setupDelete(String dbName, String collectionName) + throws InterruptedException; + + protected abstract long delete(T collection) throws Exception; + + protected abstract T setupGetMore(String dbName, String collectionName); + + protected abstract void getMore(T collection); + + protected abstract void error(String dbName, String collectionName) throws Throwable; + + protected void ignoreTracesAndClear(int numberOfTraces) { + testing().waitForTraces(numberOfTraces); + testing().clearData(); + } + + @Test + @DisplayName("test port open") + void testPortOpen() { + assertThatNoException().isThrownBy(() -> new Socket(host, port)); + } + + @Test + @DisplayName("test create collection") + void testCreateCollection() throws InterruptedException { + String dbName = "test_db"; + String collectionName = createCollectionName(); + + testing().runWithSpan("parent", () -> createCollection(dbName, collectionName)); + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + mongoSpan( + span, + "create", + collectionName, + dbName, + trace.getSpan(0), + asList( + "{\"create\":\"" + collectionName + "\",\"capped\":\"?\"}", + "{\"create\":\"" + + collectionName + + "\",\"capped\":\"?\",\"$db\":\"?\"}", + "{\"create\":\"" + + collectionName + + "\",\"capped\":\"?\",\"$db\":\"?\",\"$readPreference\":{\"mode\":\"?\"}}", + "{\"create\":\"" + + collectionName + + "\",\"capped\":\"?\",\"$db\":\"?\",\"lsid\":{\"id\":\"?\"}}")))); + } + + @Test + @DisplayName("test create collection no description") + void testCreateCollectionNoDescription() throws InterruptedException { + String dbName = "test_db"; + String collectionName = createCollectionName(); + + testing().runWithSpan("parent", () -> createCollectionNoDescription(dbName, collectionName)); + + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + mongoSpan( + span, + "create", + collectionName, + dbName, + trace.getSpan(0), + asList( + "{\"create\":\"" + collectionName + "\",\"capped\":\"?\"}", + "{\"create\":\"" + + collectionName + + "\",\"capped\":\"?\",\"$db\":\"?\"}", + "{\"create\":\"" + + collectionName + + "\",\"capped\":\"?\",\"$db\":\"?\",\"$readPreference\":{\"mode\":\"?\"}}", + "{\"create\":\"" + + collectionName + + "\",\"capped\":\"?\",\"$db\":\"?\",\"lsid\":{\"id\":\"?\"}}")))); + } + + @Test + @DisplayName("test create collection calling build twice") + void testCreateCollectionCallingBuildTwice() throws InterruptedException { + String dbName = "test_db"; + String collectionName = createCollectionName(); + + testing() + .runWithSpan("parent", () -> createCollectionCallingBuildTwice(dbName, collectionName)); + + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + mongoSpan( + span, + "create", + collectionName, + dbName, + trace.getSpan(0), + asList( + "{\"create\":\"" + collectionName + "\",\"capped\":\"?\"}", + "{\"create\":\"" + + collectionName + + "\",\"capped\":\"?\",\"$db\":\"?\"}", + "{\"create\":\"" + + collectionName + + "\",\"capped\":\"?\",\"$db\":\"?\",\"$readPreference\":{\"mode\":\"?\"}}", + "{\"create\":\"" + + collectionName + + "\",\"capped\":\"?\",\"$db\":\"?\",\"lsid\":{\"id\":\"?\"}}")))); + } + + @Test + @DisplayName("test get collection") + void testGetCollection() throws Exception { + String dbName = "test_db"; + String collectionName = createCollectionName(); + + long count = testing().runWithSpan("parent", () -> getCollection(dbName, collectionName)); + assertThat(count).isEqualTo(0); + + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + mongoSpan( + span, + "count", + collectionName, + dbName, + trace.getSpan(0), + asList( + "{\"count\":\"" + collectionName + "\",\"query\":{}}", + "{\"count\":\"" + collectionName + "\",\"query\":{},\"$db\":\"?\"}", + "{\"count\":\"" + + collectionName + + "\",\"query\":{},\"$db\":\"?\",\"lsid\":{\"id\":\"?\"}}", + "{\"count\":\"" + + collectionName + + "\",\"query\":{},\"$db\":\"?\",\"$readPreference\":{\"mode\":\"?\"}}", + "{\"count\":\"" + + collectionName + + "\",\"$db\":\"?\",\"lsid\":{\"id\":\"?\"}}")))); + } + + @Test + @DisplayName("test insert") + void testInsert() throws Exception { + String dbName = "test_db"; + String collectionName = createCollectionName(); + + T collection = setupInsert(dbName, collectionName); + long count = testing().runWithSpan("parent", () -> insert(collection)); + + assertThat(count).isEqualTo(1); + + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + mongoSpan( + span, + "insert", + collectionName, + dbName, + trace.getSpan(0), + asList( + "{\"insert\":\"" + + collectionName + + "\",\"ordered\":\"?\",\"documents\":[{\"_id\":\"?\",\"password\":\"?\"}]}", + "{\"insert\":\"" + + collectionName + + "\",\"ordered\":\"?\",\"$db\":\"?\",\"documents\":[{\"_id\":\"?\",\"password\":\"?\"}]}", + "{\"insert\":\"" + + collectionName + + "\",\"ordered\":\"?\",\"$db\":\"?\",\"lsid\":{\"id\":\"?\"},\"documents\":[{\"_id\":\"?\",\"password\":\"?\"}]}")), + span -> + mongoSpan( + span, + "count", + collectionName, + dbName, + trace.getSpan(0), + asList( + "{\"count\":\"" + collectionName + "\",\"query\":{}}", + "{\"count\":\"" + collectionName + "\",\"query\":{},\"$db\":\"?\"}", + "{\"count\":\"" + + collectionName + + "\",\"query\":{},\"$db\":\"?\",\"lsid\":{\"id\":\"?\"}}", + "{\"count\":\"" + + collectionName + + "\",\"query\":{},\"$db\":\"?\",\"$readPreference\":{\"mode\":\"?\"}}", + "{\"count\":\"" + + collectionName + + "\",\"$db\":\"?\",\"lsid\":{\"id\":\"?\"}}")))); + } + + @Test + @DisplayName("test update") + void testUpdate() throws Exception { + String dbName = "test_db"; + String collectionName = createCollectionName(); + + T collection = setupUpdate(dbName, collectionName); + long modifiedCount = testing().runWithSpan("parent", () -> update(collection)); + + assertThat(modifiedCount).isEqualTo(1); + + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + mongoSpan( + span, + "update", + collectionName, + dbName, + trace.getSpan(0), + asList( + "{\"update\":\"" + + collectionName + + "\",\"ordered\":\"?\",\"updates\":[{\"q\":{\"password\":\"?\"},\"u\":{\"$set\":{\"password\":\"?\"}}}]}", + "{\"update\":\"" + + collectionName + + "\",\"ordered\":\"?\",\"$db\":\"?\",\"updates\":[{\"q\":{\"password\":\"?\"},\"u\":{\"$set\":{\"password\":\"?\"}}}]}", + "{\"update\":\"" + + collectionName + + "\",\"ordered\":\"?\",\"$db\":\"?\",\"lsid\":{\"id\":\"?\"},\"updates\":[{\"q\":{\"password\":\"?\"},\"u\":{\"$set\":{\"password\":\"?\"}}}]}")), + span -> + mongoSpan( + span, + "count", + collectionName, + dbName, + trace.getSpan(0), + asList( + "{\"count\":\"" + collectionName + "\",\"query\":{}}", + "{\"count\":\"" + collectionName + "\",\"query\":{},\"$db\":\"?\"}", + "{\"count\":\"" + + collectionName + + "\",\"query\":{},\"$db\":\"?\",\"lsid\":{\"id\":\"?\"}}", + "{\"count\":\"" + + collectionName + + "\",\"query\":{},\"$db\":\"?\",\"$readPreference\":{\"mode\":\"?\"}}", + "{\"count\":\"" + + collectionName + + "\",\"$db\":\"?\",\"lsid\":{\"id\":\"?\"}}")))); + } + + @Test + @DisplayName("test delete") + void testDelete() throws Exception { + String dbName = "test_db"; + String collectionName = createCollectionName(); + + T collection = setupDelete(dbName, collectionName); + long deletedCount = testing().runWithSpan("parent", () -> delete(collection)); + + assertThat(deletedCount).isEqualTo(1); + + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + mongoSpan( + span, + "delete", + collectionName, + dbName, + trace.getSpan(0), + asList( + "{\"delete\":\"" + + collectionName + + "\",\"ordered\":\"?\",\"deletes\":[{\"q\":{\"password\":\"?\"},\"limit\":\"?\"}]}", + "{\"delete\":\"" + + collectionName + + "\",\"ordered\":\"?\",\"$db\":\"?\",\"deletes\":[{\"q\":{\"password\":\"?\"},\"limit\":\"?\"}]}", + "{\"delete\":\"" + + collectionName + + "\",\"ordered\":\"?\",\"$db\":\"?\",\"lsid\":{\"id\":\"?\"},\"deletes\":[{\"q\":{\"password\":\"?\"},\"limit\":\"?\"}]}")), + span -> + mongoSpan( + span, + "count", + collectionName, + dbName, + trace.getSpan(0), + asList( + "{\"count\":\"" + collectionName + "\",\"query\":{}}", + "{\"count\":\"" + collectionName + "\",\"query\":{},\"$db\":\"?\"}", + "{\"count\":\"" + + collectionName + + "\",\"query\":{},\"$db\":\"?\",\"lsid\":{\"id\":\"?\"}}", + "{\"count\":\"" + + collectionName + + "\",\"query\":{},\"$db\":\"?\",\"$readPreference\":{\"mode\":\"?\"}}", + "{\"count\":\"" + + collectionName + + "\",\"$db\":\"?\",\"lsid\":{\"id\":\"?\"}}")))); + } + + @Test + @DisplayName("test collection name for getMore command") + void testCollectionNameForGetMoreCommand() { + String dbName = "test_db"; + String collectionName = createCollectionName(); + + T collection = setupGetMore(dbName, collectionName); + testing().runWithSpan("parent", () -> getMore(collection)); + + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + mongoSpan( + span, + "find", + collectionName, + dbName, + trace.getSpan(0), + asList( + "{\"find\":\"" + + collectionName + + "\",\"filter\":{\"_id\":{\"$gte\":\"?\"}},\"batchSize\":\"?\"}", + "{\"find\":\"" + + collectionName + + "\",\"filter\":{\"_id\":{\"$gte\":\"?\"}},\"batchSize\":\"?\",\"$db\":\"?\"}", + "{\"find\":\"" + + collectionName + + "\",\"filter\":{\"_id\":{\"$gte\":\"?\"}},\"batchSize\":\"?\",\"$db\":\"?\",\"$readPreference\":{\"mode\":\"?\"}}", + "{\"find\":\"" + + collectionName + + "\",\"filter\":{\"_id\":{\"$gte\":\"?\"}},\"batchSize\":\"?\",\"$db\":\"?\",\"lsid\":{\"id\":\"?\"}}")), + span -> + mongoSpan( + span, + "getMore", + collectionName, + dbName, + trace.getSpan(0), + asList( + "{\"getMore\":\"?\",\"collection\":\"?\",\"batchSize\":\"?\"}", + "{\"getMore\":\"?\",\"collection\":\"?\",\"batchSize\":\"?\",\"$db\":\"?\"}", + "{\"getMore\":\"?\",\"collection\":\"?\",\"batchSize\":\"?\",\"$db\":\"?\",\"$readPreference\":{\"mode\":\"?\"}}", + "{\"getMore\":\"?\",\"collection\":\"?\",\"batchSize\":\"?\",\"$db\":\"?\",\"lsid\":{\"id\":\"?\"}}")))); + } + + @Test + @DisplayName("test error") + void testError() { + assertThatIllegalArgumentException().isThrownBy(() -> error("test_db", createCollectionName())); + // Unfortunately not caught by our instrumentation. + assertThat(testing().spans()).isEmpty(); + } + + @Test + @DisplayName("test create collection with already built ClientOptions") + void testCreateCollectionWithAlreadyBuiltClientOptions() { + String dbName = "test_db"; + String collectionName = createCollectionName(); + + testing() + .runWithSpan( + "parent", () -> createCollectionWithAlreadyBuiltClientOptions(dbName, collectionName)); + + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + mongoSpan( + span, + "create", + collectionName, + dbName, + trace.getSpan(0), + asList( + "{\"create\":\"" + collectionName + "\",\"capped\":\"?\"}", + "{\"create\":\"" + + collectionName + + "\",\"capped\":\"?\",\"$db\":\"?\",\"$readPreference\":{\"mode\":\"?\"}}", + "{\"create\":\"" + + collectionName + + "\",\"capped\":\"?\",\"$db\":\"?\",\"lsid\":{\"id\":\"?\"}}")))); + } + + protected String createCollectionName() { + return "testCollection-" + collectionIndex.getAndIncrement(); + } + + @SuppressWarnings("deprecation") + // TODO DbIncubatingAttributes.DB_CONNECTION_STRING deprecation + void mongoSpan( + SpanDataAssert span, + String operation, + String collection, + String dbName, + Object parentSpan, + List statements) { + span.hasName(operation + " " + dbName + "." + collection).hasKind(CLIENT); + if (parentSpan == null) { + span.hasNoParent(); + } else { + span.hasParent((SpanData) parentSpan); + } + + span.hasAttributesSatisfyingExactly( + equalTo(ServerAttributes.SERVER_ADDRESS, host), + equalTo(ServerAttributes.SERVER_PORT, port), + satisfies( + DbIncubatingAttributes.DB_STATEMENT, + val -> + val.satisfies( + statement -> assertThat(statements).contains(statement.replaceAll(" ", "")))), + equalTo(DbIncubatingAttributes.DB_SYSTEM, "mongodb"), + equalTo(DbIncubatingAttributes.DB_CONNECTION_STRING, "mongodb://localhost:" + port), + equalTo(DbIncubatingAttributes.DB_NAME, dbName), + equalTo(DbIncubatingAttributes.DB_OPERATION, operation), + equalTo(DbIncubatingAttributes.DB_MONGODB_COLLECTION, collection)); + } +}