-
-
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 <endeavour9@gmail.com>
- 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
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" | ||
} | ||
} | ||
} | ||
} |