Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions embedded-mongodb/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
* `embedded.mongodb.username`
* `embedded.mongodb.password`
* `embedded.mongodb.database` `(default is test)`
* `embedded.mongodb.replica-set-name` Default is to run in standalone
* `embedded.toxiproxy.proxies.mongodb.enabled` Enables both creation of the container with ToxiProxy TCP proxy and a proxy to the `embedded-mongodb` container.


Expand All @@ -33,6 +34,7 @@
* `embedded.mongodb.username`
* `embedded.mongodb.password`
* `embedded.mongodb.database`
* `embedded.mongodb.replica-set-name`
* `embedded.mongodb.toxiproxy.host`
* `embedded.mongodb.toxiproxy.port`
* `embedded.mongodb.networkAlias`
Expand All @@ -48,3 +50,5 @@ To auto-configure `spring-data-mongodb` use these properties in your test `appli
----
spring.data.mongodb.uri=mongodb://${embedded.mongodb.host}:${embedded.mongodb.port}/${embedded.mongodb.database}
----

For replicaset, add additional mongodb properties: `?rs=${embedded.mongodb.replica-set-name}&directConnection=true&authSource=admin`
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,22 @@
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.io.ClassPathResource;
import org.testcontainers.containers.BindMode;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.ToxiproxyContainer;
import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy;
import org.testcontainers.images.builder.Transferable;
import org.testcontainers.shaded.org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.LinkedHashMap;
import java.util.Optional;

Expand All @@ -44,10 +48,10 @@ public class EmbeddedMongodbBootstrapConfiguration {
@Bean
@ConditionalOnToxiProxyEnabled(module = "mongodb")
ToxiproxyClientProxy mongodbContainerProxy(ToxiproxyClient toxiproxyClient,
ToxiproxyContainer toxiproxyContainer,
@Qualifier(BEAN_NAME_EMBEDDED_MONGODB) GenericContainer<?> mongodb,
MongodbProperties properties,
ConfigurableEnvironment environment) {
ToxiproxyContainer toxiproxyContainer,
@Qualifier(BEAN_NAME_EMBEDDED_MONGODB) GenericContainer<?> mongodb,
MongodbProperties properties,
ConfigurableEnvironment environment) {
ToxiproxyClientProxy proxy = ToxiproxyHelper.createProxy(
toxiproxyClient,
toxiproxyContainer,
Expand All @@ -64,16 +68,36 @@ ToxiproxyClientProxy mongodbContainerProxy(ToxiproxyClient toxiproxyClient,
@Bean(value = BEAN_NAME_EMBEDDED_MONGODB, destroyMethod = "stop")
public GenericContainer<?> mongodb(ConfigurableEnvironment environment,
MongodbProperties properties,
MongodbStatusCheck mongodbStatusCheck,
Optional<Network> network) {
GenericContainer<?> mongodb =
new GenericContainer<>(ContainerUtils.getDockerImageName(properties))
.withEnv("MONGO_INITDB_ROOT_USERNAME", properties.getUsername())
.withEnv("MONGO_INITDB_ROOT_PASSWORD", properties.getPassword())
.withEnv("MONGO_INITDB_DATABASE", properties.getDatabase())
.withExposedPorts(properties.getPort())
.waitingFor(new LogMessageWaitStrategy().withRegEx(".*mongod startup complete.*"))
.withNetworkAliases(MONGODB_NETWORK_ALIAS);
Optional<Network> network) throws IOException, InterruptedException {

GenericContainer<?> mongodb;
if (StringUtils.isBlank(properties.getReplicaSetName())) {
mongodb = new GenericContainer<>(ContainerUtils.getDockerImageName(properties))
.withEnv("MONGO_INITDB_ROOT_USERNAME", properties.getUsername())
.withEnv("MONGO_INITDB_ROOT_PASSWORD", properties.getPassword())
.withEnv("MONGO_INITDB_DATABASE", properties.getDatabase())
.withExposedPorts(properties.getPort())
.waitingFor(new MongodbWaitStrategy(properties))
.withNetworkAliases(MONGODB_NETWORK_ALIAS);
} else {
mongodb = new GenericContainer<>(ContainerUtils.getDockerImageName(properties))
.withCommand("-f", "/etc/mongod.conf")
.withClasspathResourceMapping("/mongod/gen-keyfile.sh", "/docker-entrypoint-initdb.d/gen-keyfile.sh", BindMode.READ_ONLY)
.withCopyToContainer(
Transferable.of(
new ClassPathResource("/mongod/mongod.conf")
.getContentAsString(Charset.defaultCharset())
.replace("${replica-set-name}", properties.getReplicaSetName())
)
, "/etc/mongod.conf")
.withEnv("MONGO_INITDB_ROOT_USERNAME", properties.getUsername())
.withEnv("MONGO_INITDB_ROOT_PASSWORD", properties.getPassword())
.withEnv("MONGO_INITDB_DATABASE", properties.getDatabase())
.withEnv("MONGO_INITDB_REPL_SET_HOST", properties.getHost())
.withExposedPorts(properties.getPort())
.waitingFor(new MongodbWaitStrategy(properties))
.withNetworkAliases(MONGODB_NETWORK_ALIAS);
}

network.ifPresent(mongodb::withNetwork);

Expand All @@ -82,12 +106,6 @@ public GenericContainer<?> mongodb(ConfigurableEnvironment environment,
return mongodb;
}

@Bean
@ConditionalOnMissingBean
MongodbStatusCheck mongodbStartupCheckStrategy(MongodbProperties properties) {
return new MongodbStatusCheck(properties);
}

private void registerMongodbEnvironment(GenericContainer<?> mongodb, ConfigurableEnvironment environment, MongodbProperties properties) {
Integer mappedPort = mongodb.getMappedPort(properties.getPort());
String host = mongodb.getHost();
Expand All @@ -100,8 +118,13 @@ private void registerMongodbEnvironment(GenericContainer<?> mongodb, Configurabl
map.put("embedded.mongodb.database", properties.getDatabase());
map.put("embedded.mongodb.networkAlias", MONGODB_NETWORK_ALIAS);
map.put("embedded.mongodb.internalPort", properties.getPort());
if (StringUtils.isNotBlank(properties.getReplicaSetName())) {
map.put("embedded.mongodb.replica-set-name", properties.getReplicaSetName());
}

log.info("Started mongodb. Connection Details: {}, Connection URI: mongodb://{}:{}/{}", map, host, mappedPort, properties.getDatabase());
log.info("Started mongodb. Connection Details: {}, Connection URI: mongodb://{}:{}/{}{}", map, host, mappedPort, properties.getDatabase(), Optional.ofNullable(
properties.getReplicaSetName()).map("?directConnection=true&authSource=admin&rs="::concat).orElse("")
);

MapPropertySource propertySource = new MapPropertySource("embeddedMongoInfo", map);
environment.getPropertySources().addFirst(propertySource);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ public class MongodbProperties extends CommonContainerProperties {
private String username;
private String password;
private String database = "test";
private String[] checkCommand = new String[]{"mongosh", "admin", "--eval", "\"db['system.version'].find()\""};
/**
* If provided, mongodb will be started as a replica set with the given name. Default: null (standalone mode).
*/
private String replicaSetName;

public MongodbProperties() {
this.setCapabilities(List.of(Capability.ALL));
Expand All @@ -36,6 +39,6 @@ public String getDefaultDockerImage() {
// Please don`t remove this comment.
// renovate: datasource=docker
// https://hub.docker.com/_/mongo
return "mongodb/mongodb-community-server:8.0.10-ubuntu2204";
return "mongodb/mongodb-community-server:8.2.2-ubuntu2204";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

