Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
28 changes: 27 additions & 1 deletion src/Testcontainers.MongoDb/MongoDbBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ private static async Task InitiateReplicaSetAsync(MongoDbContainer container, Mo
return;
}

var readiness = new WaitIndicateReadiness(configuration);
var readiness = new WaitReplicationEnabled();

// This is a simple workaround to use the default options, which can be configured
// with custom configurations as needed.
Expand Down Expand Up @@ -227,6 +227,32 @@ await WaitStrategy.WaitUntilAsync(initiate, options.Interval, options.Timeout, o
}

/// <inheritdoc cref="IWaitUntil" />
/// <inheritdoc cref="IWaitUntil" />
private sealed class WaitReplicationEnabled : IWaitUntil
{
// rs.status() only answers once the final mongod is serving. The official image forks a
// temporary mongod during first-time initialization to create the root user, and that
// process is not started with --replSet, so it never reports NotYetInitialized. This
// preserves the handover guarantee from #1636 without counting log messages, whose
// number depends on log content the module does not control: #1732.
private const string ScriptContent = "try{rs.status();quit(0);}catch(e){quit(e.codeName===\"NotYetInitialized\"?0:1);}";

/// <inheritdoc />
public Task<bool> UntilAsync(IContainer container)
{
return UntilAsync(container as MongoDbContainer);
}

/// <inheritdoc cref="IWaitUntil.UntilAsync" />
private static async Task<bool> UntilAsync(MongoDbContainer container)
{
var execResult = await container.ExecScriptAsync(ScriptContent)
.ConfigureAwait(false);

return 0L.Equals(execResult.ExitCode);
}
}

private sealed class WaitIndicateReadiness : IWaitUntil
{
private static readonly string[] LineEndings = { "\r\n", "\n" };
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System.Text;
using System.Threading;
using DotNet.Testcontainers.Configurations;

namespace Testcontainers.MongoDb;

/// <summary>
/// The readiness check that runs before the replica set is initiated counts occurrences of a log
/// message and compares that count for equality. Any other log line carrying the same text pushes
/// the count past the expected value, and it can then never match
/// (https://github.com/testcontainers/testcontainers-dotnet/issues/1732).
/// </summary>
public sealed class MongoDbReplicaSetReadinessTest : IAsyncLifetime
{
private const string ExtraMarkerScriptFilePath = "/docker-entrypoint-initdb.d/00-extra-marker.sh";

private readonly MongoDbContainer _mongoDbContainer = new MongoDbBuilder(TestSession.GetImageFromDockerfile())
.WithReplicaSet()
.WithResourceMapping(
Encoding.Default.GetBytes("#!/bin/bash\necho 'Waiting for connections'\necho 'Waiting for connections'\n"),
ExtraMarkerScriptFilePath,
fileMode: Unix.FileMode755)
.Build();

public ValueTask InitializeAsync()
{
return ValueTask.CompletedTask;
}

public async ValueTask DisposeAsync()
{
await _mongoDbContainer.DisposeAsync()
.ConfigureAwait(false);

GC.SuppressFinalize(this);
}

[Fact]
[Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
public async Task StartsWhenTheLogContainsAdditionalReadinessMessages()
{
// Given
// The default wait strategy timeout is one hour, so bound the wait to keep a regression
// from stalling the test run instead of failing it.
using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(3));

// When
await _mongoDbContainer.StartAsync(cts.Token)
.ConfigureAwait(true);

// Then
const string scriptContent = "rs.status().ok;";

var execResult = await _mongoDbContainer.ExecScriptAsync(scriptContent, cts.Token)
.ConfigureAwait(true);

Assert.True(0L.Equals(execResult.ExitCode), execResult.Stderr);
}
}
Loading