-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add MongoDB Atlas implementation (#9290)
Co-authored-by: Luke Thompson <[email protected]>
- Loading branch information
1 parent
04206d9
commit 8921a5a
Showing
5 changed files
with
352 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,7 +3,14 @@ | |
!!! note | ||
This module is INCUBATING. While it is ready for use and operational in the current version of Testcontainers, it is possible that it may receive breaking changes in the future. See [our contributing guidelines](/contributing/#incubating-modules) for more information on our incubating modules policy. | ||
|
||
## Usage example | ||
The MongoDB module provides two Testcontainers for MongoDB unit testing: | ||
|
||
* [MongoDBContainer](#mongodbcontainer) - the core MongoDB database | ||
* [MongoDBAtlasLocalContainer](#mongodbatlaslocalcontainer) - the core MongoDB database combined with MongoDB Atlas Search + Atlas Vector Search | ||
|
||
## MongoDBContainer | ||
|
||
### Usage example | ||
|
||
The following example shows how to create a MongoDBContainer: | ||
|
||
|
@@ -36,6 +43,37 @@ For instance, to initialize a single node replica set on fixed ports via Docker, | |
As we can see, there is a lot of operations to execute and we even haven't touched a non-fixed port approach. | ||
That's where the MongoDBContainer might come in handy. | ||
|
||
## MongoDBAtlasLocalContainer | ||
|
||
### Usage example | ||
|
||
The following example shows how to create a MongoDBAtlasLocalContainer: | ||
|
||
<!--codeinclude--> | ||
[Creating a MongoDB Atlas Local Container](../../../modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBAtlasLocalContainerTest.java) inside_block:creatingAtlasLocalContainer | ||
<!--/codeinclude--> | ||
|
||
And how to start it: | ||
|
||
<!--codeinclude--> | ||
[Start the Container](../../../modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBAtlasLocalContainerTest.java) inside_block:startingAtlasLocalContainer | ||
<!--/codeinclude--> | ||
|
||
The connection string provided by the MongoDBAtlasLocalContainer's getConnectionString() method includes the dynamically allocated port: | ||
|
||
<!--codeinclude--> | ||
[Get the Connection String](../../../modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBAtlasLocalContainerTest.java) inside_block:getConnectionStringAtlasLocalContainer | ||
<!--/codeinclude--> | ||
|
||
e.g. `mongodb://localhost:12345/?directConnection=true` | ||
|
||
### References | ||
MongoDB Atlas Local combines the MongoDB database engine with MongoT, a sidecar process for advanced searching capabilities built by MongoDB and powered by [Apache Lucene](https://lucene.apache.org/). | ||
|
||
The container (mongodb/mongodb-atlas-local) documentation can be found [here](https://www.mongodb.com/docs/atlas/cli/current/atlas-cli-deploy-docker/). | ||
|
||
General information about Atlas Search can be found [here](https://www.mongodb.com/docs/atlas/atlas-search/). | ||
|
||
## Adding this module to your project dependencies | ||
|
||
Add the following dependency to your `pom.xml`/`build.gradle` file: | ||
|
@@ -55,7 +93,7 @@ Add the following dependency to your `pom.xml`/`build.gradle` file: | |
``` | ||
|
||
!!! hint | ||
Adding this Testcontainers library JAR will not automatically add a database driver JAR to your project. You should ensure that your project also has a suitable database driver as a dependency | ||
Adding this Testcontainers library JAR will not automatically add a database driver JAR to your project. You should ensure that your project also has a suitable database driver as a dependency | ||
|
||
#### Copyright | ||
Copyright (c) 2019 Konstantin Silaev <[email protected]> |
38 changes: 38 additions & 0 deletions
38
modules/mongodb/src/main/java/org/testcontainers/mongodb/MongoDBAtlasLocalContainer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package org.testcontainers.mongodb; | ||
|
||
import org.testcontainers.containers.GenericContainer; | ||
import org.testcontainers.containers.wait.strategy.Wait; | ||
import org.testcontainers.utility.DockerImageName; | ||
|
||
/** | ||
* Testcontainers implementation for MongoDB Atlas. | ||
* <p> | ||
* Supported images: {@code mongodb/mongodb-atlas-local} | ||
* <p> | ||
* Exposed ports: 27017 | ||
*/ | ||
public class MongoDBAtlasLocalContainer extends GenericContainer<MongoDBAtlasLocalContainer> { | ||
|
||
private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("mongodb/mongodb-atlas-local"); | ||
|
||
private static final int MONGODB_INTERNAL_PORT = 27017; | ||
|
||
public MongoDBAtlasLocalContainer(final String dockerImageName) { | ||
this(DockerImageName.parse(dockerImageName)); | ||
} | ||
|
||
public MongoDBAtlasLocalContainer(final DockerImageName dockerImageName) { | ||
super(dockerImageName); | ||
dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); | ||
|
||
withExposedPorts(MONGODB_INTERNAL_PORT); | ||
waitingFor(Wait.forSuccessfulCommand("runner healthcheck")); | ||
} | ||
|
||
/** | ||
* Get the connection string to MongoDB. | ||
*/ | ||
public String getConnectionString() { | ||
return String.format("mongodb://%s:%d/?directConnection=true", getHost(), getMappedPort(MONGODB_INTERNAL_PORT)); | ||
} | ||
} |
177 changes: 177 additions & 0 deletions
177
modules/mongodb/src/test/java/org/testcontainers/mongodb/AtlasLocalDataAccess.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
package org.testcontainers.mongodb; | ||
|
||
import com.mongodb.ConnectionString; | ||
import com.mongodb.MongoClientSettings; | ||
import com.mongodb.client.ListSearchIndexesIterable; | ||
import com.mongodb.client.MongoClient; | ||
import com.mongodb.client.MongoClients; | ||
import com.mongodb.client.MongoCollection; | ||
import com.mongodb.client.MongoDatabase; | ||
import com.mongodb.client.model.Aggregates; | ||
import com.mongodb.client.model.search.SearchOperator; | ||
import com.mongodb.client.model.search.SearchOptions; | ||
import com.mongodb.client.model.search.SearchPath; | ||
import org.bson.BsonDocument; | ||
import org.bson.Document; | ||
import org.bson.codecs.configuration.CodecRegistries; | ||
import org.bson.codecs.configuration.CodecRegistry; | ||
import org.bson.codecs.pojo.PojoCodecProvider; | ||
import org.bson.conversions.Bson; | ||
import org.bson.json.JsonWriterSettings; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import java.io.IOException; | ||
import java.net.URISyntaxException; | ||
import java.nio.charset.StandardCharsets; | ||
import java.nio.file.Files; | ||
import java.nio.file.Paths; | ||
import java.time.Instant; | ||
import java.time.temporal.ChronoUnit; | ||
import java.util.Collections; | ||
import java.util.concurrent.TimeUnit; | ||
|
||
import static org.awaitility.Awaitility.await; | ||
|
||
public class AtlasLocalDataAccess implements AutoCloseable { | ||
|
||
private static final Logger log = LoggerFactory.getLogger(AtlasLocalDataAccess.class); | ||
|
||
private final MongoClient mongoClient; | ||
|
||
private final MongoDatabase testDB; | ||
|
||
private final MongoCollection<TestData> testCollection; | ||
|
||
private final String collectionName; | ||
|
||
public AtlasLocalDataAccess(String connectionString, String databaseName, String collectionName) { | ||
this.collectionName = collectionName; | ||
log.info("DataAccess connecting to {}", connectionString); | ||
|
||
CodecRegistry pojoCodecRegistry = CodecRegistries.fromProviders( | ||
PojoCodecProvider.builder().automatic(true).build() | ||
); | ||
CodecRegistry codecRegistry = CodecRegistries.fromRegistries( | ||
MongoClientSettings.getDefaultCodecRegistry(), | ||
pojoCodecRegistry | ||
); | ||
MongoClientSettings clientSettings = MongoClientSettings | ||
.builder() | ||
.applyConnectionString(new ConnectionString(connectionString)) | ||
.codecRegistry(codecRegistry) | ||
.build(); | ||
mongoClient = MongoClients.create(clientSettings); | ||
testDB = mongoClient.getDatabase(databaseName); | ||
testCollection = testDB.getCollection(collectionName, TestData.class); | ||
} | ||
|
||
@Override | ||
public void close() { | ||
mongoClient.close(); | ||
} | ||
|
||
public void initAtlasSearchIndex() throws URISyntaxException, IOException, InterruptedException { | ||
//Create the collection (if it doesn't exist). Required because unlike other database operations, createSearchIndex will fail if the collection doesn't exist yet | ||
testDB.createCollection(collectionName); | ||
|
||
//Read the atlas search index JSON from a resource file | ||
String atlasSearchIndexJson = new String( | ||
Files.readAllBytes(Paths.get(getClass().getResource("/atlas-local-index.json").toURI())), | ||
StandardCharsets.UTF_8 | ||
); | ||
log.info( | ||
"Creating Atlas Search index AtlasSearchIndex on collection {}:\n{}", | ||
collectionName, | ||
atlasSearchIndexJson | ||
); | ||
testCollection.createSearchIndex("AtlasSearchIndex", BsonDocument.parse(atlasSearchIndexJson)); | ||
|
||
//wait for the atlas search index to be ready | ||
Instant start = Instant.now(); | ||
await() | ||
.atMost(5, TimeUnit.SECONDS) | ||
.pollInterval(10, TimeUnit.MILLISECONDS) | ||
.pollInSameThread() | ||
.until(this::getIndexStatus, "READY"::equalsIgnoreCase); | ||
|
||
log.info( | ||
"Atlas Search index AtlasSearchIndex on collection {} is ready (took {} milliseconds) to create.", | ||
collectionName, | ||
start.until(Instant.now(), ChronoUnit.MILLIS) | ||
); | ||
} | ||
|
||
private String getIndexStatus() { | ||
ListSearchIndexesIterable<Document> searchIndexes = testCollection.listSearchIndexes(); | ||
for (Document searchIndex : searchIndexes) { | ||
if (searchIndex.get("name").equals("AtlasSearchIndex")) { | ||
return searchIndex.getString("status"); | ||
} | ||
} | ||
return null; | ||
} | ||
|
||
public void insertData(TestData data) { | ||
log.info("Inserting document {}", data); | ||
testCollection.insertOne(data); | ||
} | ||
|
||
public TestData findAtlasSearch(String test) { | ||
Bson searchClause = Aggregates.search( | ||
SearchOperator.of(SearchOperator.text(SearchPath.fieldPath("test"), test).fuzzy()), | ||
SearchOptions.searchOptions().index("AtlasSearchIndex") | ||
); | ||
log.trace( | ||
"Searching for document using Atlas Search:\n{}", | ||
searchClause.toBsonDocument().toJson(JsonWriterSettings.builder().indent(true).build()) | ||
); | ||
return testCollection.aggregate(Collections.singletonList(searchClause)).first(); | ||
} | ||
|
||
public static class TestData { | ||
|
||
String test; | ||
|
||
int test2; | ||
|
||
boolean test3; | ||
|
||
public TestData() {} | ||
|
||
public TestData(String test, int test2, boolean test3) { | ||
this.test = test; | ||
this.test2 = test2; | ||
this.test3 = test3; | ||
} | ||
|
||
public String getTest() { | ||
return test; | ||
} | ||
|
||
public void setTest(String test) { | ||
this.test = test; | ||
} | ||
|
||
public int getTest2() { | ||
return test2; | ||
} | ||
|
||
public void setTest2(int test2) { | ||
this.test2 = test2; | ||
} | ||
|
||
public boolean isTest3() { | ||
return test3; | ||
} | ||
|
||
public void setTest3(boolean test3) { | ||
this.test3 = test3; | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return "TestData{" + "test='" + test + '\'' + ", test2=" + test2 + ", test3=" + test3 + '}'; | ||
} | ||
} | ||
} |
79 changes: 79 additions & 0 deletions
79
modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBAtlasLocalContainerTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package org.testcontainers.mongodb; | ||
|
||
import org.junit.Test; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import java.time.Instant; | ||
import java.time.temporal.ChronoUnit; | ||
import java.util.Objects; | ||
import java.util.concurrent.TimeUnit; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static org.awaitility.Awaitility.await; | ||
|
||
public class MongoDBAtlasLocalContainerTest { | ||
|
||
private static final Logger log = LoggerFactory.getLogger(MongoDBAtlasLocalContainerTest.class); | ||
|
||
@Test | ||
public void getConnectionString() { | ||
try ( | ||
MongoDBAtlasLocalContainer container = new MongoDBAtlasLocalContainer("mongodb/mongodb-atlas-local:7.0.9") | ||
) { | ||
container.start(); | ||
String connectionString = container.getConnectionString(); | ||
assertThat(connectionString).isNotNull(); | ||
assertThat(connectionString).startsWith("mongodb://"); | ||
assertThat(connectionString) | ||
.isEqualTo( | ||
String.format( | ||
"mongodb://%s:%d/?directConnection=true", | ||
container.getHost(), | ||
container.getFirstMappedPort() | ||
) | ||
); | ||
} | ||
} | ||
|
||
@Test | ||
public void createAtlasIndexAndSearchIt() throws Exception { | ||
try ( | ||
// creatingAtlasLocalContainer { | ||
MongoDBAtlasLocalContainer atlasLocalContainer = new MongoDBAtlasLocalContainer( | ||
"mongodb/mongodb-atlas-local:7.0.9" | ||
); | ||
// } | ||
) { | ||
// startingAtlasLocalContainer { | ||
atlasLocalContainer.start(); | ||
// } | ||
|
||
// getConnectionStringAtlasLocalContainer { | ||
String connectionString = atlasLocalContainer.getConnectionString(); | ||
// } | ||
|
||
try ( | ||
AtlasLocalDataAccess atlasLocalDataAccess = new AtlasLocalDataAccess(connectionString, "test", "test") | ||
) { | ||
atlasLocalDataAccess.initAtlasSearchIndex(); | ||
|
||
atlasLocalDataAccess.insertData(new AtlasLocalDataAccess.TestData("tests", 123, true)); | ||
|
||
Instant start = Instant.now(); | ||
log.info( | ||
"Waiting for Atlas Search to index the data by polling atlas search query (Atlas Search is eventually consistent)" | ||
); | ||
await() | ||
.atMost(5, TimeUnit.SECONDS) | ||
.pollInterval(10, TimeUnit.MILLISECONDS) | ||
.pollInSameThread() | ||
.until(() -> atlasLocalDataAccess.findAtlasSearch("test"), Objects::nonNull); | ||
log.info( | ||
"Atlas Search indexed the new data and was searchable after {}ms.", | ||
start.until(Instant.now(), ChronoUnit.MILLIS) | ||
); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
{ | ||
"mappings": { | ||
"dynamic": false, | ||
"fields": { | ||
"test": { | ||
"type": "string" | ||
}, | ||
"test2": { | ||
"type": "number", | ||
"representation": "int64", | ||
"indexDoubles": false | ||
}, | ||
"test3": { | ||
"type": "boolean" | ||
} | ||
} | ||
} | ||
} |