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

Add support for CosmosDB Emulator and Storage Emulator (Azurite) #421

Closed
d-m4m4n opened this issue Feb 14, 2022 · 67 comments
Closed

Add support for CosmosDB Emulator and Storage Emulator (Azurite) #421

d-m4m4n opened this issue Feb 14, 2022 · 67 comments
Assignees
Labels
enhancement New feature or request help wanted Extra attention is needed module An official Testcontainers module
Milestone

Comments

@d-m4m4n
Copy link

d-m4m4n commented Feb 14, 2022

Is your feature request related to a problem? Please describe.
Not related to a problem

Describe the solution you'd like
I would like to see setups for CosmosDB Emulator and Azurite to allow usage for testing storage or cosmosDB functions.

Describe alternatives you've considered
I have one implemented locally before I found this repo. I can contribute and add these changes but need to make sure I find everything I need to make sure it works. I see there is ExecuteScripts functions and I don't think they come with CLIs in the images so I would have to check.

Additional context

@HofmeisterAn
Copy link
Collaborator

Thanks for your feature request. Supporting Azure Cosmos DB would be great.

Right now, .NET Testcontainers does not ship any 3rd party database or message broker libraries etc. This makes it easier to maintain the library, but it has a disadvantage. All vendor specific calls, features, etc. go straight to the container. We do not use any client libraries at all.

I'd like to move modules in the future (databases, message brokers, etc.) to their own library. This allows much better support incl. features for databases, message brokers, etc.

What do you think about a specific project, like DotNet.Testcontainers.CosmosDB?

@HofmeisterAn HofmeisterAn added enhancement New feature or request module An official Testcontainers module labels Feb 15, 2022
@d-m4m4n
Copy link
Author

d-m4m4n commented Feb 15, 2022

Thanks for your feature request. Supporting Azure Cosmos DB would be great.

Right now, .NET Testcontainers does not ship any 3rd party database or message broker libraries etc. This makes it easier to maintain the library, but it has a disadvantage. All vendor specific calls, features, etc. go straight to the container. We do not use any client libraries at all.

I'd like to move modules in the future (databases, message brokers, etc.) to their own library. This allows much better support incl. features for databases, message brokers, etc.

What do you think about a specific project, like DotNet.Testcontainers.CosmosDB?

That would be great! Really anything to take advantage of all the awesome work you have done here. Maybe DotNet.Testcontainers.CosmosDB and DotNet.Testcontainers.Azurite ?

@HofmeisterAn
Copy link
Collaborator

Yep, sounds good.

@Timmoth
Copy link

Timmoth commented Feb 24, 2022

Built in integration would be a god send!

I'm having trouble trying to spin up an instance of CosmosDb - I'm not able to access the cosmos explorer, do you know if the cosmos image should be compatible with this library?

Running this command works fine:
docker run --name=Cosmos_DB -d -p 8081:8081 -p 10251:10251 -p 10252:10252 -p 10253:10253 -p 10254:10254 -e AZURE_COSMOS_EMULATOR_PARTITION_COUNT=29 -e AZURE_COSMOS_EMULATOR_IP_ADDRESS_OVERRIDE=127.0.0.1 -e AZURE_COSMOS_EMULATOR_ENABLE_DATA_PERSISTENCE=true mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator

However the following creates the container but I can't connect to the explorer:

    var cosmosDbContainerBuilder = new TestcontainersBuilder<TestcontainersContainer>()
        .WithImage("mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator")
        .WithName("Cosmos_DB")
        .WithPortBinding(8081, 8081)
        .WithPortBinding(10251, 10251)
        .WithPortBinding(10252, 10252)
        .WithPortBinding(10253, 10253)
        .WithPortBinding(10254, 10254)
        .WithEnvironment("AZURE_COSMOS_EMULATOR_PARTITION_COUNT", "3")
        .WithEnvironment("AZURE_COSMOS_EMULATOR_IP_ADDRESS_OVERRIDE", "127.0.0.1")
        .WithEnvironment("AZURE_COSMOS_EMULATOR_ENABLE_DATA_PERSISTENCE", "false")
        .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(8081));

    await using (var cosmosDbContainer = cosmosDbContainerBuilder.Build())
    {
        await cosmosDbContainer.StartAsync();

        //...
    }

@HofmeisterAn
Copy link
Collaborator

You need to expose the port 8081 too. It's not exposed by the Dockerfile.
Then I recommend assigning random ports.

In addition to that, it's not enough to just wait for port 8081. You'll need a custom strategy to figure out when the container is up and running:

private readonly ITestcontainersContainer container = new TestcontainersBuilder<TestcontainersContainer>()
  .WithImage("mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator")
  .WithName("azure-cosmos-emulator")
  .WithExposedPort(8081)
  .WithPortBinding(8081, true)
  .WithPortBinding(10251, true)
  .WithPortBinding(10252, true)
  .WithPortBinding(10253, true)
  .WithPortBinding(10254, true)
  .WithEnvironment("AZURE_COSMOS_EMULATOR_PARTITION_COUNT", "1")
  .WithEnvironment("AZURE_COSMOS_EMULATOR_IP_ADDRESS_OVERRIDE", "127.0.0.1")
  .WithEnvironment("AZURE_COSMOS_EMULATOR_ENABLE_DATA_PERSISTENCE", "false")
  .WithWaitStrategy(Wait.ForUnixContainer()
    .UntilPortIsAvailable(8081))
  .Build();

[Fact]
public async Task Issue()
{
  // TODO: You need to replace this with a proper wait strategy. Port 8081 is accessible before the container is ready.
  await Task.Delay(TimeSpan.FromSeconds(30))
    .ConfigureAwait(false);

  using (var handler = new HttpClientHandler())
  {
    handler.ServerCertificateCustomValidationCallback = (_, _, _, _) => true;

    using (var client = new HttpClient(handler))
    {
      var mappedPort = this.container.GetMappedPublicPort(8081);

      var response = await client.GetAsync($"https://localhost:{mappedPort}/_explorer/emulator.pem")
        .ConfigureAwait(false);

      var pem = await response.Content.ReadAsStringAsync()
        .ConfigureAwait(false);

      Debug.WriteLine(pem);
    }
  }
}

public Task InitializeAsync()
{
  return this.container.StartAsync();
}

public Task DisposeAsync()
{
  return this.container.DisposeAsync().AsTask();
}

@d-m4m4n
Copy link
Author

d-m4m4n commented Feb 24, 2022

You need to expose the port 8081 too. It's not exposed by the Dockerfile. Then I recommend assigning random ports.

In addition to that, it's not enough to just wait for port 8081. You'll need a custom strategy to figure out when the container is up and running:

private readonly ITestcontainersContainer container = new TestcontainersBuilder<TestcontainersContainer>()
  .WithImage("mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator")
  .WithName("azure-cosmos-emulator")
  .WithExposedPort(8081)
  .WithPortBinding(8081, true)
  .WithPortBinding(10251, true)
  .WithPortBinding(10252, true)
  .WithPortBinding(10253, true)
  .WithPortBinding(10254, true)
  .WithEnvironment("AZURE_COSMOS_EMULATOR_PARTITION_COUNT", "1")
  .WithEnvironment("AZURE_COSMOS_EMULATOR_IP_ADDRESS_OVERRIDE", "127.0.0.1")
  .WithEnvironment("AZURE_COSMOS_EMULATOR_ENABLE_DATA_PERSISTENCE", "false")
  .WithWaitStrategy(Wait.ForUnixContainer()
    .UntilPortIsAvailable(8081))
  .Build();

