From 2763c1dd059bb5c4af0cc620bcc7a47b21df7774 Mon Sep 17 00:00:00 2001 From: Sergei Egorov Date: Sun, 12 Apr 2020 20:23:51 +0200 Subject: [PATCH] Rewrite Couchbase module. closes #2447 (#2491) * Rewrite couchbase module. closes #2447 This changeset completely reworks the couchbase module and hopefully greatly improve the out-of-the-box experience. Note that this is a breaking change over the previous code because by intention it does NOT depend on SDK 2 so you can test SDK 2 and 3 with it at the same time. Highlights: - Removed the need for a SDK, so both 2 and 3 can be used with it. - Updated to 6.5.0 baseline and using alternate addresses for "proper" port exposure without having to rely on the socat proxy container like the previous version had to. - Allows to define which services should be exposed and handles states automatically (i.e. will not try to create the primary index if the query service is not enabled). Note that a bunch of tests have been removed since they are not adequate anymore. A side effect of the alternate address change is that older servers cannot be used. 6.5.0 is available in both CE and EE, and Couchbase in general allows EE versions to be used in development and testing so we should use it if we can. * Wait until query respsonds with 200 * fixes * add `getBootstrap*DirectPort()` methods, review fixes * Restore `getConnectionString()` * Apply suggestions from code review Co-Authored-By: Kevin Wittek * Update docs * Update docs (2) Co-authored-by: Michael Nitschinger Co-authored-by: Kevin Wittek --- docs/modules/databases/couchbase.md | 78 +- modules/couchbase/build.gradle | 3 +- .../couchbase/AbstractCouchbaseTest.java | 72 -- .../couchbase/BucketDefinition.java | 69 ++ .../couchbase/CouchbaseContainer.java | 683 +++++++++--------- .../CouchbaseQueryServiceWaitStrategy.java | 55 -- .../couchbase/CouchbaseService.java | 44 ++ .../couchbase/BaseCouchbaseContainerTest.java | 82 --- .../couchbase/Couchbase4_6Test.java | 14 - .../couchbase/Couchbase5_1Test.java | 14 - .../couchbase/Couchbase5_5Test.java | 14 - .../couchbase/CouchbaseCommunity5_1Test.java | 32 - .../couchbase/CouchbaseContainerTest.java | 78 +- 13 files changed, 530 insertions(+), 708 deletions(-) delete mode 100644 modules/couchbase/src/main/java/org/testcontainers/couchbase/AbstractCouchbaseTest.java create mode 100644 modules/couchbase/src/main/java/org/testcontainers/couchbase/BucketDefinition.java delete mode 100644 modules/couchbase/src/main/java/org/testcontainers/couchbase/CouchbaseQueryServiceWaitStrategy.java create mode 100644 modules/couchbase/src/main/java/org/testcontainers/couchbase/CouchbaseService.java delete mode 100644 modules/couchbase/src/test/java/org/testcontainers/couchbase/BaseCouchbaseContainerTest.java delete mode 100644 modules/couchbase/src/test/java/org/testcontainers/couchbase/Couchbase4_6Test.java delete mode 100644 modules/couchbase/src/test/java/org/testcontainers/couchbase/Couchbase5_1Test.java delete mode 100644 modules/couchbase/src/test/java/org/testcontainers/couchbase/Couchbase5_5Test.java delete mode 100644 modules/couchbase/src/test/java/org/testcontainers/couchbase/CouchbaseCommunity5_1Test.java diff --git a/docs/modules/databases/couchbase.md b/docs/modules/databases/couchbase.md index f6d014a42d4..b71ee5de327 100644 --- a/docs/modules/databases/couchbase.md +++ b/docs/modules/databases/couchbase.md @@ -8,65 +8,25 @@ Testcontainers module for Couchbase. [Couchbase](https://www.couchbase.com/) is Running Couchbase as a stand-in in a test: -### Create your own bucket - -```java -public class SomeTest { - - @Rule - public CouchbaseContainer couchbase = new CouchbaseContainer() - .withClusterAdmin("admin", "secret") - .withNewBucket(DefaultBucketSettings.builder() - .enableFlush(true) - .name("bucket-name") - .password("secret") - .quota(100) - .type(BucketType.COUCHBASE) - .build()); - - @Test - public void someTestMethod() { - Bucket bucket = couchbase.getCouchbaseCluster().openBucket("bucket-name"); - - // ... interact with client as if using Couchbase normally - } -} -``` - -### Use preconfigured default bucket - -Bucket is cleared after each test - -```java -public class SomeTest extends AbstractCouchbaseTest { - - @Test - public void someTestMethod() { - Bucket bucket = getBucket(); - - // ... interact with client as if using Couchbase normally - } -} -``` - -### Special consideration - -Couchbase container is configured to use random available [ports](https://developer.couchbase.com/documentation/server/current/install/install-ports.html) for some ports only, as [Couchbase Java SDK](https://developer.couchbase.com/documentation/server/current/sdk/java/start-using-sdk.html) permit to configure only some ports: - -- **8091** : REST/HTTP traffic ([bootstrapHttpDirectPort](http://docs.couchbase.com/sdk-api/couchbase-java-client-2.4.6/com/couchbase/client/java/env/DefaultCouchbaseEnvironment.Builder.html#bootstrapCarrierDirectPort-int-)) -- **18091** : REST/HTTP traffic with SSL ([bootstrapHttpSslPort](http://docs.couchbase.com/sdk-api/couchbase-java-client-2.4.6/com/couchbase/client/java/env/DefaultCouchbaseEnvironment.Builder.html#bootstrapCarrierSslPort-int-)) -- **11210** : memcached ([bootstrapCarrierDirectPort](http://docs.couchbase.com/sdk-api/couchbase-java-client-2.4.6/com/couchbase/client/java/env/DefaultCouchbaseEnvironment.Builder.html#bootstrapCarrierDirectPort-int-)) -- **11207** : memcached SSL ([bootstrapCarrierSslPort](http://docs.couchbase.com/sdk-api/couchbase-java-client-2.4.6/com/couchbase/client/java/env/DefaultCouchbaseEnvironment.Builder.html#bootstrapCarrierSslPort-int-)) - -All other ports cannot be changed by Java SDK, there are sadly fixed: - -- **8092** : Queries, views, XDCR -- **8093** : REST/HTTP Query service -- **8094** : REST/HTTP Search Service -- **8095** : REST/HTTP Analytic service - -So if you disable Query, Search and Analytic service, you can run multiple instance of this container, otherwise, you're stuck with one instance, for now. - +1. Define a bucket: + + [Bucket Definition](../../../modules/couchbase/src/test/java/org/testcontainers/couchbase/CouchbaseContainerTest.java) inside_block:bucket_definition + + +2. define a container: + + [Container definition](../../../modules/couchbase/src/test/java/org/testcontainers/couchbase/CouchbaseContainerTest.java) inside_block:container_definition + + +3. create an environment & cluster: + + [Cluster creation](../../../modules/couchbase/src/test/java/org/testcontainers/couchbase/CouchbaseContainerTest.java) inside_block:cluster_creation + + +4. authenticate: + + [Authentication](../../../modules/couchbase/src/test/java/org/testcontainers/couchbase/CouchbaseContainerTest.java) inside_block:auth + ## Adding this module to your project dependencies diff --git a/modules/couchbase/build.gradle b/modules/couchbase/build.gradle index 66f29fc4718..095f8828aac 100644 --- a/modules/couchbase/build.gradle +++ b/modules/couchbase/build.gradle @@ -2,7 +2,6 @@ description = "Testcontainers :: Couchbase" dependencies { compile project(':testcontainers') - compile 'com.couchbase.client:java-client:2.7.13' - testCompile project(':test-support') + testCompile 'com.couchbase.client:java-client:2.7.13' } diff --git a/modules/couchbase/src/main/java/org/testcontainers/couchbase/AbstractCouchbaseTest.java b/modules/couchbase/src/main/java/org/testcontainers/couchbase/AbstractCouchbaseTest.java deleted file mode 100644 index 7fd99ca9fbc..00000000000 --- a/modules/couchbase/src/main/java/org/testcontainers/couchbase/AbstractCouchbaseTest.java +++ /dev/null @@ -1,72 +0,0 @@ -package org.testcontainers.couchbase; - -import com.couchbase.client.java.Bucket; -import com.couchbase.client.java.CouchbaseCluster; -import com.couchbase.client.java.bucket.BucketType; -import com.couchbase.client.java.cluster.BucketSettings; -import com.couchbase.client.java.cluster.DefaultBucketSettings; -import com.couchbase.client.java.query.N1qlParams; -import com.couchbase.client.java.query.N1qlQuery; -import com.couchbase.client.java.query.consistency.ScanConsistency; -import org.junit.After; - -import java.util.concurrent.TimeUnit; - -/** - * Basic class that can be used for couchbase tests. It will clear the database after every test. - */ -public abstract class AbstractCouchbaseTest { - - public static final String TEST_BUCKET = "test"; - - public static final String DEFAULT_PASSWORD = "password"; - - Bucket bucket; - - @After - public void clear() { - try { - if (getCouchbaseContainer().isIndex() && getCouchbaseContainer().isQuery() && getCouchbaseContainer().isPrimaryIndex()) { - getBucket().query( - N1qlQuery.simple(String.format("DELETE FROM `%s`", getBucket().name()), - N1qlParams.build().consistency(ScanConsistency.STATEMENT_PLUS))); - } else { - getBucket().bucketManager().flush(); - } - } finally { - bucket.close(60, TimeUnit.SECONDS); - bucket = null; - } - } - - protected abstract CouchbaseContainer getCouchbaseContainer(); - - protected static CouchbaseContainer initCouchbaseContainer(String imageName) { - CouchbaseContainer couchbaseContainer = (imageName == null) ? new CouchbaseContainer() : new CouchbaseContainer(imageName); - couchbaseContainer.withNewBucket(getDefaultBucketSettings()); - return couchbaseContainer; - } - - protected static BucketSettings getDefaultBucketSettings() { - return DefaultBucketSettings.builder() - .enableFlush(true) - .name(TEST_BUCKET) - .password(DEFAULT_PASSWORD) - .quota(100) - .replicas(0) - .type(BucketType.COUCHBASE) - .build(); - } - - protected synchronized Bucket getBucket() { - if (bucket == null) { - bucket = openBucket(TEST_BUCKET, DEFAULT_PASSWORD); - } - return bucket; - } - - private Bucket openBucket(String bucketName, String password) { - CouchbaseCluster cluster = getCouchbaseContainer().getCouchbaseCluster(); - return cluster.openBucket(bucketName, password); - } -} diff --git a/modules/couchbase/src/main/java/org/testcontainers/couchbase/BucketDefinition.java b/modules/couchbase/src/main/java/org/testcontainers/couchbase/BucketDefinition.java new file mode 100644 index 00000000000..8c2b11403ad --- /dev/null +++ b/modules/couchbase/src/main/java/org/testcontainers/couchbase/BucketDefinition.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2020 Couchbase, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.testcontainers.couchbase; + +/** + * Allows to configure the properties of a bucket that should be created. + */ +public class BucketDefinition { + + private final String name; + private boolean queryPrimaryIndex = true; + private int quota = 100; + + public BucketDefinition(final String name) { + this.name = name; + } + + /** + * Sets a custom bucket quota (100MB by default). + * + * @param quota the quota to set for the bucket. + * @return this {@link BucketDefinition} for chaining purposes. + */ + public BucketDefinition withQuota(final int quota) { + if (quota < 100) { + throw new IllegalArgumentException("Bucket quota cannot be less than 100MB!"); + } + this.quota = quota; + return this; + } + + /** + * Allows to disable creating a primary index for this bucket (enabled by default). + * + * @param create if false, a primary index will not be created. + * @return this {@link BucketDefinition} for chaining purposes. + */ + public BucketDefinition withPrimaryIndex(final boolean create) { + this.queryPrimaryIndex = create; + return this; + } + + public String getName() { + return name; + } + + public boolean hasPrimaryIndex() { + return queryPrimaryIndex; + } + + public int getQuota() { + return quota; + } + +} 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 e43cf8f652d..14e5744bb6d 100644 --- a/modules/couchbase/src/main/java/org/testcontainers/couchbase/CouchbaseContainer.java +++ b/modules/couchbase/src/main/java/org/testcontainers/couchbase/CouchbaseContainer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Couchbase, Inc. + * Copyright (c) 2020 Couchbase, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,447 +15,424 @@ */ package org.testcontainers.couchbase; -import com.couchbase.client.core.utils.Base64; -import com.couchbase.client.java.Bucket; -import com.couchbase.client.java.CouchbaseCluster; -import com.couchbase.client.java.cluster.*; -import com.couchbase.client.java.env.CouchbaseEnvironment; -import com.couchbase.client.java.env.DefaultCouchbaseEnvironment; -import com.couchbase.client.java.query.Index; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.dockerjava.api.command.ExecCreateCmdResponse; import com.github.dockerjava.api.command.InspectContainerResponse; -import com.google.common.collect.Lists; -import lombok.*; -import org.apache.commons.compress.utils.Sets; -import org.apache.commons.io.IOUtils; -import org.jetbrains.annotations.NotNull; +import com.github.dockerjava.api.model.ContainerNetwork; +import okhttp3.Credentials; +import okhttp3.FormBody; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; import org.testcontainers.containers.GenericContainer; -import org.testcontainers.containers.Network; -import org.testcontainers.containers.SocatContainer; import org.testcontainers.containers.wait.strategy.HttpWaitStrategy; -import org.testcontainers.images.builder.Transferable; -import org.testcontainers.utility.ThrowingFunction; +import org.testcontainers.containers.wait.strategy.WaitAllStrategy; -import java.io.DataOutputStream; import java.io.IOException; -import java.net.HttpURLConnection; -import java.net.URL; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static java.net.HttpURLConnection.HTTP_OK; -import static org.testcontainers.couchbase.CouchbaseContainer.CouchbasePort.*; /** - * Based on Laurent Doguin version, + * The couchbase container initializes and configures a Couchbase Server single node cluster. *

