testcontainers / testcontainers-dotnet

A library to support tests with throwaway instances of Docker containers for all compatible .NET Standard versions.
https://dotnet.testcontainers.org
MIT License
3.84k stars 278 forks source link

Unable to create database with SDK in CosmosDb testcontainer #1207

Closed diegosasw closed 5 months ago

diegosasw commented 5 months ago

Testcontainers version

3.9.0

Using the latest Testcontainers version?

Yes

Host OS

Windows

Host arch

x64

.NET version

8.0.302

Docker version

Docker version 26.1.4, build 5650f9b

Docker info

Client:
 Version:    26.1.4
 Context:    desktop-linux
 Debug Mode: false
 Plugins:
  buildx: Docker Buildx (Docker Inc.)
    Version:  v0.14.1-desktop.1
    Path:     C:\Program Files\Docker\cli-plugins\docker-buildx.exe
  compose: Docker Compose (Docker Inc.)
    Version:  v2.27.1-desktop.1
    Path:     C:\Program Files\Docker\cli-plugins\docker-compose.exe
  debug: Get a shell into any image or container (Docker Inc.)
    Version:  0.0.32
    Path:     C:\Program Files\Docker\cli-plugins\docker-debug.exe
  dev: Docker Dev Environments (Docker Inc.)
    Version:  v0.1.2
    Path:     C:\Program Files\Docker\cli-plugins\docker-dev.exe
  extension: Manages Docker extensions (Docker Inc.)
    Version:  v0.2.24
    Path:     C:\Program Files\Docker\cli-plugins\docker-extension.exe
  feedback: Provide feedback, right in your terminal! (Docker Inc.)
    Version:  v1.0.5
    Path:     C:\Program Files\Docker\cli-plugins\docker-feedback.exe
  init: Creates Docker-related starter files for your project (Docker Inc.)
    Version:  v1.2.0
    Path:     C:\Program Files\Docker\cli-plugins\docker-init.exe
  sbom: View the packaged-based Software Bill Of Materials (SBOM) for an image (Anchore Inc.)
    Version:  0.6.0
    Path:     C:\Program Files\Docker\cli-plugins\docker-sbom.exe
  scout: Docker Scout (Docker Inc.)
    Version:  v1.9.3
    Path:     C:\Program Files\Docker\cli-plugins\docker-scout.exe

Server:
 Containers: 1
  Running: 1
  Paused: 0
  Stopped: 0
 Images: 30
 Server Version: 26.1.4
 Storage Driver: overlay2
  Backing Filesystem: extfs
  Supports d_type: true
  Using metacopy: false
  Native Overlay Diff: true
  userxattr: false
 Logging Driver: json-file
 Cgroup Driver: cgroupfs
 Cgroup Version: 1
 Plugins:
  Volume: local
  Network: bridge host ipvlan macvlan null overlay
  Log: awslogs fluentd gcplogs gelf journald json-file local splunk syslog
 Swarm: inactive
 Runtimes: runc io.containerd.runc.v2
 Default Runtime: runc
 Init Binary: docker-init
 containerd version: d2d58213f83a351ca8f528a95fbd145f5654e957
 runc version: v1.1.12-0-g51d5e94
 init version: de40ad0
 Security Options:
  seccomp
   Profile: unconfined
 Kernel Version: 5.15.153.1-microsoft-standard-WSL2
 Operating System: Docker Desktop
 OSType: linux
 Architecture: x86_64
 CPUs: 20
 Total Memory: 15.46GiB
 Name: docker-desktop
 ID: f4ed21d2-681e-47f7-a4c1-c52d5637749a
 Docker Root Dir: /var/lib/docker
 Debug Mode: false
 HTTP Proxy: http.docker.internal:3128
 HTTPS Proxy: http.docker.internal:3128
 No Proxy: hubproxy.docker.internal
 Labels:
  com.docker.desktop.address=npipe://\\.\pipe\docker_cli
 Experimental: false
 Insecure Registries:
  hubproxy.docker.internal:5555
  127.0.0.0/8
 Live Restore Enabled: false

WARNING: No blkio throttle.read_bps_device support
WARNING: No blkio throttle.write_bps_device support
WARNING: No blkio throttle.read_iops_device support
WARNING: No blkio throttle.write_iops_device support
WARNING: daemon is not using the default seccomp profile

What happened?

Attempts to create a database in the CosmosDb emulator running as a Testcontainer aren't successful due to hanging indefinitely. No error or exception.

