-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Couchbase module #688
Couchbase module #688
Changes from 10 commits
8c9102d
3f84f46
7328c95
16f9d42
39e3167
6add329
5d01553
8934841
bc45a31
96f71c8
ffc2488
50eeb6f
00232fe
a1ae86b
666f092
6e45640
3739a51
cc51bce
0661c3b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Tayeb Chlyah <[email protected]> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
<img src="https://cdn.worldvectorlogo.com/logos/couchbase.svg" width="300" /> | ||
|
||
# TestContainers Couchbase Module | ||
Testcontainers module for Couchbase. [Couchbase](https://www.couchbase.com/) is a Document oriented NoSQL database. | ||
|
||
## Usage example | ||
|
||
Running Couchbase as a stand-in in a test: | ||
|
||
### Create you own bucket | ||
|
||
```java | ||
public class SomeTest { | ||
|
||
@Rule | ||
public CouchbaseContainer couchbase = new CouchbaseContainer() | ||
.withNewBucket(DefaultBucketSettings.builder() | ||
.enableFlush(true) | ||
.name('bucket-name') | ||
.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 testContainer 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. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
description = "Testcontainers :: Couchbase" | ||
|
||
dependencies { | ||
compile project(':testcontainers') | ||
compile 'com.couchbase.client:java-client:2.5.7' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Couchbase java client is actually used, so if the client doesn't add this library, it won't run. It is also used in test, it will force me to add testCompile. |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
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.DefaultBucketSettings; | ||
import com.couchbase.client.java.query.N1qlParams; | ||
import com.couchbase.client.java.query.N1qlQuery; | ||
import com.couchbase.client.java.query.consistency.ScanConsistency; | ||
import lombok.Getter; | ||
import org.junit.After; | ||
|
||
/** | ||
* @author ctayeb | ||
*/ | ||
public abstract class AbstractCouchbaseTest { | ||
|
||
public static final String TEST_BUCKET = "test"; | ||
|
||
public static final String DEFAULT_PASSWORD = "password"; | ||
|
||
@Getter(lazy = true) | ||
private final static CouchbaseContainer couchbaseContainer = initCouchbaseContainer(); | ||
|
||
@Getter(lazy = true) | ||
private final static Bucket bucket = openBucket(TEST_BUCKET, DEFAULT_PASSWORD); | ||
|
||
@After | ||
public void clear() { | ||
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(); | ||
} | ||
} | ||
|
||
private static CouchbaseContainer initCouchbaseContainer() { | ||
CouchbaseContainer couchbaseContainer = new CouchbaseContainer() | ||
.withNewBucket(DefaultBucketSettings.builder() | ||
.enableFlush(true) | ||
.name(TEST_BUCKET) | ||
.password(DEFAULT_PASSWORD) | ||
.quota(100) | ||
.replicas(0) | ||
.type(BucketType.COUCHBASE) | ||
.build()); | ||
couchbaseContainer.start(); | ||
return couchbaseContainer; | ||
} | ||
|
||
private static Bucket openBucket(String bucketName, String password) { | ||
CouchbaseCluster cluster = getCouchbaseContainer().getCouchbaseCluster(); | ||
Bucket bucket = cluster.openBucket(bucketName, password); | ||
Runtime.getRuntime().addShutdownHook(new Thread(bucket::close)); | ||
return bucket; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,254 @@ | ||
/* | ||
* 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. | ||
* 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.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 lombok.AllArgsConstructor; | ||
import lombok.Getter; | ||
import lombok.experimental.Wither; | ||
import org.apache.commons.compress.utils.Sets; | ||
import org.testcontainers.containers.GenericContainer; | ||
import org.testcontainers.containers.wait.HttpWaitStrategy; | ||
|
||
import java.io.DataOutputStream; | ||
import java.io.IOException; | ||
import java.net.HttpURLConnection; | ||
import java.net.URL; | ||
import java.net.URLEncoder; | ||
import java.util.ArrayList; | ||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.Set; | ||
|
||
/** | ||
* Based on Laurent Doguin version | ||
* <p> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. JavaDoc task will fail |
||
* Optimized by ctayeb | ||
*/ | ||
@AllArgsConstructor | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does it really needed? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it is needed for lombok |
||
public class CouchbaseContainer<SELF extends CouchbaseContainer<SELF>> extends GenericContainer<SELF> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this is a final container, you can use: class CouchbaseContainer extends GenericContainer<CouchbaseContainer> |
||
|
||
@Wither | ||
private String memoryQuota = "300"; | ||
|
||
@Wither | ||
private String indexMemoryQuota = "300"; | ||
|
||
@Wither | ||
private String clusterUsername = "Administrator"; | ||
|
||
@Wither | ||
private String clusterPassword = "password"; | ||
|
||
@Wither | ||
private boolean keyValue = true; | ||
|
||
@Getter | ||
@Wither | ||
private boolean query = true; | ||
|
||
@Getter | ||
@Wither | ||
private boolean index = true; | ||
|
||
@Getter | ||
@Wither | ||
private boolean primaryIndex = true; | ||
|
||
@Getter | ||
@Wither | ||
private boolean fts = false; | ||
|
||
@Wither | ||
private boolean beerSample = false; | ||
|
||
@Wither | ||
private boolean travelSample = false; | ||
|
||
@Wither | ||
private boolean gamesIMSample = false; | ||
|
||
@Getter(lazy = true) | ||
private final CouchbaseEnvironment couchbaseEnvironment = createCouchbaseEnvironment(); | ||
|
||
@Getter(lazy = true) | ||
private final CouchbaseCluster couchbaseCluster = createCouchbaseCluster(); | ||
|
||
private List<BucketSettings> newBuckets = new ArrayList<>(); | ||
|
||
private String urlBase; | ||
|
||
public CouchbaseContainer() { | ||
super("couchbase/server:latest"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could you please fix the version? |
||
} | ||
|
||
public CouchbaseContainer(String containerName) { | ||
super(containerName); | ||
} | ||
|
||
@Override | ||
protected Integer getLivenessCheckPort() { | ||
return getMappedPort(8091); | ||
} | ||
|
||
@Override | ||
public Set<Integer> getLivenessCheckPortNumbers() { | ||
return Sets.newHashSet(getLivenessCheckPort()); | ||
} | ||
|
||
@Override | ||
protected void configure() { | ||
// Configurable ports | ||
addExposedPorts(11210, 11207, 8091, 18091); | ||
|
||
// Non configurable ports | ||
addFixedExposedPort(8092, 8092); | ||
addFixedExposedPort(8093, 8093); | ||
addFixedExposedPort(8094, 8094); | ||
addFixedExposedPort(8095, 8095); | ||
addFixedExposedPort(18092, 18092); | ||
addFixedExposedPort(18093, 18093); | ||
setWaitStrategy(new HttpWaitStrategy().forPath("/ui/index.html#/")); | ||
} | ||
|
||
public SELF withNewBucket(BucketSettings bucketSettings) { | ||
newBuckets.add(bucketSettings); | ||
return self(); | ||
} | ||
|
||
public void initCluster() { | ||
urlBase = String.format("http://%s:%s", getContainerIpAddress(), getMappedPort(8091)); | ||
try { | ||
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"; | ||
|
||
String bucketURL = "/sampleBuckets/install"; | ||
|
||
StringBuilder sampleBucketPayloadBuilder = new StringBuilder(); | ||
sampleBucketPayloadBuilder.append('['); | ||
if (travelSample) { | ||
sampleBucketPayloadBuilder.append("\"travel-sample\","); | ||
} | ||
if (beerSample) { | ||
sampleBucketPayloadBuilder.append("\"beer-sample\","); | ||
} | ||
if (gamesIMSample) { | ||
sampleBucketPayloadBuilder.append("\"gamesim-sample\","); | ||
} | ||
sampleBucketPayloadBuilder.append(']'); | ||
|
||
callCouchbaseRestAPI(poolURL, poolPayload); | ||
callCouchbaseRestAPI(setupServicesURL, setupServiceContent); | ||
callCouchbaseRestAPI(webSettingsURL, webSettingsContent); | ||
callCouchbaseRestAPI(bucketURL, sampleBucketPayloadBuilder.toString()); | ||
|
||
CouchbaseWaitStrategy s = new CouchbaseWaitStrategy(); | ||
s.withBasicCredentials(clusterUsername, clusterPassword); | ||
s.waitUntilReady(this); | ||
callCouchbaseRestAPI("/settings/indexes", "indexerThreads=0&logLevel=info&maxRollbackPoints=5&storageMode=memory_optimized"); | ||
} catch (Exception e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
public void createBucket(BucketSettings bucketSetting, boolean primaryIndex) { | ||
ClusterManager clusterManager = getCouchbaseCluster().clusterManager(clusterUsername, clusterPassword); | ||
// Insert Bucket | ||
BucketSettings bucketSettings = clusterManager.insertBucket(bucketSetting); | ||
// Insert Bucket admin user | ||
UserSettings userSettings = UserSettings.build() | ||
.password(bucketSetting.password()) | ||
.roles(Collections.singletonList(new UserRole("bucket_admin", bucketSetting.name()))); | ||
try { | ||
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())); | ||
} | ||
} | ||
} | ||
|
||
public void callCouchbaseRestAPI(String url, String payload) throws IOException { | ||
String fullUrl = urlBase + url; | ||
HttpURLConnection httpConnection = (HttpURLConnection) ((new URL(fullUrl).openConnection())); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please wrap with |
||
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); | ||
DataOutputStream out = new DataOutputStream(httpConnection.getOutputStream()); | ||
out.writeBytes(payload); | ||
out.flush(); | ||
out.close(); | ||
httpConnection.getResponseCode(); | ||
httpConnection.disconnect(); | ||
} | ||
|
||
@Override | ||
public void start() { | ||
super.start(); | ||
if (!newBuckets.isEmpty()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes indeed. For the record, I never tried to optimize the original code, just make it work 😉 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, ok, sorry :) Thanks for changing it 👍 |
||
for (BucketSettings bucketSetting : newBuckets) { | ||
createBucket(bucketSetting, primaryIndex); | ||
} | ||
} | ||
} | ||
|
||
private CouchbaseCluster createCouchbaseCluster() { | ||
return CouchbaseCluster.create(getCouchbaseEnvironment(), getContainerIpAddress()); | ||
} | ||
|
||
private DefaultCouchbaseEnvironment createCouchbaseEnvironment() { | ||
initCluster(); | ||
return DefaultCouchbaseEnvironment.builder() | ||
.bootstrapCarrierDirectPort(getMappedPort(11210)) | ||
.bootstrapCarrierSslPort(getMappedPort(11207)) | ||
.bootstrapHttpDirectPort(getMappedPort(8091)) | ||
.bootstrapHttpSslPort(getMappedPort(18091)) | ||
.build(); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI we usually use just word "container"