[Fact]
public async Task Issue()
{
  // TODO: You need to replace this with a proper wait strategy. Port 8081 is accessible before the container is ready.
  await Task.Delay(TimeSpan.FromSeconds(30))
    .ConfigureAwait(false);

  using (var handler = new HttpClientHandler())
  {
    handler.ServerCertificateCustomValidationCallback = (_, _, _, _) => true;

    using (var client = new HttpClient(handler))
    {
      var mappedPort = this.container.GetMappedPublicPort(8081);

      var response = await client.GetAsync($"https://localhost:{mappedPort}/_explorer/emulator.pem")
        .ConfigureAwait(false);

      var pem = await response.Content.ReadAsStringAsync()
        .ConfigureAwait(false);

      Debug.WriteLine(pem);
    }
  }
}

public Task InitializeAsync()
{
  return this.container.StartAsync();
}

public Task DisposeAsync()
{
  return this.container.DisposeAsync().AsTask();
}

Incredible work, will be looking at implementing this shortly!

@Timmoth
Copy link

Timmoth commented Mar 1, 2022

Thanks so much!
In case anyone else stumbles across this I managed to get a global instance of cosmos running for my integration tests with the following snippet:

[CollectionDefinition(nameof(IntegrationTestCollection))]
public class IntegrationTestCollection : ICollectionFixture<IntegrationTestCollectionFixture> { }

public sealed class IntegrationTestCollectionFixture : IDisposable
{
    private readonly TestcontainersContainer _cosmosContainer;
    public IntegrationTestCollectionFixture()
    {
        var outputConsumer = Consume.RedirectStdoutAndStderrToStream(new MemoryStream(), new MemoryStream());
        var waitStrategy = Wait.ForUnixContainer().UntilMessageIsLogged(outputConsumer.Stdout, "Started");
        _cosmosContainer = new TestcontainersBuilder<TestcontainersContainer>()
            .WithImage("mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator")
            .WithName("azure-cosmos-emulator")
            .WithExposedPort(8081)
            .WithExposedPort(10251)
            .WithExposedPort(10252)
            .WithExposedPort(10253)
            .WithExposedPort(10254)
            .WithPortBinding(8081, true)
            .WithPortBinding(10251, true)
            .WithPortBinding(10252, true)
            .WithPortBinding(10253, true)
            .WithPortBinding(10254, true)
            .WithEnvironment("AZURE_COSMOS_EMULATOR_PARTITION_COUNT", "30")
            .WithEnvironment("AZURE_COSMOS_EMULATOR_IP_ADDRESS_OVERRIDE", "127.0.0.1")
            .WithEnvironment("AZURE_COSMOS_EMULATOR_ENABLE_DATA_PERSISTENCE", "false")
            .WithOutputConsumer(outputConsumer)
            .WithWaitStrategy(waitStrategy)
            .Build();

        _cosmosContainer.StartAsync().GetAwaiter().GetResult();
    }

    public void Dispose()
    {
        _ = _cosmosContainer.DisposeAsync();
    }
}

Also the following is needed to setup the CosmosClient

services.AddSingleton(sp =>
{
    var cosmosClientBuilder = new CosmosClientBuilder(connString); 
    cosmosClientBuilder.WithHttpClientFactory(() =>
    {
        HttpMessageHandler httpMessageHandler = new HttpClientHandler()
        {
            ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
        };

        return new HttpClient(httpMessageHandler);
    });
    cosmosClientBuilder.WithConnectionModeGateway();

    return cosmosClientBuilder.Build();
});

See here for more

@HofmeisterAn
Copy link
Collaborator

HofmeisterAn commented Mar 1, 2022

You need to be aware of the async operations. Your IntegrationTestCollectionFixture is not right. Use the IAsyncLifetime interface of xUnit.net.

public sealed class IntegrationTestCollectionFixture : IAsyncLifetime, IDisposable
{
  private readonly IOutputConsumer consumer = Consume.RedirectStdoutAndStderrToStream(new MemoryStream(), new MemoryStream());

  private readonly ITestcontainersContainer container;

  public IntegrationTestCollectionFixture()
  {
    this.container = new TestcontainersBuilder<TestcontainersContainer>()
      .WithImage("mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator")
      .WithName("azure-cosmos-emulator")
      .WithExposedPort(8081)
      .WithExposedPort(10251)
      .WithExposedPort(10252)
      .WithExposedPort(10253)
      .WithExposedPort(10254)
      .WithPortBinding(8081, true)
      .WithPortBinding(10251, true)
      .WithPortBinding(10252, true)
      .WithPortBinding(10253, true)
      .WithPortBinding(10254, true)
      .WithEnvironment("AZURE_COSMOS_EMULATOR_PARTITION_COUNT", "30")
      .WithEnvironment("AZURE_COSMOS_EMULATOR_IP_ADDRESS_OVERRIDE", "127.0.0.1")
      .WithEnvironment("AZURE_COSMOS_EMULATOR_ENABLE_DATA_PERSISTENCE", "false")
      .WithOutputConsumer(this.consumer)
      .WithWaitStrategy(Wait.ForUnixContainer()
        .UntilMessageIsLogged(this.consumer.Stdout, "Started"))
      .Build();
  }

  public Task InitializeAsync()
  {
    return this.container.StartAsync();
  }

  public Task DisposeAsync()
  {
    return this.container.DisposeAsync().AsTask();
  }

  public void Dispose()
  {
    this.consumer.Dispose();
  }
}

@vlaskal
Copy link
Contributor

vlaskal commented Jun 18, 2022

What do you think about a specific project, like DotNet.Testcontainers.CosmosDB?

@HofmeisterAn Do you mean new project in same repo or in dedicated repo? As mentioned in #421 (comment)

@HofmeisterAn
Copy link
Collaborator

I think it makes sense to keep modules in this repository, but don't include them in the Testcontainers dependency anymore. Each module should be an independent package.

@Yeseh
Copy link
Contributor

Yeseh commented Jul 25, 2022