update also version in other places metron2@4d635ab

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do not forget add it to readme

}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.playtika.testcontainer.mongodb;

import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.testcontainers.containers.wait.strategy.AbstractWaitStrategy;
import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy;

@Slf4j
@AllArgsConstructor
public class MongodbWaitStrategy extends AbstractWaitStrategy {

private final MongodbProperties properties;

@Override
@SneakyThrows
protected void waitUntilReady() {
log.info("Waiting for mongodb to start");
new LogMessageWaitStrategy().withRegEx(".*Waiting for connections.*").waitUntilReady(waitStrategyTarget);
if (properties.getReplicaSetName() != null) {
// The docker container will restart mongod and initialize the replicaset, so we just have to wait for that to finish now.
log.info("Waiting for mongodb to become primary.");

LogMessageWaitStrategy logMessageWaitStrategy = new LogMessageWaitStrategy().withRegEx(".*database writes are now permitted.*");
logMessageWaitStrategy.waitUntilReady(waitStrategyTarget);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
{
"name": "embedded.mongodb.dockerImage",
"type": "java.lang.String",
"defaultValue": "mongodb/mongodb-community-server:7.0.12-ubuntu2204"
"defaultValue": "mongodb/mongodb-community-server:8.2.2-ubuntu2204"
},
{
"name": "embedded.mongodb.host",
Expand All @@ -35,4 +35,4 @@
"type": "java.lang.String"
}
]
}
}
7 changes: 7 additions & 0 deletions embedded-mongodb/src/main/resources/mongod/gen-keyfile.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env bash

