Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add MongoDB replica set support #1196

Merged
9 changes: 9 additions & 0 deletions docs/modules/mongodb.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,12 @@ public sealed class MongoDbContainerTest : IAsyncLifetime
```

To execute the tests, use the command `dotnet test` from a terminal.

## MongoDb Replica Set

By default, MongoDB runs as a standalone instance. If your tests require a MongoDB replica set, use the code below which will initialise it as a single node replica set:

```csharp
private readonly MongoDbContainer _mongoDbContainer =
new MongoDbBuilder().WithReplicaSet().Build();
```
23 changes: 23 additions & 0 deletions src/Testcontainers.MongoDb/MongoDbBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@ public MongoDbBuilder WithPassword(string password)
.WithEnvironment("MONGO_INITDB_ROOT_PASSWORD", initDbRootPassword);
}

public MongoDbBuilder WithReplicaSet(string replicaSetName = "rs0")
{
var initReplicaSetName = replicaSetName ?? "rs0";

return Merge(DockerResourceConfiguration, new MongoDbConfiguration(replicaSetName: initReplicaSetName));
}

/// <inheritdoc />
public override MongoDbContainer Build()
{
Expand All @@ -69,6 +76,22 @@ public override MongoDbContainer Build()
// provided, the log message "Waiting for connections" appears twice.
// If the user does not provide a custom waiting strategy, append the default MongoDb waiting strategy.
var mongoDbBuilder = DockerResourceConfiguration.WaitStrategies.Count() > 1 ? this : WithWaitStrategy(Wait.ForUnixContainer().AddCustomWaitStrategy(new WaitUntil(DockerResourceConfiguration)));

if (!string.IsNullOrEmpty(DockerResourceConfiguration.ReplicaSetName))
{
mongoDbBuilder = mongoDbBuilder
.WithCommand(DockerResourceConfiguration.Command.Concat(["--replSet", DockerResourceConfiguration.ReplicaSetName, "--keyFile", "/tmp/keyfile", "--bind_ip_all"]).ToArray())
.WithResourceMapping(Encoding.Default.GetBytes("""
#!/bin/bash
openssl rand -base64 32 > "/tmp/keyfile"
chmod 600 /tmp/keyfile
chown 999:999 /tmp/keyfile
""".Replace("\r", "")), "/docker-entrypoint-initdb.d/01-init-keyfile.sh", UnixFileModes.OtherRead | UnixFileModes.OtherExecute)
.WithWaitStrategy(Wait.ForUnixContainer().UntilCommandIsCompleted(
"mongosh -u $MONGO_INITDB_ROOT_USERNAME -p $MONGO_INITDB_ROOT_PASSWORD --quiet --eval " +
$"\"try {{ rs.status().ok }} catch (e) {{ rs.initiate({{'_id':'{DockerResourceConfiguration.ReplicaSetName}', members: [{{'_id':1, 'host':'127.0.0.1:27017'}}]}}).ok }}\""));
HofmeisterAn marked this conversation as resolved.
Show resolved Hide resolved
}

return new MongoDbContainer(mongoDbBuilder.DockerResourceConfiguration);
}

Expand Down
11 changes: 10 additions & 1 deletion src/Testcontainers.MongoDb/MongoDbConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ public sealed class MongoDbConfiguration : ContainerConfiguration
/// <param name="password">The MongoDb password.</param>
public MongoDbConfiguration(
string username = null,
string password = null)
string password = null,
string replicaSetName = null)
{
Username = username;
Password = password;
ReplicaSetName = replicaSetName;
}

/// <summary>
Expand Down Expand Up @@ -57,6 +59,7 @@ public MongoDbConfiguration(MongoDbConfiguration oldValue, MongoDbConfiguration
{
Username = BuildConfiguration.Combine(oldValue.Username, newValue.Username);
Password = BuildConfiguration.Combine(oldValue.Password, newValue.Password);
ReplicaSetName = BuildConfiguration.Combine(oldValue.ReplicaSetName, newValue.ReplicaSetName);
}

/// <summary>
Expand All @@ -68,4 +71,10 @@ public MongoDbConfiguration(MongoDbConfiguration oldValue, MongoDbConfiguration
/// Gets the MongoDb password.
/// </summary>
public string Password { get; }

/// <summary>
/// Name of the replica set. If specified, the container will be started as a single node replica set.
/// </summary>
/// <example>rs0</example>
public string ReplicaSetName { get; }
}
49 changes: 47 additions & 2 deletions tests/Testcontainers.MongoDb.Tests/MongoDbContainerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ namespace Testcontainers.MongoDb;
public abstract class MongoDbContainerTest : IAsyncLifetime
{
private readonly MongoDbContainer _mongoDbContainer;
private readonly bool _isReplicaSet;

private MongoDbContainerTest(MongoDbContainer mongoDbContainer)
private MongoDbContainerTest(MongoDbContainer mongoDbContainer, bool isReplicaSet = false)
{
_mongoDbContainer = mongoDbContainer;
_isReplicaSet = isReplicaSet;
}

public Task InitializeAsync()
Expand Down Expand Up @@ -49,6 +51,31 @@ public async Task ExecScriptReturnsSuccessful()
Assert.Empty(execResult.Stderr);
}

[Fact]
[Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
public async Task ReplicaSetStatus()
{
// Given
const string scriptContent = "rs.status().ok;";

// When
var execResult = await _mongoDbContainer.ExecScriptAsync(scriptContent)
.ConfigureAwait(true);

// Then
if (_isReplicaSet)
{
Assert.True(0L.Equals(execResult.ExitCode), execResult.Stderr);
Assert.Empty(execResult.Stderr);
Assert.Equal("true\n", execResult.Stdout);
}
else
{
Assert.Equal(1L, execResult.ExitCode);
Assert.Equal("MongoServerError: not running with --replSet\n", execResult.Stderr);
}
}

[UsedImplicitly]
public sealed class MongoDbDefaultConfiguration : MongoDbContainerTest
{
Expand Down Expand Up @@ -80,7 +107,25 @@ public MongoDbV5Configuration()
public sealed class MongoDbV4Configuration : MongoDbContainerTest
{
public MongoDbV4Configuration()
: base(new MongoDbBuilder().WithImage("mongo:4.4").Build())
: base(new MongoDbBuilder().WithImage("mongo:4.4").Build(), isReplicaSet: true) // Replica set status returns ok in MongoDB 4.4 without initialising it
{
}
}

[UsedImplicitly]
public sealed class MongoDbReplicaSetDefaultConfiguration : MongoDbContainerTest
{
public MongoDbReplicaSetDefaultConfiguration()
: base(new MongoDbBuilder().WithReplicaSet().Build(), isReplicaSet: true)
{
}
}

[UsedImplicitly]
public sealed class MongoDbNamedReplicaSetConfiguration : MongoDbContainerTest
{
public MongoDbNamedReplicaSetConfiguration()
: base(new MongoDbBuilder().WithReplicaSet("rscustom").Build(), isReplicaSet: true)
{
}
}
Expand Down
Loading