- * optimized by Tayeb Chlyah + * Note that it does not depend on a specific couchbase SDK, so it can be used with both the Java SDK 2 and 3 as well + * as the Scala SDK 1 or newer. We recommend using the latest and greatest SDKs for the best experience. */ -@AllArgsConstructor public class CouchbaseContainer extends GenericContainer { - public static final String VERSION = "5.5.1"; - public static final String DOCKER_IMAGE_NAME = "couchbase/server:"; - public static final ObjectMapper MAPPER = new ObjectMapper(); - public static final String STATIC_CONFIG = "/opt/couchbase/etc/couchbase/static_config"; - public static final String CAPI_CONFIG = "/opt/couchbase/etc/couchdb/default.d/capi.ini"; + private static final int MGMT_PORT = 8091; + + private static final int MGMT_SSL_PORT = 18091; + + private static final int VIEW_PORT = 8092; - private static final int REQUIRED_DEFAULT_PASSWORD_LENGTH = 6; + private static final int VIEW_SSL_PORT = 18092; - private String memoryQuota = "300"; + private static final int QUERY_PORT = 8093; - private String indexMemoryQuota = "300"; + private static final int QUERY_SSL_PORT = 18093; - private String clusterUsername = "Administrator"; + private static final int SEARCH_PORT = 8094; - private String clusterPassword = "password"; + private static final int SEARCH_SSL_PORT = 18094; - private boolean keyValue = true; + private static final int KV_PORT = 11210; - @Getter - private boolean query = true; + private static final int KV_SSL_PORT = 11207; - @Getter - private boolean index = true; + private static final String DOCKER_IMAGE_NAME = "couchbase/server"; - @Getter - private boolean primaryIndex = true; + private static final String VERSION = "6.5.0"; - @Getter - private boolean fts = false; + private static final ObjectMapper MAPPER = new ObjectMapper(); - @Getter(lazy = true) - private final CouchbaseEnvironment couchbaseEnvironment = createCouchbaseEnvironment(); + private static final OkHttpClient HTTP_CLIENT = new OkHttpClient(); - @Getter(lazy = true) - private final CouchbaseCluster couchbaseCluster = createCouchbaseCluster(); + private String username = "Administrator"; - private List newBuckets = new ArrayList<>(); + private String password = "password"; - private String urlBase; + private Set enabledServices = EnumSet.allOf(CouchbaseService.class); - private SocatContainer proxy; + private final List buckets = new ArrayList<>(); + /** + * Creates a new couchbase container with the default image and version. + */ public CouchbaseContainer() { - this(DOCKER_IMAGE_NAME + VERSION); + this(DOCKER_IMAGE_NAME + ":" + VERSION); } - public CouchbaseContainer(String imageName) { + /** + * Creates a new couchbase container with a custom image name. + * + * @param imageName the image name that should be used. + */ + public CouchbaseContainer(final String imageName) { super(imageName); - - withNetwork(Network.SHARED); - setWaitStrategy(new HttpWaitStrategy().forPath("/ui/index.html")); } - @Override - public Set getLivenessCheckPortNumbers() { - return Sets.newHashSet(getMappedPort(REST)); + /** + * Set custom username and password for the admin user. + * + * @param username the admin username to use. + * @param password the password for the admin user. + * @return this {@link CouchbaseContainer} for chaining purposes. + */ + public CouchbaseContainer withCredentials(final String username, final String password) { + checkNotRunning(); + this.username = username; + this.password = password; + return this; } - @Override - protected void configure() { - if (clusterPassword.length() < REQUIRED_DEFAULT_PASSWORD_LENGTH) { - logger().warn("The provided cluster admin password length is less then the default password policy length. " + - "Cluster start will fail if configured password requirements are not met."); - } + public CouchbaseContainer withBucket(final BucketDefinition bucketDefinition) { + checkNotRunning(); + this.buckets.add(bucketDefinition); + return this; } - @Override - @SneakyThrows - protected void doStart() { - startProxy(getNetworkAliases().get(0)); - try { - super.doStart(); - } catch (Throwable e) { - proxy.stop(); - throw e; - } + public CouchbaseContainer withEnabledServices(final CouchbaseService... enabled) { + checkNotRunning(); + this.enabledServices = EnumSet.copyOf(Arrays.asList(enabled)); + return this; } - @SneakyThrows - private void startProxy(String networkAlias) { - proxy = new SocatContainer().withNetwork(getNetwork()); - - for (CouchbasePort port : CouchbasePort.values()) { - if (port.isDynamic()) { - proxy.withTarget(port.getOriginalPort(), networkAlias); - } else { - proxy.addExposedPort(port.getOriginalPort()); - } - } - - proxy.setWaitStrategy(null); - proxy.start(); - - ExecCreateCmdResponse createCmdResponse = dockerClient - .execCreateCmd(proxy.getContainerId()) - .withCmd( - "sh", - "-c", - Stream.of(CouchbasePort.values()) - .map(port -> { - return "/usr/bin/socat " + - "TCP-LISTEN:" + port.getOriginalPort() + ",fork,reuseaddr " + - "TCP:" + networkAlias + ":" + getMappedPort(port); - }) - .collect(Collectors.joining(" & ", "true", "")) - ) - .exec(); - - dockerClient.execStartCmd(createCmdResponse.getId()) - .start() - .awaitCompletion(10, TimeUnit.SECONDS); + public final String getUsername() { + return username; } - @Override - public List getExposedPorts() { - return proxy.getExposedPorts(); + public final String getPassword() { + return password; } - @Override - public String getContainerIpAddress() { - return proxy.getContainerIpAddress(); + public int getBootstrapCarrierDirectPort() { + return getMappedPort(KV_PORT); } - @Override - public Integer getMappedPort(int originalPort) { - return proxy.getMappedPort(originalPort); + public int getBootstrapHttpDirectPort() { + return getMappedPort(MGMT_PORT); } - protected Integer getMappedPort(CouchbasePort port) { - return getMappedPort(port.getOriginalPort()); + public String getConnectionString() { + return String.format("couchbase://%s:%d", getContainerIpAddress(), getBootstrapCarrierDirectPort()); } @Override - public List getBoundPortNumbers() { - return proxy.getBoundPortNumbers(); - } - - @Override - @SuppressWarnings({"unchecked", "ConstantConditions"}) - public void stop() { - try { - stopCluster(); - ((AtomicReference) (Object) couchbaseEnvironment).set(null); - ((AtomicReference) (Object) couchbaseCluster).set(null); - } finally { - Stream.of(super::stop, proxy::stop).parallel().forEach(Runnable::run); + protected void configure() { + super.configure(); + + WaitAllStrategy waitStrategy = new WaitAllStrategy(); + + // Makes sure that all nodes in the cluster are healthy. + waitStrategy = waitStrategy.withStrategy( + new HttpWaitStrategy() + .forPath("/pools/default") + .forPort(MGMT_PORT) + .withBasicCredentials(username, password) + .forStatusCode(200) + .forResponsePredicate(response -> { + try { + return Optional.of(MAPPER.readTree(response)) + .map(n -> n.at("/nodes/0/status")) + .map(JsonNode::asText) + .map("healthy"::equals) + .orElse(false); + } catch (IOException e) { + logger().error("Unable to parse response {}", response); + return false; + } + }) + ); + + if (enabledServices.contains(CouchbaseService.QUERY)) { + waitStrategy = waitStrategy.withStrategy( + new HttpWaitStrategy() + .forPath("/admin/ping") + .forPort(QUERY_PORT) + .withBasicCredentials(username, password) + .forStatusCode(200) + ); } - } - - private void stopCluster() { - getCouchbaseCluster().disconnect(); - getCouchbaseEnvironment().shutdown(); - } - - public CouchbaseContainer withNewBucket(BucketSettings bucketSettings) { - newBuckets.add(new BucketAndUserSettings(bucketSettings)); - return self(); - } - public CouchbaseContainer withNewBucket(BucketSettings bucketSettings, UserSettings userSettings) { - newBuckets.add(new BucketAndUserSettings(bucketSettings, userSettings)); - return self(); + waitingFor(waitStrategy); } - @SneakyThrows - public void initCluster() { - urlBase = String.format("http://%s:%s", getContainerIpAddress(), getMappedPort(REST)); - String poolURL = "/pools/default"; - String poolPayload = "memoryQuota=" + URLEncoder.encode(memoryQuota, "UTF-8") + "&indexMemoryQuota=" + URLEncoder.encode(indexMemoryQuota, "UTF-8"); - - String setupServicesURL = "/node/controller/setupServices"; - StringBuilder servicePayloadBuilder = new StringBuilder(); - if (keyValue) { - servicePayloadBuilder.append("kv,"); - } - if (query) { - servicePayloadBuilder.append("n1ql,"); - } - if (index) { - servicePayloadBuilder.append("index,"); - } - if (fts) { - servicePayloadBuilder.append("fts,"); + @Override + protected void containerIsStarting(final InspectContainerResponse containerInfo) { + logger().debug("Couchbase container is starting, performing configuration."); + + waitUntilNodeIsOnline(); + renameNode(); + initializeServices(); + configureAdminUser(); + configureExternalPorts(); + if (enabledServices.contains(CouchbaseService.INDEX)) { + configureIndexer(); } - String setupServiceContent = "services=" + URLEncoder.encode(servicePayloadBuilder.toString(), "UTF-8"); - - String webSettingsURL = "/settings/web"; - String webSettingsContent = "username=" + URLEncoder.encode(clusterUsername, "UTF-8") + "&password=" + URLEncoder.encode(clusterPassword, "UTF-8") + "&port=8091"; - - callCouchbaseRestAPI(poolURL, poolPayload); - callCouchbaseRestAPI(setupServicesURL, setupServiceContent); - callCouchbaseRestAPI(webSettingsURL, webSettingsContent); - - createNodeWaitStrategy().waitUntilReady(this); - callCouchbaseRestAPI("/settings/indexes", "indexerThreads=0&logLevel=info&maxRollbackPoints=5&storageMode=memory_optimized"); } - @NotNull - private HttpWaitStrategy createNodeWaitStrategy() { - return new HttpWaitStrategy() - .forPath("/pools/default/") - .withBasicCredentials(clusterUsername, clusterPassword) - .forStatusCode(HTTP_OK) - .forResponsePredicate(response -> { - try { - return Optional.of(MAPPER.readTree(response)) - .map(n -> n.at("/nodes/0/status")) - .map(JsonNode::asText) - .map("healthy"::equals) - .orElse(false); - } catch (IOException e) { - logger().error("Unable to parse response {}", response); - return false; - } - }); - } - - public void createBucket(BucketSettings bucketSetting, UserSettings userSettings, boolean primaryIndex) { - ClusterManager clusterManager = getCouchbaseCluster().clusterManager(clusterUsername, clusterPassword); - // Insert Bucket - BucketSettings bucketSettings = clusterManager.insertBucket(bucketSetting); - try { - // Insert Bucket user - clusterManager.upsertUser(AuthDomain.LOCAL, bucketSetting.name(), userSettings); - } catch (Exception e) { - logger().warn("Unable to insert user '" + bucketSetting.name() + "', maybe you are using older version"); - } - if (index) { - Bucket bucket = getCouchbaseCluster().openBucket(bucketSettings.name(), bucketSettings.password()); - new CouchbaseQueryServiceWaitStrategy(bucket).waitUntilReady(this); - if (primaryIndex) { - bucket.query(Index.createPrimaryIndex().on(bucketSetting.name())); + @Override + protected void containerIsStarted(InspectContainerResponse containerInfo) { + createBuckets(); + logger().info("Couchbase container is ready! UI available at http://{}:{}", getContainerIpAddress(), getMappedPort(MGMT_PORT)); + } + + /** + * Before we can start configuring the host, we need to wait until the cluster manager is listening. + */ + private void waitUntilNodeIsOnline() { + new HttpWaitStrategy() + .forPort(MGMT_PORT) + .forPath("/pools") + .forStatusCode(200) + .waitUntilReady(this); + } + + /** + * Rebinds/renames the internal hostname. + *

+ * To make sure the internal hostname is different from the external (alternate) address and the SDK can pick it + * up automatically, we bind the internal hostname to the internal IP address. + */ + private void renameNode() { + logger().debug("Renaming Couchbase Node from localhost to {}", getContainerIpAddress()); + + Response response = doHttpRequest(MGMT_PORT, "/node/controller/rename", "POST", new FormBody.Builder() + .add("hostname", getInternalIpAddress()) + .build(), false + ); + + checkSuccessfulResponse(response, "Could not rename couchbase node"); + } + + /** + * Initializes services based on the configured enabled services. + */ + 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(",")); + + Response response = doHttpRequest(MGMT_PORT, "/node/controller/setupServices", "POST", new FormBody.Builder() + .add("services", services) + .build(), false + ); + + checkSuccessfulResponse(response, "Could not enable couchbase services"); + } + + /** + * Configures the admin user on the couchbase node. + *

+ * After this stage, all subsequent API calls need to have the basic auth header set. + */ + private void configureAdminUser() { + logger().debug("Configuring couchbase admin user with username: \"{}\"", username); + + Response response = doHttpRequest(MGMT_PORT, "/settings/web", "POST", new FormBody.Builder() + .add("username", username) + .add("password", password) + .add("port", Integer.toString(MGMT_PORT)) + .build(), false); + + checkSuccessfulResponse(response, "Could not configure couchbase admin user"); + } + + /** + * Configures the external ports for SDK access. + *

+ * Since the internal ports are not accessible from outside the container, this code configures the "external" + * hostname and services to align with the mapped ports. The SDK will pick it up and then automatically connect + * to those ports. Note that for all services non-ssl and ssl ports are configured. + */ + private void configureExternalPorts() { + logger().debug("Mapping external ports to the alternate address configuration"); + + final FormBody.Builder builder = new FormBody.Builder(); + builder.add("hostname", getContainerIpAddress()); + builder.add("mgmt", Integer.toString(getMappedPort(MGMT_PORT))); + builder.add("mgmtSSL", Integer.toString(getMappedPort(MGMT_SSL_PORT))); + + if (enabledServices.contains(CouchbaseService.KV)) { + builder.add("kv", Integer.toString(getMappedPort(KV_PORT))); + builder.add("kvSSL", Integer.toString(getMappedPort(KV_SSL_PORT))); + builder.add("capi", Integer.toString(getMappedPort(VIEW_PORT))); + builder.add("capiSSL", Integer.toString(getMappedPort(VIEW_SSL_PORT))); } - } - public void callCouchbaseRestAPI(String url, String payload) throws IOException { - String fullUrl = urlBase + url; - @Cleanup("disconnect") - HttpURLConnection httpConnection = (HttpURLConnection) ((new URL(fullUrl).openConnection())); - httpConnection.setDoOutput(true); - httpConnection.setRequestMethod("POST"); - httpConnection.setRequestProperty("Content-Type", - "application/x-www-form-urlencoded"); - String encoded = Base64.encode((clusterUsername + ":" + clusterPassword).getBytes("UTF-8")); - httpConnection.setRequestProperty("Authorization", "Basic " + encoded); - @Cleanup - DataOutputStream out = new DataOutputStream(httpConnection.getOutputStream()); - out.writeBytes(payload); - out.flush(); - httpConnection.getResponseCode(); - } + if (enabledServices.contains(CouchbaseService.QUERY)) { + builder.add("n1ql", Integer.toString(getMappedPort(QUERY_PORT))); + builder.add("n1qlSSL", Integer.toString(getMappedPort(QUERY_SSL_PORT))); + } - @Override - protected void containerIsCreated(String containerId) { - patchConfig(STATIC_CONFIG, this::addMappedPorts); - // capi needs a special configuration, see https://developer.couchbase.com/documentation/server/current/install/install-ports.html - patchConfig(CAPI_CONFIG, this::replaceCapiPort); - } + if (enabledServices.contains(CouchbaseService.SEARCH)) { + builder.add("fts", Integer.toString(getMappedPort(SEARCH_PORT))); + builder.add("ftsSSL", Integer.toString(getMappedPort(SEARCH_SSL_PORT))); + } - private void patchConfig(String configLocation, ThrowingFunction patchFunction) { - String patchedConfig = copyFileFromContainer(configLocation, - inputStream -> patchFunction.apply(IOUtils.toString(inputStream, StandardCharsets.UTF_8))); - copyFileToContainer(Transferable.of(patchedConfig.getBytes(StandardCharsets.UTF_8)), configLocation); - } + final Response response = doHttpRequest( + MGMT_PORT, + "/node/controller/setupAlternateAddresses/external", + "PUT", + builder.build(), + true + ); - private String addMappedPorts(String originalConfig) { - String portConfig = Stream.of(CouchbasePort.values()) - .filter(port -> !port.isDynamic()) - .map(port -> String.format("{%s, %d}.", port.name, getMappedPort(port))) - .collect(Collectors.joining("\n")); - return String.format("%s\n%s", originalConfig, portConfig); + checkSuccessfulResponse(response, "Could not configure external ports"); } - private String replaceCapiPort(String originalConfig) { - return Arrays.stream(originalConfig.split("\n")) - .map(s -> (s.matches("port\\s*=\\s*" + CAPI.getOriginalPort())) ? "port = " + getMappedPort(CAPI) : s) - .collect(Collectors.joining("\n")); - } + /** + * Configures the indexer service so that indexes can be created later on the bucket. + */ + private void configureIndexer() { + logger().debug("Configuring the indexer service"); - @Override - protected void containerIsStarted(InspectContainerResponse containerInfo) { - if (!newBuckets.isEmpty()) { - for (BucketAndUserSettings bucket : newBuckets) { - createBucket(bucket.getBucketSettings(), bucket.getUserSettings(), primaryIndex); - } - } - } + Response response = doHttpRequest(MGMT_PORT, "/settings/indexes", "POST", new FormBody.Builder() + .add("storageMode", "memory_optimized") + .build(), true + ); - private CouchbaseCluster createCouchbaseCluster() { - return CouchbaseCluster.create(getCouchbaseEnvironment(), getContainerIpAddress()); + checkSuccessfulResponse(response, "Could not configure the indexing service"); } - private DefaultCouchbaseEnvironment createCouchbaseEnvironment() { - initCluster(); - return DefaultCouchbaseEnvironment.builder() - .kvTimeout(10000) - .bootstrapCarrierDirectPort(getMappedPort(MEMCACHED)) - .bootstrapCarrierSslPort(getMappedPort(MEMCACHED_SSL)) - .bootstrapHttpDirectPort(getMappedPort(REST)) - .bootstrapHttpSslPort(getMappedPort(REST_SSL)) - .build(); - } + /** + * Based on the user-configured bucket definitions, creating buckets and corresponding indexes if needed. + */ + private void createBuckets() { + logger().debug("Creating " + buckets.size() + " buckets (and corresponding indexes)."); - public CouchbaseContainer withMemoryQuota(String memoryQuota) { - this.memoryQuota = memoryQuota; - return self(); - } + for (BucketDefinition bucket : buckets) { + logger().debug("Creating bucket \"" + bucket.getName() + "\""); - public CouchbaseContainer withIndexMemoryQuota(String indexMemoryQuota) { - this.indexMemoryQuota = indexMemoryQuota; - return self(); - } + Response response = doHttpRequest(MGMT_PORT, "/pools/default/buckets", "POST", new FormBody.Builder() + .add("name", bucket.getName()) + .add("ramQuotaMB", Integer.toString(bucket.getQuota())) + .build(), true); - public CouchbaseContainer withClusterAdmin(String username, String password) { - this.clusterUsername = username; - this.clusterPassword = password; - return self(); - } + checkSuccessfulResponse(response, "Could not create bucket " + bucket.getName()); - public CouchbaseContainer withKeyValue(boolean keyValue) { - this.keyValue = keyValue; - return self(); - } + new HttpWaitStrategy() + .forPath("/pools/default/buckets/" + bucket.getName()) + .forPort(MGMT_PORT) + .withBasicCredentials(username, password) + .forStatusCode(200) + .waitUntilReady(this); - public CouchbaseContainer withQuery(boolean query) { - this.query = query; - return self(); - } + if (bucket.hasPrimaryIndex()) { + if (enabledServices.contains(CouchbaseService.QUERY)) { + Response queryResponse = doHttpRequest(QUERY_PORT, "/query/service", "POST", new FormBody.Builder() + .add("statement", "CREATE PRIMARY INDEX on `" + bucket.getName() + "`") + .build(), true); - public CouchbaseContainer withIndex(boolean index) { - this.index = index; - return self(); + checkSuccessfulResponse(queryResponse, "Could not create primary index for bucket " + bucket.getName()); + } else { + logger().info("Primary index creation for bucket " + bucket.getName() + " ignored, since QUERY service is not present."); + } + } + } } - public CouchbaseContainer withPrimaryIndex(boolean primaryIndex) { - this.primaryIndex = primaryIndex; - return self(); + /** + * Helper method to extract the internal IP address based on the network configuration. + */ + private String getInternalIpAddress() { + return getContainerInfo().getNetworkSettings().getNetworks().values().stream() + .findFirst() + .map(ContainerNetwork::getIpAddress) + .orElseThrow(() -> new IllegalStateException("No network available to extract the internal IP from!")); } - public CouchbaseContainer withFts(boolean fts) { - this.fts = fts; - return self(); + /** + * Helper method to check if the response is successful and release the body if needed. + * + * @param response the response to check. + * @param message the message that should be part of the exception of not successful. + */ + private void checkSuccessfulResponse(final Response response, final String message) { + try { + if (!response.isSuccessful()) { + throw new IllegalStateException(message + ": " + response.toString()); + } + } finally { + if (response.body() != null) { + response.body().close(); + } + } } - @Getter - @RequiredArgsConstructor - protected enum CouchbasePort { - REST("rest_port", 8091, true), - CAPI("capi_port", 8092, false), - QUERY("query_port", 8093, false), - FTS("fts_http_port", 8094, false), - CBAS("cbas_http_port", 8095, false), - EVENTING("eventing_http_port", 8096, false), - MEMCACHED_SSL("memcached_ssl_port", 11207, false), - MEMCACHED("memcached_port", 11210, false), - REST_SSL("ssl_rest_port", 18091, true), - CAPI_SSL("ssl_capi_port", 18092, false), - QUERY_SSL("ssl_query_port", 18093, false), - FTS_SSL("fts_ssl_port", 18094, false), - CBAS_SSL("cbas_ssl_port", 18095, false), - EVENTING_SSL("eventing_ssl_port", 18096, false); - - final String name; - - final int originalPort; - - final boolean dynamic; + /** + * Checks if already running and if so raises an exception to prevent too-late setters. + */ + private void checkNotRunning() { + if (isRunning()) { + throw new IllegalStateException("Setter can only be called before the container is running"); + } } - @Value - @AllArgsConstructor - private class BucketAndUserSettings { + /** + * Helper method to perform a request against a couchbase server HTTP endpoint. + * + * @param port the (unmapped) original port that should be used. + * @param path the relative http path. + * @param method the http method to use. + * @param body if present, will be part of the payload. + * @param auth if authentication with the admin user and password should be used. + * @return the response of the request. + */ + private Response doHttpRequest(final int port, final String path, final String method, final RequestBody body, + final boolean auth) { + try { + Request.Builder requestBuilder = new Request.Builder() + .url("http://" + getContainerIpAddress() + ":" + getMappedPort(port) + path); - private final BucketSettings bucketSettings; - private final UserSettings userSettings; + if (auth) { + requestBuilder = requestBuilder.header("Authorization", Credentials.basic(username, password)); + } - public BucketAndUserSettings(final BucketSettings bucketSettings) { - this.bucketSettings = bucketSettings; - this.userSettings = UserSettings.build() - .password(bucketSettings.password()) - .roles(getDefaultAdminRoles(bucketSettings.name())); - } + if (body == null) { + requestBuilder = requestBuilder.get(); + } else { + requestBuilder = requestBuilder.method(method, body); + } - private List getDefaultAdminRoles(String bucketName) { - return Lists.newArrayList( - new UserRole("bucket_admin", bucketName), - new UserRole("views_admin", bucketName), - new UserRole("query_manage_index", bucketName), - new UserRole("query_update", bucketName), - new UserRole("query_select", bucketName), - new UserRole("query_insert", bucketName), - new UserRole("query_delete", bucketName) - ); + return HTTP_CLIENT.newCall(requestBuilder.build()).execute(); + } catch (Exception ex) { + throw new RuntimeException("Could not perform request against couchbase HTTP endpoint ", ex); } - } } diff --git a/modules/couchbase/src/main/java/org/testcontainers/couchbase/CouchbaseQueryServiceWaitStrategy.java b/modules/couchbase/src/main/java/org/testcontainers/couchbase/CouchbaseQueryServiceWaitStrategy.java deleted file mode 100644 index 853dd7d8eb7..00000000000 --- a/modules/couchbase/src/main/java/org/testcontainers/couchbase/CouchbaseQueryServiceWaitStrategy.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.testcontainers.couchbase; - -import com.couchbase.client.core.message.cluster.GetClusterConfigRequest; -import com.couchbase.client.core.message.cluster.GetClusterConfigResponse; -import com.couchbase.client.core.service.ServiceType; -import com.couchbase.client.java.Bucket; -import lombok.extern.slf4j.Slf4j; -import org.rnorth.ducttape.TimeoutException; -import org.testcontainers.containers.ContainerLaunchException; -import org.testcontainers.containers.wait.strategy.AbstractWaitStrategy; - -import java.time.Duration; -import java.util.concurrent.TimeUnit; - -import static org.rnorth.ducttape.unreliables.Unreliables.retryUntilSuccess; - -/** - * @author ctayeb - * Created on 06/06/2017 - */ -@Slf4j -public class CouchbaseQueryServiceWaitStrategy extends AbstractWaitStrategy { - - private final Bucket bucket; - - public CouchbaseQueryServiceWaitStrategy(Bucket bucket) { - this.bucket = bucket; - startupTimeout = Duration.ofSeconds(120); - } - - @Override - protected void waitUntilReady() { - log.info("Waiting for {} seconds for QUERY service", startupTimeout.getSeconds()); - - // try to connect to the URL - try { - retryUntilSuccess((int) startupTimeout.getSeconds(), TimeUnit.SECONDS, () -> { - getRateLimiter().doWhenReady(() -> { - GetClusterConfigResponse clusterConfig = bucket.core() - .send(new GetClusterConfigRequest()) - .toBlocking().single(); - boolean queryServiceEnabled = clusterConfig.config() - .bucketConfig(bucket.name()) - .serviceEnabled(ServiceType.QUERY); - if (!queryServiceEnabled) { - throw new ContainerLaunchException("Query service not ready yet"); - } - }); - return true; - }); - } catch (TimeoutException e) { - throw new ContainerLaunchException("Timed out waiting for QUERY service"); - } - } -} diff --git a/modules/couchbase/src/main/java/org/testcontainers/couchbase/CouchbaseService.java b/modules/couchbase/src/main/java/org/testcontainers/couchbase/CouchbaseService.java new file mode 100644 index 00000000000..d284c660709 --- /dev/null +++ b/modules/couchbase/src/main/java/org/testcontainers/couchbase/CouchbaseService.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020 Couchbase, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.testcontainers.couchbase; + +/** + * Describes the different services that should be exposed on the container. + */ +public enum CouchbaseService { + + /** + * Key-Value service. + */ + KV, + + /** + * Query (N1QL) service. + */ + QUERY, + + /** + * Search (FTS) service. + */ + SEARCH, + + /** + * Indexing service (needed if QUERY is also used!). + */ + INDEX + +} diff --git a/modules/couchbase/src/test/java/org/testcontainers/couchbase/BaseCouchbaseContainerTest.java b/modules/couchbase/src/test/java/org/testcontainers/couchbase/BaseCouchbaseContainerTest.java deleted file mode 100644 index ef2f0925c48..00000000000 --- a/modules/couchbase/src/test/java/org/testcontainers/couchbase/BaseCouchbaseContainerTest.java +++ /dev/null @@ -1,82 +0,0 @@ -package org.testcontainers.couchbase; - -import com.couchbase.client.java.document.RawJsonDocument; -import com.couchbase.client.java.query.N1qlQuery; -import com.couchbase.client.java.query.N1qlQueryResult; -import com.couchbase.client.java.query.N1qlQueryRow; -import com.couchbase.client.java.view.DefaultView; -import com.couchbase.client.java.view.DesignDocument; -import com.couchbase.client.java.view.View; -import com.google.common.collect.Lists; -import org.junit.Assert; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.RuleChain; -import org.junit.runner.Description; -import org.testcontainers.containers.FailureDetectingExternalResource; -import org.testcontainers.testsupport.Flaky; -import org.testcontainers.testsupport.FlakyTestJUnit4RetryRule; - -import java.util.List; - -public abstract class BaseCouchbaseContainerTest extends AbstractCouchbaseTest { - - private static final String VIEW_NAME = "testview"; - private static final String VIEW_FUNCTION = "function (doc, meta) {\n" + - " emit(meta.id, null);\n" + - "}"; - - - private static final String ID = "toto"; - - private static final String DOCUMENT = "{\"name\":\"toto\"}"; - - @Rule - public RuleChain ruleChain = RuleChain.emptyRuleChain() - .around(new FlakyTestJUnit4RetryRule()) - .around(new FailureDetectingExternalResource() { - @Override - protected void starting(Description description) { - getCouchbaseContainer().start(); - } - - @Override - protected void failed(Throwable e, Description description) { - getCouchbaseContainer().stop(); - } - }); - - @Test - @Flaky(githubIssueUrl = "https://github.com/testcontainers/testcontainers-java/issues/1453", reviewDate = "2020-03-01") - public void shouldInsertDocument() { - RawJsonDocument expected = RawJsonDocument.create(ID, DOCUMENT); - getBucket().upsert(expected); - RawJsonDocument result = getBucket().get(ID, RawJsonDocument.class); - Assert.assertEquals(expected.content(), result.content()); - } - - @Test - @Flaky(githubIssueUrl = "https://github.com/testcontainers/testcontainers-java/issues/1453", reviewDate = "2020-03-01") - public void shouldExecuteN1ql() { - getBucket().query(N1qlQuery.simple("INSERT INTO " + TEST_BUCKET + " (KEY, VALUE) VALUES ('" + ID + "', " + DOCUMENT + ")")); - - N1qlQueryResult query = getBucket().query(N1qlQuery.simple("SELECT * FROM " + TEST_BUCKET + " USE KEYS '" + ID + "'")); - Assert.assertTrue(query.parseSuccess()); - Assert.assertTrue(query.finalSuccess()); - List n1qlQueryRows = query.allRows(); - Assert.assertEquals(1, n1qlQueryRows.size()); - Assert.assertEquals(DOCUMENT, n1qlQueryRows.get(0).value().get(TEST_BUCKET).toString()); - } - - @Test - @Flaky(githubIssueUrl = "https://github.com/testcontainers/testcontainers-java/issues/1453", reviewDate = "2020-03-01") - public void shouldCreateView() { - View view = DefaultView.create(VIEW_NAME, VIEW_FUNCTION); - DesignDocument document = DesignDocument.create(VIEW_NAME, Lists.newArrayList(view)); - getBucket().bucketManager().insertDesignDocument(document); - DesignDocument result = getBucket().bucketManager().getDesignDocument(VIEW_NAME); - Assert.assertEquals(1, result.views().size()); - View resultView = result.views().get(0); - Assert.assertEquals(VIEW_NAME, resultView.name()); - } -} diff --git a/modules/couchbase/src/test/java/org/testcontainers/couchbase/Couchbase4_6Test.java b/modules/couchbase/src/test/java/org/testcontainers/couchbase/Couchbase4_6Test.java deleted file mode 100644 index c43d3c981ba..00000000000 --- a/modules/couchbase/src/test/java/org/testcontainers/couchbase/Couchbase4_6Test.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.testcontainers.couchbase; - -import org.junit.Rule; - -public class Couchbase4_6Test extends BaseCouchbaseContainerTest { - - @Rule - public CouchbaseContainer container = initCouchbaseContainer("couchbase/server:4.6.5"); - - @Override - public CouchbaseContainer getCouchbaseContainer() { - return container; - } -} diff --git a/modules/couchbase/src/test/java/org/testcontainers/couchbase/Couchbase5_1Test.java b/modules/couchbase/src/test/java/org/testcontainers/couchbase/Couchbase5_1Test.java deleted file mode 100644 index 2c3dbf3c901..00000000000 --- a/modules/couchbase/src/test/java/org/testcontainers/couchbase/Couchbase5_1Test.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.testcontainers.couchbase; - -import org.junit.Rule; - -public class Couchbase5_1Test extends BaseCouchbaseContainerTest { - - @Rule - public CouchbaseContainer container = initCouchbaseContainer("couchbase/server:5.1.1"); - - @Override - public CouchbaseContainer getCouchbaseContainer() { - return container; - } -} diff --git a/modules/couchbase/src/test/java/org/testcontainers/couchbase/Couchbase5_5Test.java b/modules/couchbase/src/test/java/org/testcontainers/couchbase/Couchbase5_5Test.java deleted file mode 100644 index 47bc020ef03..00000000000 --- a/modules/couchbase/src/test/java/org/testcontainers/couchbase/Couchbase5_5Test.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.testcontainers.couchbase; - -import org.junit.Rule; - -public class Couchbase5_5Test extends BaseCouchbaseContainerTest { - - @Rule - public CouchbaseContainer container = initCouchbaseContainer("couchbase/server:5.5.1"); - - @Override - public CouchbaseContainer getCouchbaseContainer() { - return container; - } -} diff --git a/modules/couchbase/src/test/java/org/testcontainers/couchbase/CouchbaseCommunity5_1Test.java b/modules/couchbase/src/test/java/org/testcontainers/couchbase/CouchbaseCommunity5_1Test.java deleted file mode 100644 index 30be62bc2bd..00000000000 --- a/modules/couchbase/src/test/java/org/testcontainers/couchbase/CouchbaseCommunity5_1Test.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.testcontainers.couchbase; - -import java.util.Collections; - -import org.junit.ClassRule; - -import com.couchbase.client.java.cluster.UserRole; -import com.couchbase.client.java.cluster.UserSettings; -import org.junit.Rule; - -public class CouchbaseCommunity5_1Test extends BaseCouchbaseContainerTest { - - @Rule - public CouchbaseContainer container = initCouchbaseContainer(); - - private static CouchbaseContainer initCouchbaseContainer() { - CouchbaseContainer couchbaseContainer = new CouchbaseContainer("couchbase/server:community-5.1.1"); - - final UserSettings communityAdminUserSettings = - UserSettings.build() - .password(getDefaultBucketSettings().password()) - .roles(Collections.singletonList(new UserRole("admin"))); - - couchbaseContainer.withNewBucket(getDefaultBucketSettings(), communityAdminUserSettings); - return couchbaseContainer; - } - - @Override - public CouchbaseContainer getCouchbaseContainer() { - return container; - } -} diff --git a/modules/couchbase/src/test/java/org/testcontainers/couchbase/CouchbaseContainerTest.java b/modules/couchbase/src/test/java/org/testcontainers/couchbase/CouchbaseContainerTest.java index 6e9afe96865..27ad0f6265a 100644 --- a/modules/couchbase/src/test/java/org/testcontainers/couchbase/CouchbaseContainerTest.java +++ b/modules/couchbase/src/test/java/org/testcontainers/couchbase/CouchbaseContainerTest.java @@ -1,22 +1,78 @@ +/* + * Copyright (c) 2020 Couchbase, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.testcontainers.couchbase; -import org.junit.Assert; +import com.couchbase.client.java.Bucket; +import com.couchbase.client.java.Cluster; +import com.couchbase.client.java.CouchbaseCluster; +import com.couchbase.client.java.document.JsonDocument; +import com.couchbase.client.java.document.json.JsonObject; +import com.couchbase.client.java.env.CouchbaseEnvironment; +import com.couchbase.client.java.env.DefaultCouchbaseEnvironment; import org.junit.Test; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + public class CouchbaseContainerTest { @Test - public void shouldUseCorrectDockerImage() { - CouchbaseContainer couchbaseContainer = new CouchbaseContainer().withClusterAdmin("admin", "foobar"); + public void testBasicContainerUsage() { + // bucket_definition { + BucketDefinition bucketDefinition = new BucketDefinition("mybucket"); + // } - Assert.assertEquals(CouchbaseContainer.DOCKER_IMAGE_NAME + CouchbaseContainer.VERSION, - couchbaseContainer.getDockerImageName()); - } + try ( + // container_definition { + CouchbaseContainer container = new CouchbaseContainer() + .withBucket(bucketDefinition) + // } + ) { + container.start(); - @Test - public void shouldStopWithoutThrowingException() { - CouchbaseContainer couchbaseContainer = new CouchbaseContainer(); - couchbaseContainer.start(); - couchbaseContainer.stop(); + // cluster_creation { + CouchbaseEnvironment environment = DefaultCouchbaseEnvironment + .builder() + .bootstrapCarrierDirectPort(container.getBootstrapCarrierDirectPort()) + .bootstrapHttpDirectPort(container.getBootstrapHttpDirectPort()) + .build(); + + Cluster cluster = CouchbaseCluster.create( + environment, + container.getContainerIpAddress() + ); + // } + + try { + // auth { + cluster.authenticate(container.getUsername(), container.getPassword()); + // } + + Bucket bucket = cluster.openBucket(bucketDefinition.getName()); + + bucket.upsert(JsonDocument.create("foo", JsonObject.empty())); + + assertTrue(bucket.exists("foo")); + assertNotNull(cluster.clusterManager().getBucket(bucketDefinition.getName())); + } finally { + cluster.disconnect(); + environment.shutdown(); + } + } } + }