From 4c10f5d97915ac2c37d7be9222884854ae16d85b Mon Sep 17 00:00:00 2001 From: Nicholas Yip Date: Tue, 11 May 2021 11:40:59 +0900 Subject: [PATCH] Revert "Rewrite Couchbase module. closes #2447 (#2491)" This reverts commit c6e0cd9faf83a160bf1cb4b3f0b6b7b9535daad2. --- 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, 708 insertions(+), 530 deletions(-) create mode 100644 modules/couchbase/src/main/java/org/testcontainers/couchbase/AbstractCouchbaseTest.java delete mode 100644 modules/couchbase/src/main/java/org/testcontainers/couchbase/BucketDefinition.java create mode 100644 modules/couchbase/src/main/java/org/testcontainers/couchbase/CouchbaseQueryServiceWaitStrategy.java delete mode 100644 modules/couchbase/src/main/java/org/testcontainers/couchbase/CouchbaseService.java create mode 100644 modules/couchbase/src/test/java/org/testcontainers/couchbase/BaseCouchbaseContainerTest.java create mode 100644 modules/couchbase/src/test/java/org/testcontainers/couchbase/Couchbase4_6Test.java create mode 100644 modules/couchbase/src/test/java/org/testcontainers/couchbase/Couchbase5_1Test.java create mode 100644 modules/couchbase/src/test/java/org/testcontainers/couchbase/Couchbase5_5Test.java create 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 b71ee5de327..f6d014a42d4 100644 --- a/docs/modules/databases/couchbase.md +++ b/docs/modules/databases/couchbase.md @@ -8,25 +8,65 @@ Testcontainers module for Couchbase. [Couchbase](https://www.couchbase.com/) is Running Couchbase as a stand-in in a test: -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 - +### 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. + ## Adding this module to your project dependencies diff --git a/modules/couchbase/build.gradle b/modules/couchbase/build.gradle index 095f8828aac..66f29fc4718 100644 --- a/modules/couchbase/build.gradle +++ b/modules/couchbase/build.gradle @@ -2,6 +2,7 @@ description = "Testcontainers :: Couchbase" dependencies { compile project(':testcontainers') + compile 'com.couchbase.client:java-client:2.7.13' - testCompile 'com.couchbase.client:java-client:2.7.13' + testCompile project(':test-support') } diff --git a/modules/couchbase/src/main/java/org/testcontainers/couchbase/AbstractCouchbaseTest.java b/modules/couchbase/src/main/java/org/testcontainers/couchbase/AbstractCouchbaseTest.java new file mode 100644 index 00000000000..7fd99ca9fbc --- /dev/null +++ b/modules/couchbase/src/main/java/org/testcontainers/couchbase/AbstractCouchbaseTest.java @@ -0,0 +1,72 @@ +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 deleted file mode 100644 index 8c2b11403ad..00000000000 --- a/modules/couchbase/src/main/java/org/testcontainers/couchbase/BucketDefinition.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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 14e5744bb6d..e43cf8f652d 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) 2020 Couchbase, Inc. + * Copyright (c) 2016 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,424 +15,447 @@ */ 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.github.dockerjava.api.model.ContainerNetwork; -import okhttp3.Credentials; -import okhttp3.FormBody; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; +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 org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.Network; +import org.testcontainers.containers.SocatContainer; import org.testcontainers.containers.wait.strategy.HttpWaitStrategy; -import org.testcontainers.containers.wait.strategy.WaitAllStrategy; +import org.testcontainers.images.builder.Transferable; +import org.testcontainers.utility.ThrowingFunction; +import java.io.DataOutputStream; import java.io.IOException; -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.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.stream.Collectors; +import java.util.stream.Stream; + +import static java.net.HttpURLConnection.HTTP_OK; +import static org.testcontainers.couchbase.CouchbaseContainer.CouchbasePort.*; /** - * The couchbase container initializes and configures a Couchbase Server single node cluster. + * Based on Laurent Doguin version, *

- * 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. + * optimized by Tayeb Chlyah */ +@AllArgsConstructor public class CouchbaseContainer extends GenericContainer { - private static final int MGMT_PORT = 8091; - - private static final int MGMT_SSL_PORT = 18091; - - private static final int VIEW_PORT = 8092; + 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 VIEW_SSL_PORT = 18092; + private static final int REQUIRED_DEFAULT_PASSWORD_LENGTH = 6; - private static final int QUERY_PORT = 8093; + private String memoryQuota = "300"; - private static final int QUERY_SSL_PORT = 18093; + private String indexMemoryQuota = "300"; - private static final int SEARCH_PORT = 8094; + private String clusterUsername = "Administrator"; - private static final int SEARCH_SSL_PORT = 18094; + private String clusterPassword = "password"; - private static final int KV_PORT = 11210; + private boolean keyValue = true; - private static final int KV_SSL_PORT = 11207; + @Getter + private boolean query = true; - private static final String DOCKER_IMAGE_NAME = "couchbase/server"; + @Getter + private boolean index = true; - private static final String VERSION = "6.5.0"; + @Getter + private boolean primaryIndex = true; - private static final ObjectMapper MAPPER = new ObjectMapper(); + @Getter + private boolean fts = false; - private static final OkHttpClient HTTP_CLIENT = new OkHttpClient(); + @Getter(lazy = true) + private final CouchbaseEnvironment couchbaseEnvironment = createCouchbaseEnvironment(); - private String username = "Administrator"; + @Getter(lazy = true) + private final CouchbaseCluster couchbaseCluster = createCouchbaseCluster(); - private String password = "password"; + private List newBuckets = new ArrayList<>(); - private Set enabledServices = EnumSet.allOf(CouchbaseService.class); + private String urlBase; - private final List buckets = new ArrayList<>(); + private SocatContainer proxy; - /** - * Creates a new couchbase container with the default image and version. - */ public CouchbaseContainer() { - this(DOCKER_IMAGE_NAME + ":" + VERSION); + this(DOCKER_IMAGE_NAME + VERSION); } - /** - * Creates a new couchbase container with a custom image name. - * - * @param imageName the image name that should be used. - */ - public CouchbaseContainer(final String imageName) { + public CouchbaseContainer(String imageName) { super(imageName); + + withNetwork(Network.SHARED); + setWaitStrategy(new HttpWaitStrategy().forPath("/ui/index.html")); } - /** - * 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 + public Set getLivenessCheckPortNumbers() { + return Sets.newHashSet(getMappedPort(REST)); } - public CouchbaseContainer withBucket(final BucketDefinition bucketDefinition) { - checkNotRunning(); - this.buckets.add(bucketDefinition); - 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 withEnabledServices(final CouchbaseService... enabled) { - checkNotRunning(); - this.enabledServices = EnumSet.copyOf(Arrays.asList(enabled)); - return this; + @Override + @SneakyThrows + protected void doStart() { + startProxy(getNetworkAliases().get(0)); + try { + super.doStart(); + } catch (Throwable e) { + proxy.stop(); + throw e; + } } - public final String getUsername() { - return username; + @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 getPassword() { - return password; + @Override + public List getExposedPorts() { + return proxy.getExposedPorts(); } - public int getBootstrapCarrierDirectPort() { - return getMappedPort(KV_PORT); + @Override + public String getContainerIpAddress() { + return proxy.getContainerIpAddress(); } - public int getBootstrapHttpDirectPort() { - return getMappedPort(MGMT_PORT); + @Override + public Integer getMappedPort(int originalPort) { + return proxy.getMappedPort(originalPort); } - public String getConnectionString() { - return String.format("couchbase://%s:%d", getContainerIpAddress(), getBootstrapCarrierDirectPort()); + protected Integer getMappedPort(CouchbasePort port) { + return getMappedPort(port.getOriginalPort()); } @Override - 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) - ); + 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); } + } - waitingFor(waitStrategy); + private void stopCluster() { + getCouchbaseCluster().disconnect(); + getCouchbaseEnvironment().shutdown(); } - @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(); + 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(); + } + + @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,"); } + 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"); } - @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!"); + @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())); } - }).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))); } + } - if (enabledServices.contains(CouchbaseService.QUERY)) { - builder.add("n1ql", Integer.toString(getMappedPort(QUERY_PORT))); - builder.add("n1qlSSL", Integer.toString(getMappedPort(QUERY_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.SEARCH)) { - builder.add("fts", Integer.toString(getMappedPort(SEARCH_PORT))); - builder.add("ftsSSL", Integer.toString(getMappedPort(SEARCH_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); + } - final Response response = doHttpRequest( - MGMT_PORT, - "/node/controller/setupAlternateAddresses/external", - "PUT", - builder.build(), - true - ); + 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); + } - checkSuccessfulResponse(response, "Could not configure external ports"); + 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); } - /** - * Configures the indexer service so that indexes can be created later on the bucket. - */ - private void configureIndexer() { - logger().debug("Configuring the indexer service"); + 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")); + } - Response response = doHttpRequest(MGMT_PORT, "/settings/indexes", "POST", new FormBody.Builder() - .add("storageMode", "memory_optimized") - .build(), true - ); + @Override + protected void containerIsStarted(InspectContainerResponse containerInfo) { + if (!newBuckets.isEmpty()) { + for (BucketAndUserSettings bucket : newBuckets) { + createBucket(bucket.getBucketSettings(), bucket.getUserSettings(), primaryIndex); + } + } + } - checkSuccessfulResponse(response, "Could not configure the indexing service"); + private CouchbaseCluster createCouchbaseCluster() { + return CouchbaseCluster.create(getCouchbaseEnvironment(), getContainerIpAddress()); } - /** - * 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)."); + private DefaultCouchbaseEnvironment createCouchbaseEnvironment() { + initCluster(); + return DefaultCouchbaseEnvironment.builder() + .kvTimeout(10000) + .bootstrapCarrierDirectPort(getMappedPort(MEMCACHED)) + .bootstrapCarrierSslPort(getMappedPort(MEMCACHED_SSL)) + .bootstrapHttpDirectPort(getMappedPort(REST)) + .bootstrapHttpSslPort(getMappedPort(REST_SSL)) + .build(); + } - for (BucketDefinition bucket : buckets) { - logger().debug("Creating bucket \"" + bucket.getName() + "\""); + public CouchbaseContainer withMemoryQuota(String memoryQuota) { + this.memoryQuota = memoryQuota; + 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 withIndexMemoryQuota(String indexMemoryQuota) { + this.indexMemoryQuota = indexMemoryQuota; + return self(); + } - checkSuccessfulResponse(response, "Could not create bucket " + bucket.getName()); + public CouchbaseContainer withClusterAdmin(String username, String password) { + this.clusterUsername = username; + this.clusterPassword = password; + return self(); + } - new HttpWaitStrategy() - .forPath("/pools/default/buckets/" + bucket.getName()) - .forPort(MGMT_PORT) - .withBasicCredentials(username, password) - .forStatusCode(200) - .waitUntilReady(this); + public CouchbaseContainer withKeyValue(boolean keyValue) { + this.keyValue = keyValue; + 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 withQuery(boolean query) { + this.query = query; + 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 withIndex(boolean index) { + this.index = index; + 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 withPrimaryIndex(boolean primaryIndex) { + this.primaryIndex = primaryIndex; + 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(); - } - } + public CouchbaseContainer withFts(boolean fts) { + this.fts = fts; + return self(); } - /** - * 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"); - } + @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; } - /** - * 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); + @Value + @AllArgsConstructor + private class BucketAndUserSettings { - if (auth) { - requestBuilder = requestBuilder.header("Authorization", Credentials.basic(username, password)); - } + private final BucketSettings bucketSettings; + private final UserSettings userSettings; - if (body == null) { - requestBuilder = requestBuilder.get(); - } else { - requestBuilder = requestBuilder.method(method, body); - } + public BucketAndUserSettings(final BucketSettings bucketSettings) { + this.bucketSettings = bucketSettings; + this.userSettings = UserSettings.build() + .password(bucketSettings.password()) + .roles(getDefaultAdminRoles(bucketSettings.name())); + } - return HTTP_CLIENT.newCall(requestBuilder.build()).execute(); - } catch (Exception ex) { - throw new RuntimeException("Could not perform request against couchbase HTTP endpoint ", ex); + 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) + ); } + } } diff --git a/modules/couchbase/src/main/java/org/testcontainers/couchbase/CouchbaseQueryServiceWaitStrategy.java b/modules/couchbase/src/main/java/org/testcontainers/couchbase/CouchbaseQueryServiceWaitStrategy.java new file mode 100644 index 00000000000..853dd7d8eb7 --- /dev/null +++ b/modules/couchbase/src/main/java/org/testcontainers/couchbase/CouchbaseQueryServiceWaitStrategy.java @@ -0,0 +1,55 @@ +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 deleted file mode 100644 index d284c660709..00000000000 --- a/modules/couchbase/src/main/java/org/testcontainers/couchbase/CouchbaseService.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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 new file mode 100644 index 00000000000..ef2f0925c48 --- /dev/null +++ b/modules/couchbase/src/test/java/org/testcontainers/couchbase/BaseCouchbaseContainerTest.java @@ -0,0 +1,82 @@ +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 new file mode 100644 index 00000000000..c43d3c981ba --- /dev/null +++ b/modules/couchbase/src/test/java/org/testcontainers/couchbase/Couchbase4_6Test.java @@ -0,0 +1,14 @@ +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 new file mode 100644 index 00000000000..2c3dbf3c901 --- /dev/null +++ b/modules/couchbase/src/test/java/org/testcontainers/couchbase/Couchbase5_1Test.java @@ -0,0 +1,14 @@ +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 new file mode 100644 index 00000000000..47bc020ef03 --- /dev/null +++ b/modules/couchbase/src/test/java/org/testcontainers/couchbase/Couchbase5_5Test.java @@ -0,0 +1,14 @@ +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 new file mode 100644 index 00000000000..30be62bc2bd --- /dev/null +++ b/modules/couchbase/src/test/java/org/testcontainers/couchbase/CouchbaseCommunity5_1Test.java @@ -0,0 +1,32 @@ +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 27ad0f6265a..6e9afe96865 100644 --- a/modules/couchbase/src/test/java/org/testcontainers/couchbase/CouchbaseContainerTest.java +++ b/modules/couchbase/src/test/java/org/testcontainers/couchbase/CouchbaseContainerTest.java @@ -1,78 +1,22 @@ -/* - * 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 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.Assert; import org.junit.Test; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - public class CouchbaseContainerTest { @Test - public void testBasicContainerUsage() { - // bucket_definition { - BucketDefinition bucketDefinition = new BucketDefinition("mybucket"); - // } - - try ( - // container_definition { - CouchbaseContainer container = new CouchbaseContainer() - .withBucket(bucketDefinition) - // } - ) { - container.start(); - - // cluster_creation { - CouchbaseEnvironment environment = DefaultCouchbaseEnvironment - .builder() - .bootstrapCarrierDirectPort(container.getBootstrapCarrierDirectPort()) - .bootstrapHttpDirectPort(container.getBootstrapHttpDirectPort()) - .build(); - - Cluster cluster = CouchbaseCluster.create( - environment, - container.getContainerIpAddress() - ); - // } + public void shouldUseCorrectDockerImage() { + CouchbaseContainer couchbaseContainer = new CouchbaseContainer().withClusterAdmin("admin", "foobar"); - 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(); - } - } + Assert.assertEquals(CouchbaseContainer.DOCKER_IMAGE_NAME + CouchbaseContainer.VERSION, + couchbaseContainer.getDockerImageName()); } + @Test + public void shouldStopWithoutThrowingException() { + CouchbaseContainer couchbaseContainer = new CouchbaseContainer(); + couchbaseContainer.start(); + couchbaseContainer.stop(); + } }