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.67k stars 254 forks source link

[Bug]: Overriding CouchbaseBuilder's default configurations resets enabled services #1039

Closed dadz01-betsson closed 8 months ago

dadz01-betsson commented 8 months ago

Testcontainers version

3.5.0

Using the latest Testcontainers version?

Yes

Host OS

Windows

Host arch

x64

.NET version

6.0.14

Docker version

Client:
 Version:           24.0.2-rd
 API version:       1.43
 Go version:        go1.20.4
 Git commit:        e63f5fa
 Built:             Fri May 26 16:43:15 2023
 OS/Arch:           windows/amd64
 Context:           default

Server: Docker Desktop 4.24.1 (123237)
 Engine:
  Version:          24.0.6
  API version:      1.43 (minimum version 1.12)
  Go version:       go1.20.7
  Git commit:       1a79695
  Built:            Mon Sep  4 12:32:16 2023
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.6.22
  GitCommit:        8165feabfdfe38c65b599c4993d227328c231fca
 runc:
  Version:          1.1.8
  GitCommit:        v1.1.8-0-g82f18fe
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

Docker info

Client:
 Version:    24.0.2-rd
 Context:    default
 Debug Mode: false
 Plugins:
  buildx: Docker Buildx (Docker Inc.)
    Version:  v0.11.2-desktop.5
    Path:     C:\Program Files\Docker\cli-plugins\docker-buildx.exe
  compose: Docker Compose (Docker Inc.)
    Version:  v2.22.0-desktop.2
    Path:     C:\Program Files\Docker\cli-plugins\docker-compose.exe
  dev: Docker Dev Environments (Docker Inc.)
    Version:  v0.1.0
    Path:     C:\Program Files\Docker\cli-plugins\docker-dev.exe
  extension: Manages Docker extensions (Docker Inc.)
    Version:  v0.2.20
    Path:     C:\Program Files\Docker\cli-plugins\docker-extension.exe
  init: Creates Docker-related starter files for your project (Docker Inc.)
    Version:  v0.1.0-beta.8
    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
  scan: Docker Scan (Docker Inc.)
    Version:  v0.26.0
    Path:     C:\Program Files\Docker\cli-plugins\docker-scan.exe
  scout: Docker Scout (Docker Inc.)
    Version:  v1.0.7
    Path:     C:\Program Files\Docker\cli-plugins\docker-scout.exe

Server:
 Containers: 2
  Running: 2
  Paused: 0
  Stopped: 0
 Images: 3
 Server Version: 24.0.6
 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 logentries splunk syslog
 Swarm: inactive
 Runtimes: io.containerd.runc.v2 runc
 Default Runtime: runc
 Init Binary: docker-init
 containerd version: 8165feabfdfe38c65b599c4993d227328c231fca
 runc version: v1.1.8-0-g82f18fe
 init version: de40ad0
 Security Options:
  seccomp
   Profile: unconfined
 Kernel Version: 5.15.90.1-microsoft-standard-WSL2
 Operating System: Docker Desktop
 OSType: linux
 Architecture: x86_64
 CPUs: 20
 Total Memory: 15.46GiB
 Name: docker-desktop
 ID: bec855d3-715d-498f-81c7-e0112e9df5f3
 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
 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?

I want to create bucket and use couchbase SDK together with Testcontainers. When writing basic code for setting up the container and create bucket I getting not very detailed exception.

Couchbase.Core.Exceptions.KeyValue.SocketNotAvailableException: ChannelConnectionPool at Couchbase.Core.ClusterContext.GetOrCreateBucketLockedAsync(String name) at Program.

$(String[] args) in \ConsoleApp4\ConsoleApp4\Program.cs:line 24

Code to repro: Csproj:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="CouchbaseNetClient" Version="3.4.12" />
    <PackageReference Include="Testcontainers" Version="3.5.0" />
    <PackageReference Include="Testcontainers.Couchbase" Version="3.5.0" />
  </ItemGroup>

</Project>

Program.cs


using Couchbase;
using Testcontainers.Couchbase;

const string TestBucketName = "Test";

CouchbaseContainer CouchbaseContainer
    = new CouchbaseBuilder()
        .Build();
