Skip to content

Commit 87fb26a

Browse files
Bouncheckavelanarius
authored andcommitted
Add testing support and basic tests for Scylla Cloud
Extends `@CCMConfig` annotation with new option to start SNI proxy. Adds custom `clusterProvider` for tests with proxy enabled. It uses special `MockSniEndPointFactory` in order to resolve hosts correctly on local network. The tests establish connection with single and multi node SNI proxy enabled ccm clusters using ccm generated .yaml for configuration. Simple queries like keyspace and table creation are executed and verified. Adding nodes to already running proxy enabled ccm clusters is not working yet. However as a side effect of the changes some `long` tests that do add nodes to clusters without proxy have been fixed.
1 parent ca8f929 commit 87fb26a

File tree

7 files changed

+215
-1
lines changed

7 files changed

+215
-1
lines changed

driver-core/src/test/java/com/datastax/driver/core/CCMAccess.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ enum Workload {
8181
/** @return The binary port for this CCM cluster. */
8282
int getBinaryPort();
8383

84+
/** @return The SNI proxy port for this CCM cluster. */
85+
int getSniPort();
86+
8487
/** Signals that logs for this CCM cluster should be kept after the cluster is stopped. */
8588
void setKeepLogs(boolean keepLogs);
8689

driver-core/src/test/java/com/datastax/driver/core/CCMBridge.java

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,14 +350,20 @@ public static boolean isWindows() {
350350

351351
private final int binaryPort;
352352

353+
private final int sniPort;
354+
353355
private final String ipPrefix;
354356

355357
private final File ccmDir;
356358

357359
private final boolean isDSE;
358360

361+
private final boolean isScylla;
362+
359363
private final String jvmArgs;
360364

365+
private final boolean startSniProxy;
366+
361367
private boolean keepLogs = false;
362368

363369
private boolean started = false;
@@ -376,8 +382,10 @@ protected CCMBridge(
376382
int storagePort,
377383
int thriftPort,
378384
int binaryPort,
385+
int sniPort,
379386
int[] jmxPorts,
380387
String jvmArgs,
388+
boolean startSniProxy,
381389
int[] nodes) {
382390

383391
this.clusterName = clusterName;
@@ -387,8 +395,11 @@ protected CCMBridge(
387395
this.storagePort = storagePort;
388396
this.thriftPort = thriftPort;
389397
this.binaryPort = binaryPort;
398+
this.sniPort = sniPort;
390399
this.isDSE = dseVersion != null;
400+
this.isScylla = (getGlobalScyllaVersion() != null);
391401
this.jvmArgs = jvmArgs;
402+
this.startSniProxy = startSniProxy;
392403
this.nodes = nodes;
393404
this.ccmDir = Files.createTempDir();
394405
this.jmxPorts = jmxPorts;
@@ -489,6 +500,11 @@ public int getBinaryPort() {
489500
return binaryPort;
490501
}
491502

503+
@Override
504+
public int getSniPort() {
505+
return sniPort;
506+
}
507+
492508
@Override
493509
public void setKeepLogs(boolean keepLogs) {
494510
this.keepLogs = keepLogs;
@@ -554,6 +570,10 @@ public synchronized void start() {
554570
if (isWindows() && this.cassandraVersion.compareTo(VersionNumber.parse("2.2.4")) >= 0) {
555571
cmd += " --quiet-windows";
556572
}
573+
if (startSniProxy) {
574+
cmd += " --sni-proxy";
575+
cmd += " --sni-port " + sniPort;
576+
}
557577
execute(cmd);
558578

559579
// Wait for binary interface on each node.
@@ -692,7 +712,8 @@ public void add(int dc, int n) {
692712
execute(
693713
CCM_COMMAND
694714
+ " add node%d -d dc%s -i %s%d -t %s -l %s --binary-itf %s -j %d -r %s -s -b"
695-
+ (isDSE ? " --dse" : ""),
715+
+ (isDSE ? " --dse" : "")
716+
+ (isScylla ? " --scylla" : ""),
696717
n,
697718
dc,
698719
ipPrefix,
@@ -932,6 +953,7 @@ public static class Builder {
932953
private int[] jmxPorts = {};
933954
private boolean start = true;
934955
private boolean dse = isDse();
956+
private boolean startSniProxy = false;
935957
private VersionNumber version = null;
936958
private final Set<String> createOptions = new LinkedHashSet<String>();
937959
private final Set<String> jvmArgs = new LinkedHashSet<String>();
@@ -964,6 +986,11 @@ public Builder withoutNodes() {
964986
return withNodes();
965987
}
966988

989+
public Builder withSniProxy() {
990+
this.startSniProxy = true;
991+
return this;
992+
}
993+
967994
/** Enables SSL encryption. */
968995
public Builder withSSL() {
969996
cassandraConfiguration.put("client_encryption_options.enabled", "true");
@@ -1111,6 +1138,8 @@ public CCMBridge build() {
11111138
int binaryPort =
11121139
Integer.parseInt(cassandraConfiguration.get("native_transport_port").toString());
11131140

1141+
int sniPort = TestUtils.findAvailablePort();
1142+
11141143
// Copy any supplied jmx ports over, and find available ports for the rest
11151144
int numNodes = 0;
11161145
for (int i : nodes) {
@@ -1157,8 +1186,10 @@ public CCMBridge build() {
11571186
storagePort,
11581187
thriftPort,
11591188
binaryPort,
1189+
sniPort,
11601190
generatedJmxPorts,
11611191
joinJvmArgs(),
1192+
startSniProxy,
11621193
nodes);
11631194

11641195
Runtime.getRuntime()
@@ -1365,6 +1396,7 @@ public boolean equals(Object o) {
13651396
return false;
13661397
if (!createOptions.equals(builder.createOptions)) return false;
13671398
if (!jvmArgs.equals(builder.jvmArgs)) return false;
1399+
if (startSniProxy != builder.startSniProxy) return false;
13681400
if (!cassandraConfiguration.equals(builder.cassandraConfiguration)) return false;
13691401
if (!dseConfiguration.equals(builder.dseConfiguration)) return false;
13701402
return workloads.equals(builder.workloads);
@@ -1379,6 +1411,7 @@ public int hashCode() {
13791411
result = 31 * result + (version != null ? version.hashCode() : 0);
13801412
result = 31 * result + createOptions.hashCode();
13811413
result = 31 * result + jvmArgs.hashCode();
1414+
result = 31 * result + (startSniProxy ? 1 : 0);
13821415
result = 31 * result + cassandraConfiguration.hashCode();
13831416
result = 31 * result + dseConfiguration.hashCode();
13841417
result = 31 * result + workloads.hashCode();

driver-core/src/test/java/com/datastax/driver/core/CCMCache.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,11 @@ public int getBinaryPort() {
9999
return ccm.getBinaryPort();
100100
}
101101

102+
@Override
103+
public int getSniPort() {
104+
return ccm.getSniPort();
105+
}
106+
102107
@Override
103108
public void setKeepLogs(boolean keepLogs) {
104109
ccm.setKeepLogs(keepLogs);

driver-core/src/test/java/com/datastax/driver/core/CCMConfig.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16+
/*
17+
* Copyright (C) 2022 ScyllaDB
18+
*
19+
* Modified by ScyllaDB
20+
*/
1621
package com.datastax.driver.core;
1722

1823
import static java.lang.annotation.ElementType.METHOD;
@@ -210,6 +215,8 @@ final class Undefined {}
210215
*/
211216
boolean[] createKeyspace() default {};
212217

218+
boolean[] startSniProxy() default {};
219+
213220
/**
214221
* Returns {@code true} if the test class or the test method alters the CCM cluster, e.g. by
215222
* adding or removing nodes, in which case, it should not be reused after the test is finished.

driver-core/src/test/java/com/datastax/driver/core/CCMTestsSupport.java

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import static com.datastax.driver.core.CreateCCM.TestMode.PER_CLASS;
2525
import static com.datastax.driver.core.CreateCCM.TestMode.PER_METHOD;
2626
import static com.datastax.driver.core.TestUtils.CREATE_KEYSPACE_SIMPLE_FORMAT;
27+
import static com.datastax.driver.core.TestUtils.addressOfNode;
2728
import static com.datastax.driver.core.TestUtils.executeNoFail;
2829
import static com.datastax.driver.core.TestUtils.ipOfNode;
2930
import static org.assertj.core.api.Assertions.fail;
@@ -39,6 +40,7 @@
3940
import com.google.common.util.concurrent.Uninterruptibles;
4041
import java.io.Closeable;
4142
import java.io.File;
43+
import java.io.FileInputStream;
4244
import java.io.IOException;
4345
import java.lang.annotation.Annotation;
4446
import java.lang.reflect.Constructor;
@@ -154,6 +156,11 @@ public int getBinaryPort() {
154156
return delegate.getBinaryPort();
155157
}
156158

159+
@Override
160+
public int getSniPort() {
161+
return delegate.getSniPort();
162+
}
163+
157164
@Override
158165
public void setKeepLogs(boolean keepLogs) {
159166
delegate.setKeepLogs(keepLogs);
@@ -484,6 +491,14 @@ private boolean dirtiesContext() {
484491
return false;
485492
}
486493

494+
@SuppressWarnings("SimplifiableIfStatement")
495+
private boolean startSniProxy() {
496+
for (CCMConfig ann : annotations) {
497+
if (ann != null && ann.startSniProxy().length > 0) return ann.startSniProxy()[0];
498+
}
499+
return false;
500+
}
501+
487502
private CCMBridge.Builder ccmBuilder(Object testInstance) throws Exception {
488503
if (ccmBuilder == null) {
489504
ccmBuilder = ccmProvider(testInstance);
@@ -501,6 +516,7 @@ private CCMBridge.Builder ccmBuilder(Object testInstance) throws Exception {
501516
if (dse != null) ccmBuilder.withDSE(dse);
502517
if (ssl()) ccmBuilder.withSSL();
503518
if (auth()) ccmBuilder.withAuth();
519+
if (startSniProxy()) ccmBuilder.withSniProxy();
504520
for (Map.Entry<String, Object> entry : config().entrySet()) {
505521
ccmBuilder.withCassandraConfiguration(entry.getKey(), entry.getValue());
506522
}
@@ -742,6 +758,49 @@ public Cluster.Builder createClusterBuilderNoDebouncing() {
742758
return createClusterBuilder().withQueryOptions(TestUtils.nonDebouncingQueryOptions());
743759
}
744760

761+
/**
762+
* Returns the cluster builder to test against Scylla Cloud (sniProxy enabled).
763+
*
764+
* <p>This implementation returns a vanilla builder with contact points and port that match
765+
* datacenter description in CCM generated yaml configuration file. This configuration may contain
766+
* domain names that cannot be resolved on local machine, therefore we overwrite EndPointFactory
767+
* afterwards and add sniProxy contact point using raw ip addresses (with ports from configuration
768+
* file). It's not required to call {@link Cluster.Builder#addContactPointsWithPorts}, it will be
769+
* done automatically.
770+
*
771+
* @return The cluster builder to use for the tests.
772+
*/
773+
public Cluster.Builder createClusterBuilderScyllaCloud() throws IOException {
774+
assert ccmTestConfig.startSniProxy();
775+
Cluster.Builder builder = Cluster.builder();
776+
777+
File ccmdir = ccm.getCcmDir();
778+
File clusterFile = new File(ccmdir, ccm.getClusterName());
779+
File yamlFile = new File(clusterFile, "config_data.yaml");
780+
781+
final ScyllaCloudConnectionConfig cloudConfig =
782+
ScyllaCloudConnectionConfig.fromInputStream(new FileInputStream(yamlFile));
783+
784+
builder.withScyllaCloudConnectionConfig(cloudConfig);
785+
builder.withEndPointFactory(
786+
new MockSniEndPointFactory(
787+
InetSocketAddress.createUnresolved(
788+
addressOfNode(1).getHostAddress(),
789+
cloudConfig.getCurrentDatacenter().getServer().getPort()),
790+
builder.getConfiguration().getPolicies().getEndPointFactory()));
791+
builder.addContactPoint(
792+
new SniEndPoint(
793+
InetSocketAddress.createUnresolved(
794+
addressOfNode(1).getHostAddress(),
795+
cloudConfig.getCurrentDatacenter().getServer().getPort()),
796+
cloudConfig.getCurrentDatacenter().getServer().getHostName()));
797+
798+
builder
799+
.withCodecRegistry(new CodecRegistry())
800+
.withPort(cloudConfig.getCurrentDatacenter().getServer().getPort());
801+
return builder;
802+
}
803+
745804
/**
746805
* Configures the builder with contact points and port that match the running CCM cluster.
747806
* Therefore it's not required to call {@link Cluster.Builder#addContactPointsWithPorts}, it will
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright (C) 2022 ScyllaDB
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+
* http://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 com.datastax.driver.core;
17+
18+
import java.net.InetSocketAddress;
19+
20+
class MockSniEndPointFactory implements EndPointFactory {
21+
private final InetSocketAddress proxyAddress;
22+
private final EndPointFactory childSniFactory;
23+
24+
public MockSniEndPointFactory(InetSocketAddress proxyAddress, EndPointFactory childSniFactory) {
25+
this.proxyAddress = proxyAddress;
26+
this.childSniFactory = childSniFactory;
27+
}
28+
29+
@Override
30+
public void init(Cluster cluster) {
31+
childSniFactory.init(cluster);
32+
}
33+
34+
@Override
35+
public EndPoint create(Row peersRow) {
36+
SniEndPoint originalEndPoint = (SniEndPoint) childSniFactory.create(peersRow);
37+
return new SniEndPoint(proxyAddress, originalEndPoint.getServerName());
38+
}
39+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright (C) 2022 ScyllaDB
3+
*/
4+
package com.datastax.driver.core;
5+
6+
import static org.mockito.Matchers.any;
7+
import static org.mockito.Mockito.atLeast;
8+
import static org.mockito.Mockito.atMost;
9+
import static org.mockito.Mockito.mock;
10+
import static org.mockito.Mockito.times;
11+
import static org.mockito.Mockito.verify;
12+
13+
import java.util.Collection;
14+
import org.testng.annotations.Test;
15+
16+
@CreateCCM(CreateCCM.TestMode.PER_METHOD)
17+
public class ScyllaSniProxyTest extends CCMTestsSupport {
18+
19+
private void test_ccm_cluster(int testNodes) {
20+
Cluster c = cluster().init();
21+
Session s = c.connect();
22+
TestUtils.waitForUp(TestUtils.ipOfNode(1), c);
23+
24+
Collection<Host> hosts = s.getState().getConnectedHosts();
25+
assert hosts.size() == testNodes;
26+
for (Host host : hosts) {
27+
assert (host.getListenAddress() == null
28+
|| host.getListenAddress().equals(TestUtils.addressOfNode(1)));
29+
assert host.getEndPoint().resolve().getAddress().equals(TestUtils.addressOfNode(1));
30+
assert host.getEndPoint().resolve().getPort() == ccm().getSniPort();
31+
assert !host.getEndPoint().toString().contains("any.");
32+
}
33+
((SessionManager) s).cluster.manager.controlConnection.triggerReconnect();
34+
35+
SchemaChangeListener listener = mock(SchemaChangeListenerBase.class);
36+
c.register(listener);
37+
38+
s.execute(String.format(TestUtils.CREATE_KEYSPACE_SIMPLE_FORMAT, "testks", testNodes));
39+
s.execute("CREATE TABLE testks.testtab (a int PRIMARY KEY, b int);");
40+
41+
verify(listener, times(1)).onTableAdded(any(TableMetadata.class));
42+
verify(listener, atLeast(1)).onKeyspaceAdded(any(KeyspaceMetadata.class));
43+
verify(listener, atMost(2)).onKeyspaceAdded(any(KeyspaceMetadata.class));
44+
45+
s.close();
46+
c.close();
47+
}
48+
49+
@Test(groups = "short")
50+
@CCMConfig(
51+
startSniProxy = true,
52+
numberOfNodes = 3,
53+
clusterProvider = "createClusterBuilderScyllaCloud",
54+
dirtiesContext = true)
55+
public void test_ccm_cluster_3node() throws InterruptedException {
56+
test_ccm_cluster(3);
57+
}
58+
59+
@Test(groups = "short")
60+
@CCMConfig(
61+
startSniProxy = true,
62+
numberOfNodes = 1,
63+
clusterProvider = "createClusterBuilderScyllaCloud",
64+
dirtiesContext = true)
65+
public void test_ccm_cluster_1node() throws InterruptedException {
66+
test_ccm_cluster(1);
67+
}
68+
}

0 commit comments

Comments
 (0)