# Mongo requires a keyfile for replicasets, but the docker image does not generate one.
# The image does let you run this arbitrary script after db initialization.

echo "testcontainers" > /data/configdb/mongod.keyfile
chmod 400 /data/configdb/mongod.keyfile
17 changes: 17 additions & 0 deletions embedded-mongodb/src/main/resources/mongod/mongod.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@

storage:
dbPath: /data/db

processManagement:
timeZoneInfo: /usr/share/zoneinfo

net:
port: 27017
bindIp: 0.0.0.0

security:
keyFile: /data/configdb/mongod.keyfile
authorization: enabled

replication:
replSetName: ${replica-set-name}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.playtika.testcontainer.mongodb;


import lombok.Value;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.MongoTemplate;

import java.time.Instant;
import java.util.UUID;

import static org.assertj.core.api.Assertions.assertThat;

@Slf4j
@SpringBootTest(
properties = {
"embedded.mongodb.username=root",
"embedded.mongodb.password=letmein",
"embedded.mongodb.replica-set-name=rs0",
"spring.data.mongodb.uri=mongodb://${embedded.mongodb.username}:${embedded.mongodb.password}@${embedded.mongodb.host}:${embedded.mongodb.port}/${embedded.mongodb.database}?replicaSet=${embedded.mongodb.replica-set-name}&directConnection=true&authSource=admin"
}
, classes = EmbeddedMongodbBootstrapReplicaSetConfigurationTest.TestConfiguration.class
)
public class EmbeddedMongodbBootstrapReplicaSetConfigurationTest {

@Autowired
MongoTemplate mongoTemplate;

@Autowired
ConfigurableEnvironment environment;


@Test
public void shouldSaveAndGet() {
String someId = UUID.randomUUID().toString();
Foo foo = new Foo(someId, "foo", Instant.parse("2019-09-26T07:57:12.801Z"), -42L);
mongoTemplate.save(foo);

assertThat(mongoTemplate.findById(someId, Foo.class)).isEqualTo(foo);
}

@Test
public void propertiesAreAvailable() {
assertThat(environment.getProperty("embedded.mongodb.port")).isNotEmpty();
assertThat(environment.getProperty("embedded.mongodb.host")).isNotEmpty();
assertThat(environment.getProperty("embedded.mongodb.username")).isNotEmpty();
assertThat(environment.getProperty("embedded.mongodb.password")).isNotEmpty();
assertThat(environment.getProperty("embedded.mongodb.database")).isNotEmpty();
assertThat(environment.getProperty("embedded.mongodb.replica-set-name")).isNotEmpty();
Comment on lines +50 to +55
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do not use environment. add fields with spring Value annotation

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was following the convention of the other test, do you want me to change both of them? I pretty much copied that one and added this line for the replicaset mode.

}

@Value
static class Foo {
@Id
String someId;
String someString;
Instant someTimestamp;
Long someNumber;
}

@EnableAutoConfiguration
@Configuration
static class TestConfiguration {
}
}