try
{
    await CouchbaseContainer.StartAsync();
    var options = new ClusterOptions
    {
        UserName = CouchbaseBuilder.DefaultUsername,
        Password = CouchbaseBuilder.DefaultPassword,
    };

    var connectionString = CouchbaseContainer.GetConnectionString();
    var cluster = await Cluster.ConnectAsync(connectionString, options);

    var bucket = await cluster.BucketAsync(TestBucketName);
    var collection = await bucket.DefaultCollectionAsync();
}
catch (Exception e)
{
    await CouchbaseContainer.DisposeAsync();
    Console.WriteLine(e);

}

Relevant log output

No response

Additional information

I tried to create bucket from the UI and it worked.

HofmeisterAn commented 8 months ago

Well, the bucket does not exist, nor do you create it. Use the default generated bucket:

_couchbaseContainer.Buckets.Single().Name

Or create one:

var bucketSettings = new BucketSettings();
bucketSettings.Name = TestBucketName;
bucketSettings.FlushEnabled = false;
bucketSettings.ReplicaIndexes = true;
bucketSettings.RamQuotaMB = 100;
bucketSettings.NumReplicas = 0;

await cluster.Buckets.CreateBucketAsync(bucketSettings)
    .ConfigureAwait(false);
dadz01-betsson commented 8 months ago

Well, the bucket does not exist, nor do you create it. Use the default generated bucket:

_couchbaseContainer.Buckets.Single().Name

Or create one:

var bucketSettings = new BucketSettings();
bucketSettings.Name = TestBucketName;
bucketSettings.FlushEnabled = false;
bucketSettings.ReplicaIndexes = true;
bucketSettings.RamQuotaMB = 100;
bucketSettings.NumReplicas = 0;

await cluster.Buckets.CreateBucketAsync(bucketSettings)
    .ConfigureAwait(false);

@HofmeisterAn Thanks, however await cluster.BucketAsync(TestBucketName); Implementation suggest that it will create bucket if it does not exists.

If I create new one as you suggested and want get default collection (to be able to query/add things ) from created bucket with following code:

 var bucket = await cluster.BucketAsync(TestBucketName);
 var collection = await bucket.DefaultCollectionAsync();

I still getting the same error.

HofmeisterAn commented 8 months ago

The following lines work fine. You need to wait until the bucket is created. It is not available immediately. I am not a Couchbase expert, but I do not think these issues are related to the module implementation.

await cluster.Buckets.CreateBucketAsync(bucketSettings)
    .ConfigureAwait(false);

await Task.Delay(TimeSpan.FromSeconds(5))
    .ConfigureAwait(false);

var bucket = await cluster.BucketAsync(bucketName)
    .ConfigureAwait(false);

var collection = await bucket.DefaultCollectionAsync()
    .ConfigureAwait(false);
dadz01-betsson commented 8 months ago

The following lines work fine. You need to wait until the bucket is created. It is not available immediately. I am not a Couchbase expert, but I do not think these issues are related to the module implementation.

await cluster.Buckets.CreateBucketAsync(bucketSettings)
    .ConfigureAwait(false);

await Task.Delay(TimeSpan.FromSeconds(5))
    .ConfigureAwait(false);

var bucket = await cluster.BucketAsync(bucketName)
    .ConfigureAwait(false);

var collection = await bucket.DefaultCollectionAsync()
    .ConfigureAwait(false);

Is this really working for you ? This sample is giving me still the same error:



using Couchbase;
using Couchbase.Management.Buckets;
using Testcontainers.Couchbase;

string TestBucketName = "Test";

CouchbaseContainer CouchbaseContainer
    = new CouchbaseBuilder()
        .WithPortBinding(8091, 8091)
        .Build();
