Testcontainers Couchbase timeouts

Viewed 636

I've created a minimal, complete and verifiable example illustrating my problem: https://github.com/Virkom/CouchbaseMCVE.

I've created an integration test using Testcontainers and CouchbaseContainer. My gradle.build:

implementation "com.couchbase.client:java-client:3.1.3"
testImplementation "io.micronaut.test:micronaut-test-junit5"
testImplementation "org.testcontainers:junit-jupiter"
testImplementation "org.testcontainers:testcontainers"
testImplementation "org.testcontainers:couchbase"

Couchbase Client:

ClusterEnvironment env = ClusterEnvironment.builder()
    .transcoder(SerializableTranscoder.INSTANCE)
    .aggregatingMeterConfig(AggregatingMeterConfig.builder()
        .enabled(true)
        .emitInterval(Duration.ofSeconds(60)))
    .build();

ClusterOptions opts = ClusterOptions.clusterOptions(bucket, password).environment(env);
couchbaseCluster = Cluster.connect("localhost", opts);
couchbaseBucket = couchbaseCluster.bucket(bucket);

when bucket is "testBucket" and password is "testtest".

Container creation code in tests:

BucketDefinition bucketDefinition = new BucketDefinition("testBucket");

CouchbaseContainer couchbaseContainer = new CouchbaseContainer("couchbase/server")
    .withCredentials("testBucket", "testtest")
    .withBucket(bucketDefinition);

couchbaseContainer.start();

Containers starts, I can connect it by webinterface, the testBucket exists and everything is fine, but I have exception when I try to upsert the value.

The code of method:

public void set(String key, Serializable o, int ttlSeconds) {
    UpsertOptions opts = UpsertOptions.upsertOptions()
        .durability(PersistTo.NONE, ReplicateTo.NONE)
        .expiry(Duration.ofSeconds(ttlSeconds));
    couchbaseBucket.defaultCollection().upsert(key, o, opts);
}

Result:

UpsertRequest, Reason: TIMEOUT
com.couchbase.client.core.error.AmbiguousTimeoutException: UpsertRequest, Reason: TIMEOUT {"cancelled":true,"completed":true,"coreId":"0x48ac5a3200000001","idempotent":false,"reason":"TIMEOUT","requestId":5,"requestType":"UpsertRequest","retried":14,"retryReasons":["BUCKET_OPEN_IN_PROGRESS"],"service":{"bucket":"testBucket","collection":"_default","documentId":"0","opaque":"0x3","scope":"_default","type":"kv"},"timeoutMs":2500,"timings":{"encodingMicros":1434,"totalMicros":8118167}}

Also I have many warnings in the terminal like:

12:54:15.896 [cb-events] WARN  com.couchbase.endpoint - [com.couchbase.endpoint][EndpointConnectionFailedEvent][948us] Connect attempt 9 failed because of AnnotatedConnectException: finishConnect(..) failed: connection refused: localhost/127.0.0.1:8091 {"circuitBreaker":"DISABLED","coreId":"0x48ac5a3200000001","remote":"localhost:8091","type":"MANAGER"}
com.couchbase.client.core.deps.io.netty.channel.AbstractChannel$AnnotatedConnectException: finishConnect(..) failed: connection refused: localhost/127.0.0.1:8091
Caused by: java.net.ConnectException: finishConnect(..) failed: connection refused

I've spent much time to find the reason, tried to connect to cluster with couchbase container port couchbaseCluster = Cluster.connect(serverIp + ":" + serverPort, opts); and tried to create container with exposed ports .withExposedPorts(8091, 8092, 8093, 8094, 11207, 11210, 11211, 18091, 18092, 18093) but it doesn't work. Can anybody help me?

1 Answers

The key point of integration tests setup is instructing the application under test to use the random ports of the dependencies, started by testcontainers. In your case the Couchbase DB. Your application is trying to connect to the db using the default 8091 port (as set in application-test.yml), but the docker container exposes Couchbase on the random port, as you mentioned in the comments.

In order to do this with Micronaut and JUnit5 you can use TestPropertyProvider interface for example. See more details about it in the Micronaut documentation.

// @Testcontainers MUST be before @MicronautTest to make sure
// container is started before adjusting properties
@Testcontainers
@MicronautTest
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class CouchbaseServiceTest implements TestPropertyProvider {

    @Inject
    private CouchbaseService couchbaseService;

    @Container
    private static final CouchbaseContainer couchbaseContainer = new CouchbaseContainer("couchbase/server")
            .withCredentials("testBucket", "testPassword")
            .withExposedPorts(8091, 8092, 8093, 8094, 11207, 11210, 11211, 18091, 18092, 18093)
            .withBucket(new BucketDefinition("testBucket"));

    @Override
    public Map<String, String> getProperties() {
        return Map.of(
                "app.server_port", String.valueOf(couchbaseContainer.getBootstrapHttpDirectPort()),
                "app.kv_port", String.valueOf(couchbaseContainer.getBootstrapCarrierDirectPort())
        );
    }
    ...

But then you also need to change the cluster connection code slightly (CouchbaseClient.java):

...
SeedNode seedNode = SeedNode.create(
    serverIp, Optional.of(Integer.valueOf(kvPort)), Optional.of(Integer.valueOf(serverPort)));
couchbaseCluster = Cluster.connect(Set.of(seedNode), opts);

And as you probably noticed, you need to expose one more property for kv_port. The same way you did for server_port.

Related