Skip to content

Commit ddfbc1f

Browse files
authored
Support testing against Capella. (#1386)
Support testing against Capella. Also includes sample to use Capella control-plane. Closes #1385.
1 parent ab0bd5c commit ddfbc1f

22 files changed

+490
-112
lines changed

pom.xml

+8-1
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,14 @@
193193
<dependency>
194194
<groupId>com.squareup.okhttp3</groupId>
195195
<artifactId>okhttp</artifactId>
196-
<version>4.4.0</version>
196+
<version>4.8.1</version>
197+
<scope>test</scope>
198+
</dependency>
199+
200+
<dependency>
201+
<groupId>com.squareup.okhttp3</groupId>
202+
<artifactId>okhttp-tls</artifactId>
203+
<version>4.8.1</version>
197204
<scope>test</scope>
198205
</dependency>
199206

src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateQueryCollectionIntegrationTests.java

+16-23
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
import java.util.UUID;
3131
import java.util.stream.Collectors;
3232

33-
import com.couchbase.client.core.msg.kv.DurabilityLevel;
3433
import org.junit.jupiter.api.AfterAll;
3534
import org.junit.jupiter.api.AfterEach;
3635
import org.junit.jupiter.api.BeforeAll;
@@ -58,7 +57,7 @@
5857

5958
import com.couchbase.client.core.error.AmbiguousTimeoutException;
6059
import com.couchbase.client.core.error.UnambiguousTimeoutException;
61-
import com.couchbase.client.core.io.CollectionIdentifier;
60+
import com.couchbase.client.core.msg.kv.DurabilityLevel;
6261
import com.couchbase.client.java.analytics.AnalyticsOptions;
6362
import com.couchbase.client.java.kv.ExistsOptions;
6463
import com.couchbase.client.java.kv.GetAnyReplicaOptions;
@@ -765,9 +764,6 @@ public void testScopeCollectionAnnotation() {
765764
.withConsistency(QueryScanConsistency.REQUEST_PLUS).inScope(scopeName).inCollection(collectionName)
766765
.matching(query).all();
767766
assertEquals(saved, found.get(0), "should have found what was saved");
768-
List<UserCol> notfound = couchbaseTemplate.findByQuery(UserCol.class).inScope(CollectionIdentifier.DEFAULT_SCOPE)
769-
.inCollection(CollectionIdentifier.DEFAULT_COLLECTION).matching(query).all();
770-
assertEquals(0, notfound.size(), "should not have found what was saved");
771767
couchbaseTemplate.removeByQuery(UserCol.class).inScope(scopeName).inCollection(collectionName).matching(query)
772768
.all();
773769
} finally {
@@ -789,9 +785,6 @@ public void testScopeCollectionRepoWith() {
789785
.withConsistency(QueryScanConsistency.REQUEST_PLUS).inScope(scopeName).inCollection(collectionName)
790786
.matching(query).all();
791787
assertEquals(saved, found.get(0), "should have found what was saved");
792-
List<UserCol> notfound = couchbaseTemplate.findByQuery(UserCol.class).inScope(CollectionIdentifier.DEFAULT_SCOPE)
793-
.inCollection(CollectionIdentifier.DEFAULT_COLLECTION).matching(query).all();
794-
assertEquals(0, notfound.size(), "should not have found what was saved");
795788
couchbaseTemplate.removeByQuery(UserCol.class).inScope(scopeName).inCollection(collectionName).matching(query)
796789
.all();
797790
} finally {
@@ -810,28 +803,28 @@ void testFluentApi() {
810803
RemoveResult rr;
811804
result = couchbaseTemplate.insertById(User.class).withDurability(dl).inScope(scopeName).inCollection(collectionName)
812805
.one(user1);
813-
assertEquals(user1,result);
806+
assertEquals(user1, result);
814807
result = couchbaseTemplate.upsertById(User.class).withDurability(dl).inScope(scopeName).inCollection(collectionName)
815808
.one(user1);
816-
assertEquals(user1,result);
817-
result = couchbaseTemplate.replaceById(User.class).withDurability(dl).inScope(scopeName).inCollection(collectionName)
818-
.one(user1);
819-
assertEquals(user1,result);
809+
assertEquals(user1, result);
810+
result = couchbaseTemplate.replaceById(User.class).withDurability(dl).inScope(scopeName)
811+
.inCollection(collectionName).one(user1);
812+
assertEquals(user1, result);
820813
rr = couchbaseTemplate.removeById(User.class).withDurability(dl).inScope(scopeName).inCollection(collectionName)
821814
.one(user1.getId());
822815
assertEquals(rr.getId(), user1.getId());
823-
assertEquals(user1,result);
824-
result = reactiveCouchbaseTemplate.insertById(User.class).withDurability(dl).inScope(scopeName).inCollection(collectionName)
825-
.one(user1).block();
826-
assertEquals(user1,result);
827-
result = reactiveCouchbaseTemplate.upsertById(User.class).withDurability(dl).inScope(scopeName).inCollection(collectionName)
828-
.one(user1).block();
829-
assertEquals(user1,result);
816+
assertEquals(user1, result);
817+
result = reactiveCouchbaseTemplate.insertById(User.class).withDurability(dl).inScope(scopeName)
818+
.inCollection(collectionName).one(user1).block();
819+
assertEquals(user1, result);
820+
result = reactiveCouchbaseTemplate.upsertById(User.class).withDurability(dl).inScope(scopeName)
821+
.inCollection(collectionName).one(user1).block();
822+
assertEquals(user1, result);
830823
result = reactiveCouchbaseTemplate.replaceById(User.class).withDurability(dl).inScope(scopeName)
831824
.inCollection(collectionName).one(user1).block();
832-
assertEquals(user1,result);
833-
rr = reactiveCouchbaseTemplate.removeById(User.class).withDurability(dl).inScope(scopeName).inCollection(collectionName)
834-
.one(user1.getId()).block();
825+
assertEquals(user1, result);
826+
rr = reactiveCouchbaseTemplate.removeById(User.class).withDurability(dl).inScope(scopeName)
827+
.inCollection(collectionName).one(user1.getId()).block();
835828
assertEquals(rr.getId(), user1.getId());
836829
}
837830

src/test/java/org/springframework/data/couchbase/core/CustomTypeKeyIntegrationTests.java

+11
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@
3535
import org.springframework.data.couchbase.util.IgnoreWhen;
3636
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
3737

38+
import com.couchbase.client.core.deps.io.netty.handler.ssl.util.InsecureTrustManagerFactory;
39+
import com.couchbase.client.core.env.SecurityConfig;
40+
import com.couchbase.client.java.env.ClusterEnvironment;
3841
import com.couchbase.client.java.kv.GetResult;
3942

4043
/**
@@ -91,6 +94,14 @@ public String getBucketName() {
9194
return bucketName();
9295
}
9396

97+
@Override
98+
protected void configureEnvironment(ClusterEnvironment.Builder builder) {
99+
if (config().isUsingCloud()) {
100+
builder.securityConfig(
101+
SecurityConfig.builder().trustManagerFactory(InsecureTrustManagerFactory.INSTANCE).enableTls(true));
102+
}
103+
}
104+
94105
@Override
95106
public String typeKey() {
96107
return CUSTOM_TYPE_KEY;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
/*
2+
* Copyright 2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.couchbase.domain;
17+
18+
import static com.couchbase.client.java.query.QueryOptions.queryOptions;
19+
import static java.nio.charset.StandardCharsets.UTF_8;
20+
21+
import okhttp3.OkHttpClient;
22+
import okhttp3.Request;
23+
import okhttp3.Response;
24+
import okhttp3.tls.HandshakeCertificates;
25+
26+
import java.io.IOException;
27+
import java.io.UnsupportedEncodingException;
28+
import java.security.InvalidKeyException;
29+
import java.security.NoSuchAlgorithmException;
30+
import java.time.Duration;
31+
import java.util.ArrayList;
32+
import java.util.Base64;
33+
import java.util.HashMap;
34+
import java.util.List;
35+
import java.util.Map;
36+
37+
import javax.crypto.Mac;
38+
import javax.crypto.spec.SecretKeySpec;
39+
40+
import com.couchbase.client.core.deps.io.netty.handler.ssl.util.InsecureTrustManagerFactory;
41+
import com.couchbase.client.core.env.IoConfig;
42+
import com.couchbase.client.core.env.SecurityConfig;
43+
import com.couchbase.client.java.Bucket;
44+
import com.couchbase.client.java.Cluster;
45+
import com.couchbase.client.java.ClusterOptions;
46+
import com.couchbase.client.java.Collection;
47+
import com.couchbase.client.java.env.ClusterEnvironment;
48+
import com.couchbase.client.java.json.JsonArray;
49+
import com.couchbase.client.java.json.JsonObject;
50+
import com.couchbase.client.java.manager.query.CreatePrimaryQueryIndexOptions;
51+
import com.couchbase.client.java.query.QueryResult;
52+
import com.fasterxml.jackson.databind.ObjectMapper;
53+
54+
/**
55+
* Sample code for connecting to Capella through both the control-plane and the data-plane. An Access Key and a Secret
56+
* Key are required and a bucket named "my_bucket" on the 'last' cluster.
57+
*/
58+
public class CapellaConnectSample {
59+
60+
static final String cbc_access_key = "3gcpgyTBzOetdETYxOAtmLYBe3f9ZSVN";
61+
static final String cbc_secret_key = "PWiACuJIZUlv0fCZaIQbhI44NDXVZCDdRBbpdaWlACioN7jkuOINCUVrU2QL1jVO";
62+
static final String hostname = "cloudapi.cloud.couchbase.com";
63+
static final HandshakeCertificates clientCertificates = new HandshakeCertificates.Builder()
64+
.addPlatformTrustedCertificates().addInsecureHost(hostname).build();
65+
static final OkHttpClient httpClient = new OkHttpClient.Builder()
66+
.sslSocketFactory(clientCertificates.sslSocketFactory(), clientCertificates.trustManager()).build();
67+
68+
protected static final ObjectMapper MAPPER = new ObjectMapper();
69+
static final String authorizationHeaderLabel = "Authorization";
70+
static final String timestampHeaderLabel = "Couchbase-Timestamp";
71+
72+
public static void main(String... args) {
73+
String endpoint = null; // "cb.zsibzkbgllfbcj8g.cloud.couchbase.com";
74+
List<String> clusterIds = getClustersControlPlane();
75+
for (String id : clusterIds) {
76+
endpoint = getClusterControlPlane(id);
77+
}
78+
79+
// Update this to your cluster
80+
String bucketName = "my_bucket";
81+
String username = "user";
82+
String password = "Couch0base!";
83+
// User Input ends here.
84+
85+
ClusterEnvironment env = ClusterEnvironment.builder()
86+
.securityConfig(SecurityConfig.enableTls(true).trustManagerFactory(InsecureTrustManagerFactory.INSTANCE))
87+
.ioConfig(IoConfig.enableDnsSrv(true)).build();
88+
89+
// Initialize the Connection
90+
Cluster cluster = Cluster.connect(endpoint, ClusterOptions.clusterOptions(username, password).environment(env));
91+
Bucket bucket = cluster.bucket(bucketName);
92+
bucket.waitUntilReady(Duration.parse("PT10S"));
93+
Collection collection = bucket.defaultCollection();
94+
95+
cluster.queryIndexes().createPrimaryIndex(bucketName,
96+
CreatePrimaryQueryIndexOptions.createPrimaryQueryIndexOptions().ignoreIfExists(true));
97+
98+
// Create a JSON Document
99+
JsonObject arthur = JsonObject.create().put("name", "Arthur").put("email", "[email protected]")
100+
.put("interests", JsonArray.from("Holy Grail", "African Swallows"));
101+
102+
// Store the Document
103+
collection.upsert("u:king_arthur", arthur);
104+
105+
// Load the Document and print it
106+
// Prints Content and Metadata of the stored Document
107+
System.err.println(collection.get("u:king_arthur"));
108+
109+
// Perform a N1QL Query
110+
QueryResult result = cluster.query(String.format("SELECT name FROM `%s` WHERE $1 IN interests", bucketName),
111+
queryOptions().parameters(JsonArray.from("African Swallows")));
112+
113+
// Print each found Row
114+
for (JsonObject row : result.rowsAsObject()) {
115+
System.err.println(row);
116+
}
117+
118+
cluster.disconnect();
119+
}
120+
121+
public static List<String> getClustersControlPlane() {
122+
List<String> clusterIds = new ArrayList<>();
123+
Map<String, Object> decoded = doRequest(hostname, "GET", "/v3/clusters");
124+
HashMap data = (HashMap) decoded.get("data");
125+
List<Map> items = (List<Map>) data.get("items");
126+
for (Map m : items) {
127+
clusterIds.add((String) m.get("id"));
128+
}
129+
return clusterIds;
130+
}
131+
132+
public static String getClusterControlPlane(String clusterId) {
133+
String endpointsSrv;
134+
Map<String, Object> decoded = doRequest(hostname, "GET", "/v3/clusters/" + clusterId);
135+
endpointsSrv = (String) decoded.get("endpointsSrv");
136+
return endpointsSrv;
137+
}
138+
139+
private static Map<String, Object> doRequest(String hostname, String cbc_api_method, String cbc_api_endpoint) {
140+
Map<String, Object> decoded;
141+
String responseString;
142+
try {
143+
String cbc_api_now = Long.toString(System.currentTimeMillis());
144+
String authorizationValue = getApiSignature(cbc_api_method, cbc_api_endpoint, cbc_api_now);
145+
String urlString = "https://" + hostname + cbc_api_endpoint;
146+
System.err.println("curl --header \"" + authorizationHeaderLabel + ": " + authorizationValue + "\" --header \""
147+
+ timestampHeaderLabel + ": " + cbc_api_now + "\" " + urlString);
148+
Response response = httpClient.newCall(new Request.Builder().header(authorizationHeaderLabel, authorizationValue)
149+
.header(timestampHeaderLabel, cbc_api_now).url(urlString).build()).execute();
150+
responseString = response.body().string();
151+
System.err.println(responseString);
152+
} catch (IOException | NoSuchAlgorithmException | InvalidKeyException e) {
153+
throw new RuntimeException(e);
154+
}
155+
156+
try {
157+
decoded = (Map<String, Object>) MAPPER.readValue(responseString.getBytes(UTF_8), Map.class);
158+
} catch (IOException e) {
159+
throw new RuntimeException("Error decoding, raw: " + responseString, e);
160+
}
161+
return decoded;
162+
}
163+
164+
private static String getApiSignature(String cbc_api_method, String cbc_api_endpoint, String cbc_api_now)
165+
throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException {
166+
String cbc_api_message = cbc_api_method + '\n' + cbc_api_endpoint + '\n' + cbc_api_now;
167+
return "Bearer " + cbc_access_key + ':' + new String(Base64.getEncoder()
168+
.encode(hmac("hmacSHA256", cbc_secret_key.getBytes("utf-8"), cbc_api_message.getBytes("utf-8"))));
169+
}
170+
171+
static byte[] hmac(String algorithm, byte[] key, byte[] message)
172+
throws NoSuchAlgorithmException, InvalidKeyException {
173+
Mac mac = Mac.getInstance(algorithm);
174+
mac.init(new SecretKeySpec(key, algorithm));
175+
return mac.doFinal(message);
176+
}
177+
178+
}

src/test/java/org/springframework/data/couchbase/domain/Config.java

+11-3
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,13 @@
1717
package org.springframework.data.couchbase.domain;
1818

1919
import java.lang.reflect.InvocationTargetException;
20-
import java.util.HashMap;
21-
import java.util.Map;
2220

2321
import org.springframework.cache.annotation.EnableCaching;
2422
import org.springframework.context.annotation.Bean;
2523
import org.springframework.context.annotation.Configuration;
2624
import org.springframework.data.auditing.DateTimeProvider;
2725
import org.springframework.data.couchbase.CouchbaseClientFactory;
2826
import org.springframework.data.couchbase.SimpleCouchbaseClientFactory;
29-
import org.springframework.data.couchbase.cache.CouchbaseCacheConfiguration;
3027
import org.springframework.data.couchbase.cache.CouchbaseCacheManager;
3128
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
3229
import org.springframework.data.couchbase.core.CouchbaseTemplate;
@@ -45,6 +42,9 @@
4542
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
4643

4744
import com.couchbase.client.core.deps.com.fasterxml.jackson.databind.DeserializationFeature;
45+
import com.couchbase.client.core.deps.io.netty.handler.ssl.util.InsecureTrustManagerFactory;
46+
import com.couchbase.client.core.env.SecurityConfig;
47+
import com.couchbase.client.java.env.ClusterEnvironment;
4848
import com.couchbase.client.java.json.JacksonTransformers;
4949

5050
/**
@@ -109,6 +109,14 @@ public String getBucketName() {
109109
return clusterGet("bucketName", bucketname);
110110
}
111111

112+
@Override
113+
protected void configureEnvironment(ClusterEnvironment.Builder builder) {
114+
if (getConnectionString().contains("cloud.couchbase.com")) {
115+
builder.securityConfig(
116+
SecurityConfig.builder().trustManagerFactory(InsecureTrustManagerFactory.INSTANCE).enableTls(true));
117+
}
118+
}
119+
112120
@Bean(name = "auditorAwareRef")
113121
public NaiveAuditorAware testAuditorAware() {
114122
return new NaiveAuditorAware();

src/test/java/org/springframework/data/couchbase/domain/FluxTest.java

+11
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,11 @@
5151
import org.springframework.data.util.Pair;
5252
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
5353

54+
import com.couchbase.client.core.deps.io.netty.handler.ssl.util.InsecureTrustManagerFactory;
55+
import com.couchbase.client.core.env.SecurityConfig;
5456
import com.couchbase.client.java.Collection;
5557
import com.couchbase.client.java.ReactiveCollection;
58+
import com.couchbase.client.java.env.ClusterEnvironment;
5659
import com.couchbase.client.java.json.JsonObject;
5760
import com.couchbase.client.java.kv.GetResult;
5861
import com.couchbase.client.java.query.QueryOptions;
@@ -264,5 +267,13 @@ public String getBucketName() {
264267
return bucketName();
265268
}
266269

270+
@Override
271+
protected void configureEnvironment(ClusterEnvironment.Builder builder) {
272+
if (config().isUsingCloud()) {
273+
builder.securityConfig(
274+
SecurityConfig.builder().trustManagerFactory(InsecureTrustManagerFactory.INSTANCE).enableTls(true));
275+
}
276+
}
277+
267278
}
268279
}

src/test/java/org/springframework/data/couchbase/repository/CouchbaseAbstractRepositoryIntegrationTests.java

+12
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@
4141
import org.springframework.data.couchbase.util.IgnoreWhen;
4242
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
4343

44+
import com.couchbase.client.core.deps.io.netty.handler.ssl.util.InsecureTrustManagerFactory;
45+
import com.couchbase.client.core.env.SecurityConfig;
46+
import com.couchbase.client.java.env.ClusterEnvironment;
47+
4448
/**
4549
* Abstract Repository tests
4650
*
@@ -128,6 +132,14 @@ public String getBucketName() {
128132
return bucketName();
129133
}
130134

135+
@Override
136+
protected void configureEnvironment(ClusterEnvironment.Builder builder) {
137+
if (config().isUsingCloud()) {
138+
builder.securityConfig(
139+
SecurityConfig.builder().trustManagerFactory(InsecureTrustManagerFactory.INSTANCE).enableTls(true));
140+
}
141+
}
142+
131143
/**
132144
* This uses a CustomMappingCouchbaseConverter instead of MappingCouchbaseConverter, which in turn uses
133145
* AbstractTypeMapper which has special mapping for AbstractUser

0 commit comments

Comments
 (0)