try
{
    await CouchbaseContainer.StartAsync();
    var options = new ClusterOptions
    {
        UserName = CouchbaseBuilder.DefaultUsername,
        Password = CouchbaseBuilder.DefaultPassword,
    };

    var connectionString = CouchbaseContainer.GetConnectionString(); 

    var cluster = await Cluster.ConnectAsync(connectionString, options);

    var bucketSettings = new BucketSettings
    {
        Name = TestBucketName,
        FlushEnabled = false,
        ReplicaIndexes = true,
        RamQuotaMB = 100,
        NumReplicas = 0
    };

    await cluster.Buckets.CreateBucketAsync(bucketSettings)
        .ConfigureAwait(false);

    await Task.Delay(TimeSpan.FromSeconds(5))
        .ConfigureAwait(false);

    var bucket = await cluster.BucketAsync(TestBucketName)
        .ConfigureAwait(false);

    var collection = await bucket.DefaultCollectionAsync()
        .ConfigureAwait(false);
}
catch (Exception e)
{
    await CouchbaseContainer.DisposeAsync();
    Console.WriteLine(e.ToString());

}```

![Error](https://github.com/testcontainers/testcontainers-dotnet/assets/136716460/78218de9-405d-4f39-8092-fe9842595592)
HofmeisterAn commented 8 months ago

Ah, I see. Remove the .WithPortBinding(8091, 8091) line for now. It appears that this line is overriding the enabled services (_enabledServices is empty with your configuration). I can fix it later.

https://github.com/testcontainers/testcontainers-dotnet/blob/871a9cd1d9ae98a02d7778f3e22010180bfb2d8b/src/Testcontainers.Couchbase/CouchbaseBuilder.cs#L84

dadz01-betsson commented 8 months ago

Thank you for suggestion but still I having the same behaviour.


using Couchbase;
using Couchbase.Management.Buckets;
using Testcontainers.Couchbase;

string TestBucketName = "Test";

CouchbaseContainer CouchbaseContainer
    = new CouchbaseBuilder()
        .Build();
try
{
    await CouchbaseContainer.StartAsync();
    var options = new ClusterOptions
    {
        UserName = CouchbaseBuilder.DefaultUsername,
        Password = CouchbaseBuilder.DefaultPassword,
    };

    var connectionString = CouchbaseContainer.GetConnectionString(); 

    var cluster = await Cluster.ConnectAsync(connectionString, options);

    var bucketSettings = new BucketSettings
    {
        Name = TestBucketName,
        FlushEnabled = false,
        ReplicaIndexes = true,
        RamQuotaMB = 100,
        NumReplicas = 0
    };

    await cluster.Buckets.CreateBucketAsync(bucketSettings)
        .ConfigureAwait(false);

    await Task.Delay(TimeSpan.FromSeconds(5))
        .ConfigureAwait(false);

    var bucket = await cluster.BucketAsync(TestBucketName)
        .ConfigureAwait(false);

    var collection = await bucket.DefaultCollectionAsync()
        .ConfigureAwait(false);
}
catch (Exception e)
{
    await CouchbaseContainer.DisposeAsync();
    Console.WriteLine(e.ToString());

}

image

HofmeisterAn commented 8 months ago

Can you please share the full stack trace? Without overriding the default configurations, it looks good.

image

dadz01-betsson commented 8 months ago

Message:

ChannelConnectionPool

Stacktrace:

at Couchbase.Core.ClusterContext.GetOrCreateBucketLockedAsync(String name) at Program.

$(String[] args) in ~\repos\ConsoleApp4\ConsoleApp4\Program.cs:line 41

After digging a bit I think this is comming from here: https://github.com/couchbase/couchbase-net-client/blob/master/src/Couchbase/Core/IO/Connections/Channels/ChannelConnectionPool.cs#L197

HofmeisterAn commented 8 months ago

Have you checked the web front-end to see if the bucket exists after some time? It is possible that it is still being created, and it may just require some additional time. Internally, we use the following lines to detect when the default bucket is finally created:

https://github.com/testcontainers/testcontainers-dotnet/blob/871a9cd1d9ae98a02d7778f3e22010180bfb2d8b/src/Testcontainers.Couchbase/CouchbaseBuilder.cs#L259-L267

dadz01-betsson commented 8 months ago

I checked the UI (but this requires to expose port 8091) and its there. image

Ive added infinite loop which will check is bucket exists and breaks when its created but result is exactly the same as before:

using Couchbase;
using Couchbase.Management.Buckets;
using Testcontainers.Couchbase;

string TestBucketName = "Test";

CouchbaseContainer CouchbaseContainer
    = new CouchbaseBuilder()
     //   .WithPortBinding(8091, 8091)
        .Build();
try
{
    await CouchbaseContainer.StartAsync();
    var options = new ClusterOptions
    {
        UserName = CouchbaseBuilder.DefaultUsername,
        Password = CouchbaseBuilder.DefaultPassword,
    };

    var connectionString = CouchbaseContainer.GetConnectionString(); 

    var cluster = await Cluster.ConnectAsync(connectionString, options);

    var bucketSettings = new BucketSettings
    {
        Name = TestBucketName,
        FlushEnabled = false,
        ReplicaIndexes = true,
        RamQuotaMB = 100,
        NumReplicas = 0
    };

    await cluster.Buckets.CreateBucketAsync(bucketSettings)
        .ConfigureAwait(false);

    while (true)
    {
       var buckets=  await cluster.Buckets.GetAllBucketsAsync();
       if (buckets.ContainsKey(TestBucketName))
       {
           break;
       }

       await Task.Delay(TimeSpan.FromSeconds(5));
    }

    var bucket = await cluster.BucketAsync(TestBucketName)
        .ConfigureAwait(false);

    var collection = await bucket.DefaultCollectionAsync()
        .ConfigureAwait(false);
}
catch (Exception e)
{
    await CouchbaseContainer.DisposeAsync();
    Console.WriteLine(e.ToString());

}
HofmeisterAn commented 8 months ago

but this requires to expose port 8091

Actually, it does not. You can obtain the randomly assigned host port from the inspect results, and the log messages contain a link to the web front-end as well.

Ive added infinite loop which will check is bucket exists and breaks when its created but result is exactly the same as before:

This confirms my supposition. Just because the bucket exists does not mean it is ready to use. If I create a bucket and immediately check the "/pools/default/buckets/" + bucketName API, I get a response similar to:

{
    "name": "Test",
    "nodes": []
}

However, when I wait a couple of seconds, I get:

{
    "name": "Test",
    "nodes": [
        {
            "status": "healthy",
            "services": [
                "fts",
                "index",
                "kv",
                "n1ql"
            ]
        }
    ]
}

Despite the issue I mentioned regarding overriding the default configuration, I do not see any other issues. I am sorry. I do not know how I can help you further.

dadz01-betsson commented 8 months ago

Thanks @HofmeisterAn. But even when I run this code on default bucket (which is I assume created already, because there is wait strategy) I am getting same error.

using Couchbase;
using Couchbase.Management.Buckets;
using Testcontainers.Couchbase;

CouchbaseContainer CouchbaseContainer
    = new CouchbaseBuilder()
        .Build();
try
{
    await CouchbaseContainer.StartAsync();
    var options = new ClusterOptions
    {
        UserName = CouchbaseBuilder.DefaultUsername,
        Password = CouchbaseBuilder.DefaultPassword,
    };

    var connectionString = CouchbaseContainer.GetConnectionString();

    var cluster = await Cluster.ConnectAsync(connectionString, options);
    var defaultBucket = (await cluster.Buckets.GetAllBucketsAsync()).Single();

    var bucket = await cluster.BucketAsync(defaultBucket.Key)
        .ConfigureAwait(false);

    var collection = await bucket.DefaultCollectionAsync()
        .ConfigureAwait(false);
}
catch (Exception e)
{
    await CouchbaseContainer.DisposeAsync();
    Console.WriteLine(e.ToString());

}

image

HofmeisterAn commented 8 months ago

As mentioned in SO#77396534, this appears to be a bug in the client. I encountered the same error with the latest client version. Downgrading the client to 3.4.3 resolved the issue. I recommend that you create an upstream issue in the Couchbase .NET client repository and share the test case reproducer leveraging Testcontainers.

dadz01-betsson commented 8 months ago

I will do that, thanks a lot for your help @HofmeisterAn.

HofmeisterAn commented 8 months ago

I will close this issue for now. If there is anything to do or to fix on the side of TC for .NET, do not hesitate to reopen the issue again or create a new one. Unfortunately, Couchbase's upstream repository does not allow creating issues. This is the related ticket, I think: NCBC-3545.