The container spins up correctly with random ports, and it returns the connection string (e.g: AccountEndpoint=https://127.0.0.1:50676/;AccountKey=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==).

When using the cosmos client with SSL validation, it throws the expected HttpRequestException(see reproducible repository). When using the cosmos client without SSL validation, it hangs trying to create database.

This is the container I use. Please see more details below in the additional information along with the repository to reproduce the problem and to compare it with Ductus Fluent Docker (which works well for the same scenario).

_cosmosDbContainer = 
    new CosmosDbBuilder()
        .WithEnvironment("AZURE_COSMOS_EMULATOR_IP_ADDRESS_OVERRIDE","127.0.0.1")
        .Build();
await _cosmosDbContainer.StartAsync();

Relevant log output

No response

Additional information

I have reproduced the error in this simple solution https://github.com/diegosasw/cosmosdb-sample/tree/21ecf563f0329fbb60a7fb67d9ffc14009237e27

To reproduce, simply clone the repository provided there is .NET 8 and Docker daemon available. Compile with

dotnet build

and run tests with

dotnet test

3 out of 4 tests will pass.

Basically I have configured two xUnit fixtures to demonstrate it.

  1. FluentDockerFixture- which spins up a CosmosDb emulator using FluentDocker, and stops it and deletes it when the tests end
  2. TestContainerFixture- which spins up the testcontainer CosmosDb emulator.

Then there are two test classes (i.e: two IClassFixture), each one using its cosmos db fixture. The same scenarios Each test class tests two things:

This is the code which can be found in the public repository.

The fixture

public class TestContainerFixture
    : IAsyncLifetime
{
    private CosmosDbContainer _cosmosDbContainer = null!;
    public string CosmosDbConnectionString { get; private set; } = null!;

    public async Task InitializeAsync()
    {
        _cosmosDbContainer = 
            new CosmosDbBuilder()
                //.WithExposedPort(8081)
                .WithEnvironment("AZURE_COSMOS_EMULATOR_IP_ADDRESS_OVERRIDE","127.0.0.1")
                .Build();
        await _cosmosDbContainer.StartAsync();
        CosmosDbConnectionString = _cosmosDbContainer.GetConnectionString();
    }

    public Task DisposeAsync() => _cosmosDbContainer.StopAsync();
}

The tests

public class DatabaseCreatorTestContainerTests(TestContainerFixture testContainerFixture, ITestOutputHelper testOutputHelper)
    : IClassFixture<TestContainerFixture>
{
    [Fact]
    public async Task Secure_Client_Should_Fail()
    {
        // Given
        var cosmosClientOptions =
            new CosmosClientOptions
            {
                ConnectionMode = ConnectionMode.Gateway
            };

        var cosmosClient = new CosmosClient(testContainerFixture.CosmosDbConnectionString, cosmosClientOptions);
        var databaseName = Guid.NewGuid().ToString();

        var sut = new DatabaseCreatorTestContainer(cosmosClient);

        // When
        testOutputHelper.WriteLine(
            $"Attempting to create {databaseName} " +
            $"with connection string {testContainerFixture.CosmosDbConnectionString}");
        var exception = await Assert.ThrowsAsync<HttpRequestException>(() => sut.Create(databaseName));
        testOutputHelper.WriteLine("Attempt finished");

        // Then
        Assert.Contains("The SSL connection could not be established", exception.Message, StringComparison.OrdinalIgnoreCase);
    }

    [Fact]
    public async Task Insecure_Client_Should_Succeed() // THIS NEVER SUCCEEDS
    {
        // Given
        var cosmosClientOptions =
            new CosmosClientOptions
            {
                ConnectionMode = ConnectionMode.Gateway,
                HttpClientFactory = () =>
                {
                    var httpMessageHandler =
                        new HttpClientHandler
                        {
                            ServerCertificateCustomValidationCallback =
                                HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
                        };

                    return new HttpClient(httpMessageHandler);
                },
            };

        var cosmosClient = new CosmosClient(testContainerFixture.CosmosDbConnectionString, cosmosClientOptions);
        var databaseName = Guid.NewGuid().ToString();
        var sut = new DatabaseCreatorTestContainer(cosmosClient);

        // When
        testOutputHelper.WriteLine(
            $"Attempting to create {databaseName} " +
            $"with connection string {testContainerFixture.CosmosDbConnectionString}");
        var result = await sut.Create(databaseName);
        testOutputHelper.WriteLine("Attempt finished");

        // Then
        Assert.True(result.IsSuccessful);
    }
}

PS: The tests run sequentially (see xunit.runner.json because if they run in parallel the 4 tests will succeed (probably due to the problematic test reaching the other Fluent Docker CosmosDb container, but not sure). I tried to debug with "maxParallelThreads": 0 and "maxParallelThreads": 1 to see why it was succeeding when running in parallel and whether the test was creating the database in the other container in 8081, but when debugging the tests run sequentially, so I couldn't reveal this mystery. In any case, the original problem remains.

HofmeisterAn commented 5 months ago

This is not a bug. Use the provided and preconfigured HTTP client:

https://github.com/testcontainers/testcontainers-dotnet/blob/a0f1f7694b4602aa2ba7da6f167ddc4a56670ecc/tests/Testcontainers.CosmosDb.Tests/CosmosDbContainerTest.cs#L26

There are plenty of similar issues. Please double-check the search results. They should contain further explanations.

diegosasw commented 5 months ago

Thank you, yes you're right. That works well.

https://github.com/diegosasw/cosmosdb-sample/commit/7c53b29a7bc506936932aa6fc329392e51f4abb0