Maybe we can avoid using the Cosmos client SDK (at least for now) by using the REST API (see: https://docs.microsoft.com/en-us/rest/api/cosmos-db/querying-cosmosdb-resources-using-the-rest-api).
Would this cover the usecase?

Would love to try my hand at this for a bit if noone else is.

@HofmeisterAn
Copy link
Collaborator

OC, we can use the version without client as MVP and enhance it as soon as the new structure is available.

@WestDiscGolf
Copy link

WestDiscGolf commented Jul 27, 2022

This thread has been incredibly helpful. Thank you!

I've got my test container starting, and I can see the cosmos db emulator container start up in the docker desktop dashboard however I am struggling to get the test to connect to it.

I think it is down to the connection string.

In the example above ...

services.AddSingleton(sp =>
{
    var cosmosClientBuilder = new CosmosClientBuilder(connString); 
    cosmosClientBuilder.WithHttpClientFactory(() =>
    {
        HttpMessageHandler httpMessageHandler = new HttpClientHandler()
        {
            ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
        };

        return new HttpClient(httpMessageHandler);
    });
    cosmosClientBuilder.WithConnectionModeGateway();

    return cosmosClientBuilder.Build();
});

The string for connString is mentioned but not constructed. Is this the default dev emulator detail or do I need to update it to point to a different port or something? If it's not this, then I am lost 😕

Any help would be appreciated.

Thanks

EDIT

I've updated the code above so that the connection string is created based on the default development one but the port is updated before being provided to the CosmosClientBuilder.

var mappedPort = _dbContainer.GetMappedPublicPort(8081);

var updated = string.Format(connString, mappedPort);

var cosmosClientBuilder = new CosmosClientBuilder(updated);

If I debug the test and pause the test after containers have spun up etc. and I have found the port number from the above I can browse to the data explorer!

Next issue, creating db/containers on the fly for the tests ...

@HofmeisterAn
Copy link
Collaborator

one but the port is updated before being provided to the CosmosClientBuilder.

I assume you refer to the random assigned host port? This is a best practice and avoid clashes. If you assign a random host port WithPortBinding(8081, true) (notice the true arg), you need to get the public mapped port first. Like you have done with GetMappedPublicPort(8081). See this example too: #438.

Next issue, creating db/containers on the fly for the tests ...

Usually, that's done via a client dependency or a cli command (ExecAsync).

@Yeseh
Copy link
Contributor

Yeseh commented Aug 2, 2022

@HofmeisterAn I opened a Draft PR #549 with some progress, managed to get it up and running and create a database in the container at least. I havn't done any OS before so some early-ish feedback is much appreciated!

@WestDiscGolf
Copy link

WestDiscGolf commented Aug 2, 2022

@Yeseh @HofmeisterAn I got CosmosDB running in a testcontainer but have some issues with performance. I reached out on twitter and was put in contact with a PM at Microsoft on the cosmos db emulator team. I put together a demo repo to show the setup. Just waiting for a reply.

Please take a look at the repo - https://github.com/WestDiscGolf/CosmosDB-Testcontainers-Test

Hope this helps. I will review the Draft PR as I am interested in seeing how this can be done 😄

@WestDiscGolf
Copy link

Found this which has some good examples of constructing the web requests to call CosmosDB directly which could aid with not using the cosmos client - https://github.com/Azure-Samples/cosmos-db-rest-samples/blob/main/Program.cs

@WestDiscGolf
Copy link

WestDiscGolf commented Aug 3, 2022

In the examples on how to run the cosmosdb linux emulator in docker it specifies setting the memory and cpu levels ...

docker run \
    --publish 8081:8081 \
    --publish 10251-10254:10251-10254 \
    --memory 3g --cpus=2.0 \
    --name=test-linux-emulator \
    --env AZURE_COSMOS_EMULATOR_PARTITION_COUNT=10 \
    --env AZURE_COSMOS_EMULATOR_ENABLE_DATA_PERSISTENCE=true \
    --env AZURE_COSMOS_EMULATOR_IP_ADDRESS_OVERRIDE=$ipaddr \
    --interactive \
    --tty \
    mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator

Docker compose examples seem to suggest the same.

version: '3.4'

services:
  db:
    container_name: cosmosdb
    image: "mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator"
    tty: true
    restart: always
    mem_limit: 2G
    cpu_count: 2
    environment:
      - AZURE_COSMOS_EMULATOR_PARTITION_COUNT=10
      - AZURE_COSMOS_EMULATOR_ENABLE_DATA_PERSISTENCE=true
    ports:
       - "8081:8081"
       - "8900:8900"
       - "8901:8901"
       - "8979:8979"
       - "10250:10250"
       - "10251:10251"
       - "10252:10252"
       - "10253:10253"
       - "10254:10254"
       - "10255:10255"
       - "10256:10256"
       - "10350:10350"
    volumes:
       - vol_cosmos:/data/db

volumes: 
  vol_cosmos:

How can these be specified in the testcontainer setup? Thanks

@HofmeisterAn
Copy link
Collaborator

HofmeisterAn commented Aug 3, 2022

I havn't done any OS before so some early-ish feedback is much appreciated!

@Yeseh OC, thanks for your contribution. I'll take a look at it in the next days.

Please take a look at the repo - https://github.com/WestDiscGolf/CosmosDB-Testcontainers-Test

@WestDiscGolf please notice, I'm not an CosmosDB expert 😃, but OC, I'll take a look at it.

How can these be specified in the testcontainer setup? Thanks

You can use WithCreateContainerParametersModifier, see #503 and #508.

@WestDiscGolf
Copy link

Cool, thanks @HofmeisterAn. Will have a go with the WithCreateContainerParametersModifier 😄

@Yeseh
Copy link
Contributor

Yeseh commented Aug 3, 2022

Found this which has some good examples of constructing the web requests to call CosmosDB directly which could aid with not using the cosmos client - https://github.com/Azure-Samples/cosmos-db-rest-samples/blob/main/Program.cs

Good stuff, that is very useful. Thanks! I'll go implemnent a couple of those at least .

What should the user interface of the Testcontainer be? The above example seems pretty complete, would it be desirable to have all these operations available to the user in the final version (with or without client)?

@vlaskal
Copy link
Contributor

vlaskal commented Aug 4, 2022

I see CosmosDB is in progress but Azurite isn't.
Are there any objections if I will try work on AzuriteTestcontainer?

vlaskal added a commit to vlaskal/dotnet-testcontainers that referenced this issue Aug 7, 2022
vlaskal added a commit to vlaskal/dotnet-testcontainers that referenced this issue Aug 7, 2022
vlaskal added a commit to vlaskal/dotnet-testcontainers that referenced this issue Aug 7, 2022
vlaskal added a commit to vlaskal/dotnet-testcontainers that referenced this issue Aug 7, 2022
…for:

- DebugLog,
- DisableProductStyleUrl,
- SkipApiVersionCheck,
- LooseMode,
- SilentMode
@wbuck
Copy link

wbuck commented Oct 11, 2022

OK, so currently I have the following network adapters:

  • Ethernet (Realtek USB GbE Family Controller)
  • vEthernet (Default Switch) Hyper-V
  • vEthernet (External VM Switch) Hyper-V
  • vEthernet (nat) Hyper-V
  • vEthernet (WSL) Hyper-V
  • Wi-Fi

In order to connect with the test container, I had to first disable the vEthernet (External VM Switch) Hyper-V adaptor.
I then had to enable Internet Protocol Version 4 (TCP/IPv4) for my actual NIC (this was disabled on the actual NIC because I was sharing the network adaptor with the VM's through the external VM switch).

I have some really distant memory that Hyper-V and Docker have had some issues, but I can't remember what that was.
Anyways, the issue seems resolved. Thank-you for everyone's help.

@HofmeisterAn
Copy link
Collaborator

HofmeisterAn commented Oct 12, 2022

In order to connect with the test container, I had to first disable the vEthernet (External VM Switch) Hyper-V adaptor.
I then had to enable Internet Protocol Version 4 (TCP/IPv4) for my actual NIC (this was disabled on the actual NIC because I was sharing the network adaptor with the VM's through the external VM switch).

@kiview @eddumelendez @mdelapenya how do you deal in other languages with the network settings? Some devs have issues to connect to containers. Usually, I cannot reproduce it. Any idea how to make it more reliable or convenient for the dev to figure out what is going wrong e.g. see #607?

@WestDiscGolf
Copy link

I have tried running my example - https://github.com/WestDiscGolf/CosmosDB-Testcontainers-Test/ - with hyper v running as well as docker desktop and the tests are successful. I have tried commenting out the following line in the DataContainerFixture:

//.WithEnvironment("AZURE_COSMOS_EMULATOR_IP_ADDRESS_OVERRIDE", "127.0.0.1")

The test runs continue to function.

I have the Default Switch Hyper-V NIC and not the "external switch" configured NIC. I am running Windows 10 21H1 if that helps.

Unsure if any of this information is helpful but thought I'd mention it just in case 😄

@wbuck
Copy link

wbuck commented Oct 12, 2022

I had to create the external switch manually via the virtual switch manager.
You can use the following guide to set that up.

I think the key is to enable Allow management operating system to share this network adapter.
Also I'm using gen 1 VM's (due to HCK, HLK limitations), but I don't believe that's an issue in this case.

If I re-enable my external VM switch I lose the ability to connect to the container.

@HofmeisterAn
Copy link
Collaborator

If I re-enable my external VM switch I lose the ability to connect to the container.

To any container? Are you able to connect through a different IP or something like that?

@wbuck
Copy link

wbuck commented Oct 12, 2022

I'll test that after I drop my kids off at school.
I have a SQL Server Linux container that I can try and connect to. I'll try connecting to it via this library and manually via Azure Data Studio.

@rrr-ganesh
Copy link

Getting an error while running the test application - https://github.com/WestDiscGolf/CosmosDB-Testcontainers-Test

System.TypeInitializationException
HResult=0x80131534
Message=The type initializer for 'DotNet.Testcontainers.Configurations.TestcontainersSettings' threw an exception.
Source=Testcontainers
StackTrace:
at DotNet.Testcontainers.Configurations.TestcontainersSettings.get_OS()
at DotNet.Testcontainers.Builders.TestcontainersBuilder`1..ctor()
at Api.Tests.Infrastructure.DataContainerFixture..ctor() in C:\CosmosDB-Testcontainers-Test-main\tests\Api.Tests\Infrastructure\DataContainerFixture.cs:line 21

This exception was originally thrown at this call stack:
[External Code]

Inner Exception 1:
InvalidOperationException: Sequence contains no elements

@HofmeisterAn
Copy link
Collaborator

@rrr-ganesh This looks like Docker is not running or Testcontainers for .NET cannot detect your Docker host configuration. The latest snapshot release should show an error message with more context (see Custom Configuration too).

@dcuccia
Copy link

dcuccia commented Nov 3, 2022

Think I am having similar issues with the stock CosmosDbTestcontainer (version 2.2.0, running .Net 7 RC2) - container is running, but connection is refused. Happens on both Windows 11 with Docker installed, and in GitHub Actions Linux environment. Here's the stack trace for Actions:


warn: Microsoft.AspNetCore.HttpsPolicy.HttpsRedirectionMiddleware[3]
      Failed to determine the https port for redirect.
warn: Microsoft.AspNetCore.HttpsPolicy.HttpsRedirectionMiddleware[3]
      Failed to determine the https port for redirect.
[xUnit.net 00:00:05.35]  TESTNAME [FAIL]
  Failed FUNCTIONNAME at CODELOCATION [2 s]
  Error Message:
   System.Net.Http.HttpRequestException : Connection refused (localhost:8081)
---- System.Net.Sockets.SocketException : Connection refused
  Stack Trace:
     at System.Net.Http.HttpConnectionPool.ConnectToTcpHostAsync(String host, Int32 port, HttpRequestMessage initialRequest, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.AddHttp11ConnectionAsync(QueueItem queueItem)
   at System.Threading.Tasks.TaskCompletionSourceWithCancellation`1.WaitWithCancellationAsync(CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.HttpConnectionWaiter`1.WaitForConnectionAsync(Boolean async, CancellationToken requestCancellationToken)
   at System.Net.Http.HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
   at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at Microsoft.Extensions.Http.Logging.LoggingHttpMessageHandler.<SendAsync>g__Core|5_0(HttpRequestMessage request, CancellationToken cancellationToken)
   at Microsoft.Extensions.Http.Logging.LoggingScopeHttpMessageHandler.<SendAsync>g__Core|5_0(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
   at Microsoft.Azure.Cosmos.CosmosHttpClientCore.ExecuteHttpHelperAsync(HttpRequestMessage requestMessage, ResourceType resourceType, CancellationToken cancellationToken)
   at Microsoft.Azure.Cosmos.CosmosHttpClientCore.SendHttpHelperAsync(Func`1 createRequestMessageAsync, ResourceType resourceType, HttpTimeoutPolicy timeoutPolicy, IClientSideRequestStatistics clientSideRequestStatistics, CancellationToken cancellationToken)
   at Microsoft.Azure.Cosmos.GatewayAccountReader.GetDatabaseAccountAsync(Uri serviceEndpoint)
   at Microsoft.Azure.Cosmos.Routing.GlobalEndpointManager.GetAccountPropertiesHelper.GetAndUpdateAccountPropertiesAsync(Uri endpoint)
   at Microsoft.Azure.Cosmos.Routing.GlobalEndpointManager.GetAccountPropertiesHelper.GetAccountPropertiesAsync()
   at Microsoft.Azure.Cosmos.GatewayAccountReader.InitializeReaderAsync()
   at Microsoft.Azure.Cosmos.CosmosAccountServiceConfiguration.InitializeAsync()
   at Microsoft.Azure.Cosmos.DocumentClient.InitializeGatewayConfigurationReaderAsync()
   at Microsoft.Azure.Cosmos.DocumentClient.GetInitializationTaskAsync(IStoreClientFactory storeClientFactory)
   at Microsoft.Azure.Cosmos.DocumentClient.EnsureValidClientAsync(ITrace trace)
   at Microsoft.Azure.Cosmos.Handlers.RequestInvokerHandler.EnsureValidClientAsync(RequestMessage request, ITrace trace)
   at Microsoft.Azure.Cosmos.Handlers.RequestInvokerHandler.SendAsync(RequestMessage request, CancellationToken cancellationToken)
   at Microsoft.Azure.Cosmos.Handlers.RequestInvokerHandler.SendAsync(String resourceUriString, ResourceType resourceType, OperationType operationType, RequestOptions requestOptions, ContainerInternal cosmosContainerCore, FeedRange feedRange, Stream streamPayload, Action`1 requestEnricher, ITrace trace, CancellationToken cancellationToken)
   at Microsoft.Azure.Cosmos.CosmosClient.<>c__DisplayClass50_0.<<CreateDatabaseIfNotExistsAsync>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.Azure.Cosmos.ClientContextCore.RunWithDiagnosticsHelperAsync[TResult](ITrace trace, Func`2 task)
   at Microsoft.Azure.Cosmos.ClientContextCore.<>c__DisplayClass31_0`1.<<OperationHelperWithRootTraceWithSynchronizationContextAsync>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.Azure.CosmosRepository.Services.DefaultCosmosContainerService.GetContainerAsync(Type itemType, Boolean forceContainerSync)
   at Microsoft.Azure.CosmosRepository.Providers.DefaultCosmosContainerProvider`1.<>c__DisplayClass1_0.<<-ctor>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at FUNCTIONNAME in CODELOCATION
   at FUNCTIONNAME in CODELOCATION
   at FUNCTIONNAME in CODELOCATION
--- End of stack trace from previous location ---
----- Inner Stack Trace -----
   at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ThrowException(SocketError error, CancellationToken cancellationToken)
   at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 token)
   at System.Net.Sockets.Socket.<ConnectAsync>g__WaitForConnectWithCancellation|281_0(AwaitableSocketAsyncEventArgs saea, ValueTask connectTask, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.ConnectToTcpHostAsync(String host, Int32 port, HttpRequestMessage initialRequest, Boolean async, CancellationToken cancellationToken)
[xUnit.net 00:00:41.29]     TESTNAME [FAIL]
  Failed TESTNAME [3 s]
  Error Message:
   System.Net.Http.HttpRequestException : Connection refused (localhost:49154)
---- System.Net.Sockets.SocketException : Connection refused
  Stack Trace:
     at System.Net.Http.HttpConnectionPool.ConnectToTcpHostAsync(String host, Int32 port, HttpRequestMessage initialRequest, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.AddHttp11ConnectionAsync(QueueItem queueItem)
   at System.Threading.Tasks.TaskCompletionSourceWithCancellation`1.WaitWithCancellationAsync(CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.HttpConnectionWaiter`1.WaitForConnectionAsync(Boolean async, CancellationToken requestCancellationToken)
   at System.Net.Http.HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
   at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at Microsoft.Extensions.Http.Logging.LoggingHttpMessageHandler.<SendAsync>g__Core|5_0(HttpRequestMessage request, CancellationToken cancellationToken)
   at Microsoft.Extensions.Http.Logging.LoggingScopeHttpMessageHandler.<SendAsync>g__Core|5_0(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
   at Microsoft.Azure.Cosmos.CosmosHttpClientCore.ExecuteHttpHelperAsync(HttpRequestMessage requestMessage, ResourceType resourceType, CancellationToken cancellationToken)
   at Microsoft.Azure.Cosmos.CosmosHttpClientCore.SendHttpHelperAsync(Func`1 createRequestMessageAsync, ResourceType resourceType, HttpTimeoutPolicy timeoutPolicy, IClientSideRequestStatistics clientSideRequestStatistics, CancellationToken cancellationToken)
   at Microsoft.Azure.Cosmos.GatewayAccountReader.GetDatabaseAccountAsync(Uri serviceEndpoint)
   at Microsoft.Azure.Cosmos.Routing.GlobalEndpointManager.GetAccountPropertiesHelper.GetAndUpdateAccountPropertiesAsync(Uri endpoint)
   at Microsoft.Azure.Cosmos.Routing.GlobalEndpointManager.GetAccountPropertiesHelper.GetAccountPropertiesAsync()
   at Microsoft.Azure.Cosmos.GatewayAccountReader.InitializeReaderAsync()
   at Microsoft.Azure.Cosmos.CosmosAccountServiceConfiguration.InitializeAsync()
   at Microsoft.Azure.Cosmos.DocumentClient.InitializeGatewayConfigurationReaderAsync()
   at Microsoft.Azure.Cosmos.DocumentClient.GetInitializationTaskAsync(IStoreClientFactory storeClientFactory)
   at Microsoft.Azure.Cosmos.DocumentClient.EnsureValidClientAsync(ITrace trace)
   at Microsoft.Azure.Cosmos.Handlers.RequestInvokerHandler.EnsureValidClientAsync(RequestMessage request, ITrace trace)
   at Microsoft.Azure.Cosmos.Handlers.RequestInvokerHandler.SendAsync(RequestMessage request, CancellationToken cancellationToken)
   at Microsoft.Azure.Cosmos.Handlers.RequestInvokerHandler.SendAsync(String resourceUriString, ResourceType resourceType, OperationType operationType, RequestOptions requestOptions, ContainerInternal cosmosContainerCore, FeedRange feedRange, Stream streamPayload, Action`1 requestEnricher, ITrace trace, CancellationToken cancellationToken)
   at Microsoft.Azure.Cosmos.CosmosClient.<>c__DisplayClass50_0.<<CreateDatabaseIfNotExistsAsync>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.Azure.Cosmos.ClientContextCore.RunWithDiagnosticsHelperAsync[TResult](ITrace trace, Func`2 task)
   at Microsoft.Azure.Cosmos.ClientContextCore.<>c__DisplayClass31_0`1.<<OperationHelperWithRootTraceWithSynchronizationContextAsync>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.Azure.CosmosRepository.Services.DefaultCosmosContainerService.GetContainerAsync(Type itemType, Boolean forceContainerSync)
   at Microsoft.Azure.CosmosRepository.Providers.DefaultCosmosContainerProvider`1.<>c__DisplayClass1_0.<<-ctor>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.Azure.CosmosRepository.DefaultRepository`1.ExistsAsync(String id, PartitionKey partitionKey, CancellationToken cancellationToken)
   at FUNCTIONNAME in /home/runner/work/CODELOCATION
   at FUNCTIONNAME in /home/runner/work/CODELOCATION
--- End of stack trace from previous location ---
----- Inner Stack Trace -----
   at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ThrowException(SocketError error, CancellationToken cancellationToken)
   at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 token)
   at System.Net.Sockets.Socket.<ConnectAsync>g__WaitForConnectWithCancellation|281_0(AwaitableSocketAsyncEventArgs saea, ValueTask connectTask, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.ConnectToTcpHostAsync(String host, Int32 port, HttpRequestMessage initialRequest, Boolean async, CancellationToken cancellationToken)

What can I do to troubleshoot or provide more information?

@ktjn
Copy link
Contributor

ktjn commented Nov 3, 2022

There is a known problem with the cosmos emulator running in docker. Sometimes it's stuck in starting. This is due to running under a certain intel arch with ubuntu 20 or 22. Can you see in the log that it's actually started?

@dcuccia
Copy link

dcuccia commented Nov 3, 2022

There is a known problem with the cosmos emulator running in docker. Sometimes it's stuck in starting. This is due to running under a certain intel arch with ubuntu 20 or 22. Can you see in the log that it's actually started?

@ktjn thanks for your reply - below is what I see in Docker Desktop for Windows.

TestContainers container log:

2022/11/03 18:18:02 Pinging Docker...
2022/11/03 18:18:02 Docker daemon is available!
2022/11/03 18:18:02 Starting on port 8080...
2022/11/03 18:18:02 Started!
2022/11/03 18:18:02 New client connected: 172.17.0.1:46316
2022/11/03 18:18:02 Received the first connection
2022/11/03 18:18:02 Adding {"label":{"testcontainers.resource-reaper-session=9002e6f2-ea85-47b1-8858-924a3eff9fa9":true}}

Cosmos Emulator log:

This is an evaluation version.  There are [11] days left in the evaluation period.
Starting
Started 1/3 partitions
Started 2/3 partitions
Started 3/3 partitions
Started

And here is the code I'm using (changing the connection string to my local CosmosDb emulator succeeds):

using DotNet.Testcontainers.Builders;
using DotNet.Testcontainers.Configurations;
using DotNet.Testcontainers.Containers;
using Microsoft.Azure.CosmosRepository;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System.Runtime.InteropServices;

namespace IntegrationTests.Features.Thing;

public class CosmosDbThing { public string Id { get; init; } = Guid.NewGuid().ToString(); }

public class CosmosDbThingServiceTests : IAsyncLifetime
{
    private CosmosDbTestcontainer? _dbContainer;
    private readonly List<string> _createdThingIds = new();
    private CosmosDbThingService? _cosmosDbThingService;

    public async Task InitializeAsync()
    {
        _dbContainer =
            new TestcontainersBuilder<CosmosDbTestcontainer>()
            .WithDatabase(new CosmosDbTestcontainerConfiguration())
            .WithWaitStrategy(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) switch
                {
                    true => Wait.ForWindowsContainer(),
                    _    => Wait.ForUnixContainer(),
                })
            .Build();

        await _dbContainer.StartAsync();

        var cosmosThingRepository = 
            new ServiceCollection()
                .AddSingleton<IConfiguration>(new ConfigurationBuilder().Build())
                .AddOptions()
                .AddCosmosRepository(
                    options =>
                    {
                        options.CosmosConnectionString = _dbContainer?.ConnectionString ?? "";
                        options.ContainerId = "thing-store";
                        options.DatabaseId = "main";
                        options.ContainerPerItemType = true;
                        options.ContainerBuilder.Configure<CosmosDbThing>(containerOptions => containerOptions
                            .WithContainer("things")
                            .WithPartitionKey("/id")
                            .WithSyncableContainerProperties()
                        );
                    })
                .BuildServiceProvider()
                .GetRequiredService<IRepository<CosmosDbThing>>();

        _cosmosDbThingService = new CosmosDbThingService();
    }
    
    public async Task DisposeAsync()
    {
        foreach (var createdThingId in _createdThingIds)
        {
            await _cosmosDbThingService!.DeleteAsync(createdThingId);
        }

        await (_dbContainer?.StopAsync() ?? Task.CompletedTask);
    }

    [Fact]
    public async Task CreateCosmosDbThingAsync_CreatesCosmosDbThing()
    {
        // Arrange
        var thing = new CosmosDbThing();

        // Act
        var success = await _cosmosDbThingService!.CreateCosmosDbThingAsync(thing);
        _createdThingIds.Add(thing.Id);

        // Assert
        success.Should().BeTrue();
    }
}

@ktjn
Copy link
Contributor

ktjn commented Nov 3, 2022

You should use a IClassFixture, othwise the container will be recreated for each test. Anyways...
Use something similar to: https://github.com/testcontainers/testcontainers-dotnet/blob/develop/tests/Testcontainers.Tests/Fixtures/Containers/Unix/Modules/Databases/CosmosDbFixture.cs

I think your waitstategy is the problem. Use the correct one.

See this for how to use: https://github.com/testcontainers/testcontainers-dotnet/blob/develop/tests/Testcontainers.Tests/Unit/Containers/Unix/Modules/Databases/CosmosDbTestcontainerTest.cs

@HofmeisterAn
Copy link
Collaborator

HofmeisterAn commented Nov 3, 2022

  1. The WithWaitStrategy does not make sense. Your configuration waits until the container is running, but not until the service is ready.
  2. I think you need to configure your HTTP client too, at least the Cosmos DB container offers one.

public HttpClient HttpClient => new HttpClient(this.HttpMessageHandler);

var cosmosClientOptions = new CosmosClientOptions();
cosmosClientOptions.ConnectionMode = ConnectionMode.Gateway;
cosmosClientOptions.HttpClientFactory = () => httpClient;
this.httpClient = httpClient;
this.cosmosClient = new CosmosClient(connectionString, cosmosClientOptions);

@dcuccia
Copy link

dcuccia commented Nov 3, 2022

Thanks for the feedback @ktjn and @HofmeisterAn ! My apologies for the distracting issues. To address them:

  1. Re: WaitStrategy, my fault - this was erroneous logic. I'd played with this to see if I could resolve my original issue, and it was intended to change the strategy based on the characteristics of the container, but that's not what I coded.
  2. Re: HttpClient, I'm using the Microsoft.Azure.CosmosRepository IRepository abstraction - their AddCosmosRepository() configures an HttpClient internally.
  3. Re: IClassFixture - yes, good points

I've factored the code into a stand-alone solution, and placed on my GitHub here, which now surfaces the original SSL connection denied exception I'd observed:

 IntegrationTests.Features.Thing.CosmosDbThingServiceTests.CreateCosmosDbThingAsync_CreatesCosmosDbThing
   Source: CosmosDbThingServiceTests.cs line 56
   Duration: 2.2 sec

  Message: 
System.Net.Http.HttpRequestException : The SSL connection could not be established, see inner exception.
---- System.IO.IOException :  Received an unexpected EOF or 0 bytes from the transport stream.

  Stack Trace: 
ConnectHelper.EstablishSslConnectionAsync(SslClientAuthenticationOptions sslOptions, HttpRequestMessage request, Boolean async, Stream stream, CancellationToken cancellationToken)
HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
HttpConnectionPool.AddHttp11ConnectionAsync(QueueItem queueItem)
TaskCompletionSourceWithCancellation`1.WaitWithCancellationAsync(CancellationToken cancellationToken)
HttpConnectionWaiter`1.WaitForConnectionAsync(Boolean async, CancellationToken requestCancellationToken)
HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
LoggingHttpMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
LoggingScopeHttpMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
CosmosHttpClientCore.ExecuteHttpHelperAsync(HttpRequestMessage requestMessage, ResourceType resourceType, CancellationToken cancellationToken)
CosmosHttpClientCore.SendHttpHelperAsync(Func`1 createRequestMessageAsync, ResourceType resourceType, HttpTimeoutPolicy timeoutPolicy, IClientSideRequestStatistics clientSideRequestStatistics, CancellationToken cancellationToken)
GatewayAccountReader.GetDatabaseAccountAsync(Uri serviceEndpoint)
GetAccountPropertiesHelper.GetAndUpdateAccountPropertiesAsync(Uri endpoint)
GetAccountPropertiesHelper.GetAccountPropertiesAsync()
GatewayAccountReader.InitializeReaderAsync()
CosmosAccountServiceConfiguration.InitializeAsync()
DocumentClient.InitializeGatewayConfigurationReaderAsync()
DocumentClient.GetInitializationTaskAsync(IStoreClientFactory storeClientFactory)
DocumentClient.EnsureValidClientAsync(ITrace trace)
RequestInvokerHandler.EnsureValidClientAsync(RequestMessage request, ITrace trace)
RequestInvokerHandler.SendAsync(RequestMessage request, CancellationToken cancellationToken)
RequestInvokerHandler.SendAsync(String resourceUriString, ResourceType resourceType, OperationType operationType, RequestOptions requestOptions, ContainerInternal cosmosContainerCore, FeedRange feedRange, Stream streamPayload, Action`1 requestEnricher, ITrace trace, CancellationToken cancellationToken)
<<CreateDatabaseIfNotExistsAsync>b__0>d.MoveNext()
--- End of stack trace from previous location ---
ClientContextCore.RunWithDiagnosticsHelperAsync[TResult](ITrace trace, Func`2 task)
<<OperationHelperWithRootTraceWithSynchronizationContextAsync>b__0>d.MoveNext()
--- End of stack trace from previous location ---
DefaultCosmosContainerService.GetContainerAsync(Type itemType, Boolean forceContainerSync)
<<-ctor>b__0>d.MoveNext()
--- End of stack trace from previous location ---
DefaultRepository`1.ExistsAsync(String id, PartitionKey partitionKey, CancellationToken cancellationToken)
CosmosDbThingService.CreateCosmosDbThingAsync(CosmosDbThing thing) line 20
CosmosDbThingServiceTests.CreateCosmosDbThingAsync_CreatesCosmosDbThing() line 62
--- End of stack trace from previous location ---
----- Inner Stack Trace -----
SslStream.ReceiveBlobAsync[TIOAdapter](CancellationToken cancellationToken)
SslStream.ForceAuthenticationAsync[TIOAdapter](Boolean receiveFirst, Byte[] reAuthenticationData, CancellationToken cancellationToken)
ConnectHelper.EstablishSslConnectionAsync(SslClientAuthenticationOptions sslOptions, HttpRequestMessage request, Boolean async, Stream stream, CancellationToken cancellationToken)

If you have any time to review this further, I'd be greatly appreciative!

@akamud
Copy link

akamud commented Nov 3, 2022

I've had some of the issues described in this issue, none of them had anything to do with testcontainers or the options provided by default. All of them also happened running the docker image manually.

See if your issue may be resolved by one of these:

  • Depending on your OS, you have to trust the certificate used by the emulator before you can do any connection, this process is described here. This page also talks about getting the The TLS/SSL connection couldn't be established error at the Troubleshoot issues section.
  • I was also getting timeouts/errors due to high CPU. The documentation itself says you should use at least 4 CPUs for the CosmosDb emulator container, so make sure you have at least 4 CPUs available. Even with 4 CPUs I was occasionally getting timeout errors. You can read more about it here. Took me a while to find out about that.

Hope this can help some of you.

@HofmeisterAn
Copy link
Collaborator

  • Re: HttpClient, I'm using the Microsoft.Azure.CosmosRepository IRepository abstraction - their AddCosmosRepository() configures an HttpClient internally.

Where do you configure SSL? Install the certificate, use your own or configure the ServerCertificateCustomValidationCallback:

: base(new HttpClientHandler { ServerCertificateCustomValidationCallback = (sender, certificate, chain, errors) => true })

I do not know AddCosmosRepository maybe you need to set the right security protocol too.

@ktjn
Copy link
Contributor

ktjn commented Nov 4, 2022

@dcuccia There are a lot of issues with your code. Some hints:

  • Always start with something that works and add one thing at a time
  • Cosmosdb is messy to begin with, the emulator is even messier and needs a lot a special treatment to work
  • dont check if an item exists before doing an operation, there are status codes for that. The perfomance(and cost) will be double

Here are fixed versions of your code:

Use the cosmosclient directly. It needs a special http client with ssl validation disabled. Also a special http handler to rewrite the endpoints.

using Microsoft.Azure.Cosmos;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Azure.CosmosRepository;

namespace IntegrationTests.Features.Thing;

public class CosmosDbThing : Item { }

public class CosmosDbThingService
{
    private readonly Container _container;

    public CosmosDbThingService(Container container)
    {
        _container = container;
    }

    public async ValueTask<bool> CreateCosmosDbThingAsync(CosmosDbThing thing)
    {
        return await _container.CreateItemAsync(thing) is { };
    }

    public async ValueTask<bool> UpdateAsync(CosmosDbThing thing)
    {
        return await _container.UpsertItemAsync(thing) is { };
    }

    public async ValueTask<bool> DeleteAsync(string id)
    {
        await _container.DeleteItemAsync<CosmosDbThing>(id, new PartitionKey(id));
        return true;
    }

    public async ValueTask<CosmosDbThing?> GetByIdAsync(string id)
        => await _container.ReadItemAsync<CosmosDbThing>(id, new PartitionKey(id));
}
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Azure.CosmosRepository;
using DotNet.Testcontainers.Builders;
using DotNet.Testcontainers.Configurations;
using DotNet.Testcontainers.Containers;
using Microsoft.Azure.Cosmos;

namespace IntegrationTests.Features.Thing;

public class CosmosDbThingServiceTests : IClassFixture<CosmosDbFixture>, IAsyncLifetime
{
    private readonly List<string> _createdThingIds = new();
    private CosmosDbThingService? _cosmosDbThingService;
    
    private readonly CosmosClient _cosmosClient;

    public CosmosDbThingServiceTests(CosmosDbFixture cosmosDbFixture)
    {
        var cosmosClientOptions = new CosmosClientOptions { ConnectionMode = ConnectionMode.Gateway, HttpClientFactory = () => cosmosDbFixture.Container.HttpClient };
        _cosmosClient = new CosmosClient(cosmosDbFixture.Container.ConnectionString, cosmosClientOptions);
    }
    public async Task InitializeAsync()
    {
        var db = await _cosmosClient.CreateDatabaseIfNotExistsAsync("thing-store");
        var c = await db.Database.CreateContainerIfNotExistsAsync(new ContainerProperties { Id = "things", PartitionKeyPath = "/id"});
        _cosmosDbThingService = new CosmosDbThingService(c.Container);
    }

    public async Task DisposeAsync()
    {
        foreach (var createdThingId in _createdThingIds)
        {
            await _cosmosDbThingService!.DeleteAsync(createdThingId);
        }
    }

    [Fact]
    public async Task CreateCosmosDbThingAsync_CreatesCosmosDbThing()
    {
        // Arrange
        var thing = new CosmosDbThing { Id = Guid.NewGuid().ToString() };

        // Act
        var success = await _cosmosDbThingService!.CreateCosmosDbThingAsync(thing);
        _createdThingIds.Add(thing.Id);

        // Assert
        Assert.True(success);
    }
}

public class CosmosDbFixture : DatabaseFixture<CosmosDbTestcontainer>
{
    private readonly CosmosDbTestcontainerConfiguration configuration = new CosmosDbTestcontainerConfiguration();

    private readonly CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromMinutes(5));

    public CosmosDbFixture()
    {
        this.Container = new TestcontainersBuilder<CosmosDbTestcontainer>()
          .WithDatabase(this.configuration)
          .Build();
    }

    public override Task InitializeAsync()
    {
        return this.Container.StartAsync(this.cts.Token);
    }

    public override Task DisposeAsync()
    {
        return this.Container.DisposeAsync().AsTask();
    }

    public override void Dispose()
    {
        this.configuration.Dispose();
    }
}

public abstract class DatabaseFixture<TDockerContainer> : IAsyncLifetime, IDisposable
  where TDockerContainer : ITestcontainersContainer
{
    public TDockerContainer Container { get; protected set; }

    public abstract Task InitializeAsync();

    public abstract Task DisposeAsync();

    public abstract void Dispose();
}

@malinovic-nemanja
Copy link

Thank you to everyone contributing to this thread, I've been trying to set this up on a project since yesterday, but I cannot figure out what the problem is. It seems as if the CosmosClient is defaulting to port :8081 even though I initialize it with the proper connection string. Here is my code:

public class GetPhraseHandlerTests : IAsyncLifetime
{
    private readonly CosmosDbTestcontainer? _cosmosDbTestContainer;

    public GetPhraseHandlerTests()
    {
        _cosmosDbTestContainer = new TestcontainersBuilder<CosmosDbTestcontainer>()
	        .WithDatabase(new CosmosDbTestcontainerConfiguration())
	        .Build();
    }

    public async Task InitializeAsync()
    {
        if (_cosmosDbTestContainer != null)
            await _cosmosDbTestContainer.StartAsync();

        var cosmosClientBuilder = new CosmosClientBuilder(_cosmosDbTestContainer.ConnectionString);
        cosmosClientBuilder.WithHttpClientFactory(() =>
        {
            HttpMessageHandler httpMessageHandler = new HttpClientHandler()
            {
                ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
            };

            return new HttpClient(httpMessageHandler);
        });
        var cosmosClient = cosmosClientBuilder.WithConnectionModeGateway().Build();

        await cosmosClient.CreateDatabaseAsync("NewDatabase");
        var db = cosmosClient.GetDatabase("NewDatabase");
    }
}

When the code gets to the .CreateDatabaseAsync() method, everything crashes, and this is what I get in the output:
image

However, just before that it says: "DocDBTrace Information: 0 : DocumentClient with id 1 initialized at endpoint: https://localhost:49159/ with ConnectionMode: Gateway, connection Protocol: Https, and consistency level: null"

If I use .WithPortBinding (8081, 8081), this same code works just fine. This question might be better suited for the azure-cosmos-dotnet repo, apologies if that's the case. :)

@HofmeisterAn
Copy link
Collaborator

Can you compare the ConnectionString and the endpoint the instance of cosmosClient points to? 127.0.0.1:8081 looks wrong. Testcontainers assigns random host ports to avoid port conflicts.

@WestDiscGolf
Copy link

As @HofmeisterAn said testcontainers will associate a new port to it each time. If I recall correctly the emulator hard codes the port to 8081 so you have to manually update it for the requests. Take a look at https://github.com/WestDiscGolf/CosmosDB-Testcontainers-Test/blob/main/tests/Api.Tests/Infrastructure/FixRequestLocationHandler.cs as an example of how to do this.

@malinovic-nemanja
Copy link

Can you compare the ConnectionString and the endpoint the instance of cosmosClient points to? 127.0.0.1:8081 looks wrong. Testcontainers assigns random host ports to avoid port conflicts.

@HofmeisterAn cosmosClient.Endpoint shows the correct random port.

As @HofmeisterAn said testcontainers will associate a new port to it each time. If I recall correctly the emulator hard codes the port to 8081 so you have to manually update it for the requests. Take a look at https://github.com/WestDiscGolf/CosmosDB-Testcontainers-Test/blob/main/tests/Api.Tests/Infrastructure/FixRequestLocationHandler.cs as an example of how to do this.

Although it's very unintuitive behaviour, it seems @WestDiscGolf is correct - even if you instantiate the Client using the proper ConnectionString, it still tries communicating via :8081.

Thank you both so much for the quick replies, looks like the FixRequestLocationHandler.cs did the trick for me. Funny thing is, I am sure I tried implementing it yesterday, but I must've got something wrong (my brain probably just gave up :) ).

@rrr-ganesh
Copy link

rrr-ganesh commented Dec 12, 2022

Hi, does anyone facing the below issue on this repo (https://github.com/WestDiscGolf/CosmosDB-Testcontainers-Test)

System.Net.Http.HttpRequestException : No connection could be made because the target machine actively refused it. (localhost:49194)
---- System.Net.Sockets.SocketException : No connection could be made because the target machine actively refused it.

@WestDiscGolf
Copy link

@rrr-ganesh Is this just running the repository as it is? Or doing anything different? If you raise an issue with the information in the repository then I would be happy to take a look to see if I can see anything obvious - https://github.com/WestDiscGolf/CosmosDB-Testcontainers-Test/issues

@Makciek
Copy link

Makciek commented Feb 16, 2023

Can you compare the ConnectionString and the endpoint the instance of cosmosClient points to? 127.0.0.1:8081 looks wrong. Testcontainers assigns random host ports to avoid port conflicts.

@HofmeisterAn cosmosClient.Endpoint shows the correct random port.

As @HofmeisterAn said testcontainers will associate a new port to it each time. If I recall correctly the emulator hard codes the port to 8081 so you have to manually update it for the requests. Take a look at https://github.com/WestDiscGolf/CosmosDB-Testcontainers-Test/blob/main/tests/Api.Tests/Infrastructure/FixRequestLocationHandler.cs as an example of how to do this.

Although it's very unintuitive behaviour, it seems @WestDiscGolf is correct - even if you instantiate the Client using the proper ConnectionString, it still tries communicating via :8081.

Thank you both so much for the quick replies, looks like the FixRequestLocationHandler.cs did the trick for me. Funny thing is, I am sure I tried implementing it yesterday, but I must've got something wrong (my brain probably just gave up :) ).

After long, long search I found this:
Azure/azure-cosmos-dotnet-v3#2784

and after even longer struggle I managed to make it work:
image

I know it's far from a good solution but it works without too much hacking in cosmos code itself, where you need these options:
image

@rf-0
Copy link

rf-0 commented Jun 30, 2023

Do you guys know how to set "/EnablePreview" flag when spinning up the container? I'm using hierarchical partition keys.

I thought this could be achieved with .WithEnvironment("AZURE_COSMOS_EMULATOR_ARGS","/EnablePreview") but when I access the portal on the container itself I cant see the partition keys columns.

@ktjn
Copy link
Contributor

ktjn commented Jul 5, 2023

@rf-0 Starting without any argument will add the /EnablePreview flag. From a 'ps axv' inside the container:
19 ? Sl 0:39 237 1612 28229991 1516060 4.6 ./cosmosdb-emulator -w /tmp/cosmos/appdata -- /Microsoft.Azure.Cosmos.Emulator.exe /enablepreview

From the 'start.sh' entrypoint:
EMULATOR_OTHER_ARGS="${AZURE_COSMOS_EMULATOR_ARGS:-/enablepreview}"

@Ungerfall
Copy link

For me

.WithWaitStrategy(Wait.ForUnixContainer().UntilMessageIsLogged("Started"))

wasn't enough as I received

System.Net.Http.HttpRequestException : The SSL connection could not be established, see inner exception.
---- System.IO.IOException : Received an unexpected EOF or 0 bytes from the transport stream.

Figured it out during debugging.
Adding a delay helped

    public async Task InitializeAsync()
    {
        await _container.StartAsync();
        await Task.Delay(TimeSpan.FromSeconds(120));
        await SeedData();
    }

@HofmeisterAn
Copy link
Collaborator

I think you are using an outdated version. This issue should already be addressed in version 3.8.0: #1109.

@Ungerfall
Copy link

Oh, I see. It is encapsulated in 3.8.0. Removing .WithWaitStrategy(...) helped without a need to delay.
Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request help wanted Extra attention is needed module An official Testcontainers module
Projects
None yet
Development

No branches or pull requests