3

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?

jannis
  • 4,843
  • 1
  • 23
  • 53
Virkom
  • 373
  • 4
  • 22
  • 2
    Can you post the complete source code of your test (or better - a [MCVE](https://meta.stackoverflow.com/questions/349789/how-do-i-create-a-minimal-complete-verifiable-example))? – jannis Jun 22 '21 at 13:22
  • 1
    Yes, of course. I've created the sample project: https://github.com/Virkom/CouchbaseMCVE – Virkom Jun 22 '21 at 14:41
  • Have you followed [these instructions](https://www.testcontainers.org/modules/databases/couchbase/)? – jannis Jun 22 '21 at 15:36
  • Yes, I've started from this manual. But it's for outdated version of couchbase (2.6.2). I have 3.1.3 in project and I cannot change it just like that. So container creation on test part is the same, but I can't use CouchbaseEnvironment, DefaultCouchbaseEnvironment classes and `CouchbaseCluster.create()` method – Virkom Jun 22 '21 at 16:36
  • At which port the test container of couchbase is starting? – riorio Jun 23 '21 at 05:45
  • It's different every time. Something like 49572, 49168 or any other. – Virkom Jun 23 '21 at 06:47
  • It looks like you may be passing in the bucket name as the user, both on the `ClusterOptions.clusterOptions(bucket, password)` and the `.withCredentials("testBucket", "testtest")` lines? This looks like a callback to Couchbase 4.0 days, when you'd authenticate against a given bucket. Couchbase 5.0+ introduced RBAC security, and you now authenticate with a username and password. – Graham Pople Jun 23 '21 at 14:18

1 Answers1

2

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.

yuppie-flu
  • 1,270
  • 9
  • 13