From 848ae832edcf80cd91d0df17f0ee38ab29a0d104 Mon Sep 17 00:00:00 2001 From: Michael Nitschinger Date: Fri, 17 Jul 2020 08:36:22 +0200 Subject: [PATCH] couchbase: wait until all services are part of the config This changeset adds a predicate to the wait strategy to make sure that it not only returns a 200, but also that every enabled service is actually already exposed in the config. Since we are polling the server during bootstrap here, not all of them might show up at the same time. Also, while not contributing to the fix we poll the terse bucket http config "b" instead of the verbose one "buckets" since it is a little more efficient on the server side and actually the config the client internally works with. fixes #2993 --- .../couchbase/CouchbaseContainer.java | 58 +++++++++++++++---- .../couchbase/CouchbaseService.java | 17 ++++-- 2 files changed, 60 insertions(+), 15 deletions(-) diff --git a/modules/couchbase/src/main/java/org/testcontainers/couchbase/CouchbaseContainer.java b/modules/couchbase/src/main/java/org/testcontainers/couchbase/CouchbaseContainer.java index ee4b3212cac..568fde56360 100644 --- a/modules/couchbase/src/main/java/org/testcontainers/couchbase/CouchbaseContainer.java +++ b/modules/couchbase/src/main/java/org/testcontainers/couchbase/CouchbaseContainer.java @@ -15,8 +15,10 @@ */ package org.testcontainers.couchbase; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.github.dockerjava.api.command.InspectContainerResponse; import com.github.dockerjava.api.model.ContainerNetwork; import lombok.Cleanup; @@ -35,11 +37,15 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.EnumSet; +import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; import java.util.stream.Collectors; +import java.util.stream.StreamSupport; /** * The couchbase container initializes and configures a Couchbase Server single node cluster. @@ -168,7 +174,7 @@ protected void configure() { .map("healthy"::equals) .orElse(false); } catch (IOException e) { - logger().error("Unable to parse response {}", response); + logger().error("Unable to parse response: " + response, e); return false; } }) @@ -241,15 +247,10 @@ private void renameNode() { private void initializeServices() { logger().debug("Initializing couchbase services on host: {}", enabledServices); - final String services = enabledServices.stream().map(s -> { - switch (s) { - case KV: return "kv"; - case QUERY: return "n1ql"; - case INDEX: return "index"; - case SEARCH: return "fts"; - default: throw new IllegalStateException("Unknown service!"); - } - }).collect(Collectors.joining(",")); + final String services = enabledServices + .stream() + .map(CouchbaseService::getIdentifier) + .collect(Collectors.joining(",")); @Cleanup Response response = doHttpRequest(MGMT_PORT, "/node/controller/setupServices", "POST", new FormBody.Builder() .add("services", services) @@ -350,10 +351,11 @@ private void createBuckets() { checkSuccessfulResponse(response, "Could not create bucket " + bucket.getName()); new HttpWaitStrategy() - .forPath("/pools/default/buckets/" + bucket.getName()) + .forPath("/pools/default/b/" + bucket.getName()) .forPort(MGMT_PORT) .withBasicCredentials(username, password) .forStatusCode(200) + .forResponsePredicate(new AllServicesEnabledPredicate()) .waitUntilReady(this); if (enabledServices.contains(CouchbaseService.QUERY)) { @@ -450,4 +452,38 @@ private Response doHttpRequest(final int port, final String path, final String m throw new RuntimeException("Could not perform request against couchbase HTTP endpoint ", ex); } } + + /** + * In addition to getting a 200, we need to make sure that all services we need are enabled and available on + * the bucket. + *

+ * Fixes the issue observed in https://github.com/testcontainers/testcontainers-java/issues/2993 + */ + private class AllServicesEnabledPredicate implements Predicate { + + @Override + public boolean test(final String rawConfig) { + try { + for (JsonNode node : MAPPER.readTree(rawConfig).at("/nodesExt")) { + for (CouchbaseService enabledService : enabledServices) { + boolean found = false; + Iterator fieldNames = node.get("services").fieldNames(); + while (fieldNames.hasNext()) { + if (fieldNames.next().startsWith(enabledService.getIdentifier())) { + found = true; + } + } + if (!found) { + logger().trace("Service {} not yet part of config, retrying.", enabledService); + return false; + } + } + } + return true; + } catch (IOException ex) { + logger().error("Unable to parse response: " + rawConfig, ex); + return false; + } + } + } } diff --git a/modules/couchbase/src/main/java/org/testcontainers/couchbase/CouchbaseService.java b/modules/couchbase/src/main/java/org/testcontainers/couchbase/CouchbaseService.java index d284c660709..c60fc6b71d0 100644 --- a/modules/couchbase/src/main/java/org/testcontainers/couchbase/CouchbaseService.java +++ b/modules/couchbase/src/main/java/org/testcontainers/couchbase/CouchbaseService.java @@ -24,21 +24,30 @@ public enum CouchbaseService { /** * Key-Value service. */ - KV, + KV("kv"), /** * Query (N1QL) service. */ - QUERY, + QUERY("n1ql"), /** * Search (FTS) service. */ - SEARCH, + SEARCH("fts"), /** * Indexing service (needed if QUERY is also used!). */ - INDEX + INDEX("index"); + private final String identifier; + + CouchbaseService(String identifier) { + this.identifier = identifier; + } + + String getIdentifier() { + return identifier; + } }