Where did DefaultCouchbaseEnvironment.queryPort() go?

We use testcontainers to allow us to run our unit tests in an automated fashion. We have become to exploit some additional subdocument functionality (xattr, if you’re curious) and our new test fails, while other subdocument functionality works fine.

So, I began hunting and found this blog:
https://blog.couchbase.com/unit-integration-tests-couchbase-docker-container/
Which references queryPort();
I found that here:
https://docs.couchbase.com/sdk-api/couchbase-java-client-2.0.0/com/couchbase/client/java/env/DefaultCouchbaseEnvironment.Builder.html#queryPort(int)
But it seems to be removed as of:
https://docs.couchbase.com/sdk-api/couchbase-java-client-2.3.0/com/couchbase/client/java/env/DefaultCouchbaseEnvironment.Builder.html#queryPort(int)

Is there a way to make testcontainers on ephemeral ports (allow simultaneous runs) work with Couchbase? Can this feature be brought back?

Hi unhuman,

The latest version of the org.testcontainers:couchbase module has an org.testcontainers.couchbase.CouchbaseContainer that uses a socat proxy sidecar to map all of the ports. This includes the query ports, despite what the documentation says (looks like the docs weren’t updated after the magic was added.)

I just did a little test and was able to run N1QL queries against two separate Couchbase clusters running at the same time. Quick & dirty example:


public static void main(String[] args) throws InterruptedException {
  CouchbaseContainer couchbase1 = new CouchbaseContainer()
      .withClusterAdmin("admin", "secret")
      .withNewBucket(DefaultBucketSettings.builder()
          .enableFlush(true)
          .name("bucket-name")
          .password("secret")
          .quota(100)
          .type(BucketType.COUCHBASE)
          .build());
  CouchbaseContainer couchbase2 = new CouchbaseContainer()
      .withClusterAdmin("admin", "secret")
      .withNewBucket(DefaultBucketSettings.builder()
          .enableFlush(true)
          .name("bucket-name")
          .password("secret")
          .quota(100)
          .type(BucketType.COUCHBASE)
          .build());

  couchbase1.start();
  couchbase2.start();
  CouchbaseCluster cluster1 = couchbase1.getCouchbaseCluster();
  CouchbaseCluster cluster2 = couchbase2.getCouchbaseCluster();

  System.out.println("cluster1 query ports: ");
  System.out.println(couchbase1.getMappedPort(8092));
  System.out.println(couchbase1.getMappedPort(8093));

  System.out.println("cluster2 query ports:");
  System.out.println(couchbase2.getMappedPort(8092));
  System.out.println(couchbase2.getMappedPort(8093));

  Bucket b1 = cluster1.openBucket("bucket-name");
  Bucket b2 = cluster2.openBucket("bucket-name");

  b1.upsert(JsonDocument.create("123", JsonObject.create().put("foo", "bar")));
  b2.upsert(JsonDocument.create("456", JsonObject.create().put("FOO", "BAR")));

  System.out.println(b1.query(N1qlQuery.simple("select `bucket-name`.*, meta().id from `bucket-name`",
      N1qlParams.build().consistency(ScanConsistency.REQUEST_PLUS)))
      .allRows());
  System.out.println(b2.query(N1qlQuery.simple("select `bucket-name`.*, meta().id from `bucket-name`",
      N1qlParams.build().consistency(ScanConsistency.REQUEST_PLUS)))
      .allRows());
}

Thanks,
David

Thanks @david.nault,

Right - it works for us… Except for the xattr functionality in subdoc interface. I was just guessing about the changing ports stuff and that I missed something. Maybe it’s something else?

1 Like

Oh yes, I see what you mean. Executing xattr commands against that container (which is running Couchbase 5.5.1) gives an exception:

com.couchbase.client.core.CouchbaseException: Access error for subdocument operations (This can also happen if the server version doesn't support it. Couchbase server 4.5 or later is required for Subdocument operations and Couchbase Server 5.0 or later is required for extended attributes access)

I’m guessing the CouchbaseCluster returned by CouchbaseContainer.getCouchbaseCluster() is misconfigured somehow, or maybe there’s a Java client bug that causes problems when the carrier bootstrap port is overridden.

Creating the cluster by hand seems to work. Unfortunately it requires a bit of gymnastics to use the correct bootstrap port. Here’s the code that worked for me:

public static void main(String[] args) {
  final String adminUsername = "Administrator";
  final String adminPassword = "password";

  CouchbaseContainer couchbase = new CouchbaseContainer()
      .withClusterAdmin(adminUsername, adminPassword)
      .withNewBucket(DefaultBucketSettings.builder()
          .enableFlush(true)
          .name("bucket-name")
          .password("secret")
          .quota(100)
          .type(BucketType.COUCHBASE)
          .build());

  couchbase.start();

  // Custom ports aren't supported in Couchbase connection strings (hostname lists)
  // but we can work around that by configuring the bootstrap port via the environment.
  CouchbaseEnvironment env = DefaultCouchbaseEnvironment.builder()
      .bootstrapHttpDirectPort(couchbase.getMappedPort(8091))
      .build();

  try {
    CouchbaseCluster cluster = CouchbaseCluster.create(env, "localhost")
        .authenticate(adminUsername, adminPassword);
    Bucket bucket = cluster.openBucket("bucket-name");
    String docId = "123";
    bucket.upsert(JsonDocument.create(docId, JsonObject.create().put("foo", "bar")));

    DocumentFragment<Mutation> mutationResult = bucket.mutateIn(docId)
        .upsert("fax", "311-555-0151", SubdocOptionsBuilder.builder().xattr(true))
        .execute();

    System.out.println(mutationResult);

    DocumentFragment<Lookup> lookupResult = bucket.lookupIn("123")
        .get("fax", SubdocOptionsBuilder.builder().xattr(true))
        .execute();

    System.out.println(lookupResult);

    cluster.disconnect();

  } finally {
    env.shutdown();
  }
}