|
| 1 | +diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/core/PeersV2NodeRefreshIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/core/PeersV2NodeRefreshIT.java |
| 2 | +index d70c6d3fac..c9839c3b93 100644 |
| 3 | +--- a/integration-tests/src/test/java/com/datastax/oss/driver/core/PeersV2NodeRefreshIT.java |
| 4 | ++++ b/integration-tests/src/test/java/com/datastax/oss/driver/core/PeersV2NodeRefreshIT.java |
| 5 | +@@ -29,6 +29,7 @@ import com.datastax.oss.simulacron.common.cluster.QueryLog; |
| 6 | + import com.datastax.oss.simulacron.server.BoundCluster; |
| 7 | + import com.datastax.oss.simulacron.server.Server; |
| 8 | + import java.util.concurrent.ExecutionException; |
| 9 | ++import org.junit.After; |
| 10 | + import org.junit.AfterClass; |
| 11 | + import org.junit.BeforeClass; |
| 12 | + import org.junit.Test; |
| 13 | +@@ -38,6 +39,7 @@ public class PeersV2NodeRefreshIT { |
| 14 | + |
| 15 | + private static Server peersV2Server; |
| 16 | + private static BoundCluster cluster; |
| 17 | ++ private static CqlSession session; |
| 18 | + |
| 19 | + @BeforeClass |
| 20 | + public static void setup() { |
| 21 | +@@ -55,6 +57,13 @@ public class PeersV2NodeRefreshIT { |
| 22 | + } |
| 23 | + } |
| 24 | + |
| 25 | ++ @After |
| 26 | ++ public void closeSession() { |
| 27 | ++ if (session != null) { |
| 28 | ++ session.close(); |
| 29 | ++ } |
| 30 | ++ } |
| 31 | ++ |
| 32 | + @Test |
| 33 | + public void should_successfully_send_peers_v2_node_refresh_query() |
| 34 | + throws InterruptedException, ExecutionException { |
| 35 | +diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/core/clientroutes/ClientRoutesIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/core/clientroutes/ClientRoutesIT.java |
| 36 | +index 9a23f36854..872f1221d7 100644 |
| 37 | +--- a/integration-tests/src/test/java/com/datastax/oss/driver/core/clientroutes/ClientRoutesIT.java |
| 38 | ++++ b/integration-tests/src/test/java/com/datastax/oss/driver/core/clientroutes/ClientRoutesIT.java |
| 39 | +@@ -91,6 +91,13 @@ public class ClientRoutesIT { |
| 40 | + private static final Logger LOG = LoggerFactory.getLogger(ClientRoutesIT.class); |
| 41 | + |
| 42 | + private static final int NLB_BASE_PORT = 29042; |
| 43 | ++ // Separate base port for should_work_with_mixed_proxy_and_direct_nodes to avoid port collision |
| 44 | ++ // with should_survive_full_node_replacement_through_nlb, which also uses NLB_BASE_PORT. |
| 45 | ++ // Both tests run sequentially with no delay between them; using distinct ranges eliminates the |
| 46 | ++ // risk of SO_REUSEADDR being insufficient to rebind ports still in TIME_WAIT. |
| 47 | ++ // NLB_BASE_PORT range: 29042 (discovery), 29043-29046 (nodes 1-4) |
| 48 | ++ // NLB_BASE_PORT_2 range: 29100 (discovery), 29101 (node 1) |
| 49 | ++ private static final int NLB_BASE_PORT_2 = 29100; |
| 50 | + private static final String NLB_ADDRESS = "127.254.254.254"; |
| 51 | + private static final String CONNECTION_ID = "11111111-1111-1111-1111-111111111111"; |
| 52 | + private static final String DC_NAME = "dc1"; |
| 53 | +@@ -829,7 +836,7 @@ public class ClientRoutesIT { |
| 54 | + ccm.create(); |
| 55 | + ccm.start(); |
| 56 | + |
| 57 | +- NlbSimulator nlb = new NlbSimulator(ccm, NLB_ADDRESS, NLB_BASE_PORT); |
| 58 | ++ NlbSimulator nlb = new NlbSimulator(ccm, NLB_ADDRESS, NLB_BASE_PORT_2); |
| 59 | + try { |
| 60 | + nlb.addNode(1); |
| 61 | + |
| 62 | +diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/core/pool/AdvancedShardAwarenessIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/core/pool/AdvancedShardAwarenessIT.java |
| 63 | +index aeda4a02c7..782aa2dfdb 100644 |
| 64 | +--- a/integration-tests/src/test/java/com/datastax/oss/driver/core/pool/AdvancedShardAwarenessIT.java |
| 65 | ++++ b/integration-tests/src/test/java/com/datastax/oss/driver/core/pool/AdvancedShardAwarenessIT.java |
| 66 | +@@ -234,7 +234,7 @@ public class AdvancedShardAwarenessIT { |
| 67 | + CqlSession session4 = CompletableFutures.getUninterruptibly(stage4); ) { |
| 68 | + List<CqlSession> allSessions = Arrays.asList(session1, session2, session3, session4); |
| 69 | + Awaitility.await() |
| 70 | +- .atMost(20, TimeUnit.SECONDS) |
| 71 | ++ .atMost(60, TimeUnit.SECONDS) |
| 72 | + .pollInterval(500, TimeUnit.MILLISECONDS) |
| 73 | + .until(() -> areAllPoolsFullyInitialized(allSessions, expectedChannelsPerNode)); |
| 74 | + int tolerance = 2; // Sometimes socket ends up already in use |
| 75 | +diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/core/resolver/MockResolverIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/core/resolver/MockResolverIT.java |
| 76 | +index 0498462ce1..d99e0d3f68 100644 |
| 77 | +--- a/integration-tests/src/test/java/com/datastax/oss/driver/core/resolver/MockResolverIT.java |
| 78 | ++++ b/integration-tests/src/test/java/com/datastax/oss/driver/core/resolver/MockResolverIT.java |
| 79 | +@@ -274,8 +274,10 @@ public class MockResolverIT { |
| 80 | + int counter = 0; |
| 81 | + while (filteredNodes.size() == 1) { |
| 82 | + counter++; |
| 83 | +- if (counter == 255) { |
| 84 | +- LOG.error("Completed 254 runs. Breaking."); |
| 85 | ++ // Capping to 99 in the patch because that's what ccm create --help says is the max id |
| 86 | ++ // allowed |
| 87 | ++ if (counter == 99) { |
| 88 | ++ LOG.error("Completed 99 runs. Breaking."); |
| 89 | + break; |
| 90 | + } |
| 91 | + LOG.warn( |
| 92 | +diff --git a/pom.xml b/pom.xml |
| 93 | +index 96fb2401c4..2e491fbafa 100644 |
| 94 | +--- a/pom.xml |
| 95 | ++++ b/pom.xml |
| 96 | +@@ -100,7 +100,7 @@ |
| 97 | + <skipUnitTests>${skipTests}</skipUnitTests> |
| 98 | + <release.autopublish>false</release.autopublish> |
| 99 | + <pushChanges>false</pushChanges> |
| 100 | +- <mockitoopens.argline /> |
| 101 | ++ <mockitoopens.argline/> |
| 102 | + <api.plumber.skip>false</api.plumber.skip> |
| 103 | + </properties> |
| 104 | + <dependencyManagement> |
| 105 | +diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/BaseCcmRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/BaseCcmRule.java |
| 106 | +index b50d56823b..eb12ba2c7c 100644 |
| 107 | +--- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/BaseCcmRule.java |
| 108 | ++++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/BaseCcmRule.java |
| 109 | +@@ -23,9 +23,14 @@ |
| 110 | + */ |
| 111 | + package com.datastax.oss.driver.api.testinfra.ccm; |
| 112 | + |
| 113 | ++import static org.awaitility.Awaitility.await; |
| 114 | ++ |
| 115 | ++import com.datastax.oss.driver.api.core.AllNodesFailedException; |
| 116 | ++import com.datastax.oss.driver.api.core.CqlSession; |
| 117 | + import com.datastax.oss.driver.api.core.DefaultProtocolVersion; |
| 118 | + import com.datastax.oss.driver.api.core.ProtocolVersion; |
| 119 | + import com.datastax.oss.driver.api.core.Version; |
| 120 | ++import com.datastax.oss.driver.api.core.auth.ProgrammaticPlainTextAuthProvider; |
| 121 | + import com.datastax.oss.driver.api.core.metadata.EndPoint; |
| 122 | + import com.datastax.oss.driver.api.testinfra.CassandraResourceRule; |
| 123 | + import com.datastax.oss.driver.api.testinfra.ScyllaOnly; |
| 124 | +@@ -36,12 +41,17 @@ import com.datastax.oss.driver.internal.core.metadata.DefaultEndPoint; |
| 125 | + import java.net.InetSocketAddress; |
| 126 | + import java.util.Collections; |
| 127 | + import java.util.Set; |
| 128 | ++import java.util.concurrent.TimeUnit; |
| 129 | + import org.junit.AssumptionViolatedException; |
| 130 | + import org.junit.runner.Description; |
| 131 | + import org.junit.runners.model.Statement; |
| 132 | ++import org.slf4j.Logger; |
| 133 | ++import org.slf4j.LoggerFactory; |
| 134 | + |
| 135 | + public abstract class BaseCcmRule extends CassandraResourceRule { |
| 136 | + |
| 137 | ++ private static final Logger LOG = LoggerFactory.getLogger(BaseCcmRule.class); |
| 138 | ++ |
| 139 | + protected final CcmBridge ccmBridge; |
| 140 | + |
| 141 | + BaseCcmRule(CcmBridge ccmBridge) { |
| 142 | +@@ -62,6 +72,37 @@ public abstract class BaseCcmRule extends CassandraResourceRule { |
| 143 | + protected void before() { |
| 144 | + ccmBridge.create(); |
| 145 | + ccmBridge.start(); |
| 146 | ++ if (CcmBridge.isDistributionOf(BackendType.SCYLLA) && ccmBridge.isAuthEnabled()) { |
| 147 | ++ awaitScyllaAuth(); |
| 148 | ++ } |
| 149 | ++ } |
| 150 | ++ |
| 151 | ++ private void awaitScyllaAuth() { |
| 152 | ++ InetSocketAddress contact = new InetSocketAddress(ccmBridge.getNodeIpAddress(1), 9042); |
| 153 | ++ LOG.info("Waiting for ScyllaDB superuser to become available at {}", contact); |
| 154 | ++ await() |
| 155 | ++ .atMost(30, TimeUnit.SECONDS) |
| 156 | ++ .pollInterval(1, TimeUnit.SECONDS) |
| 157 | ++ .until( |
| 158 | ++ () -> { |
| 159 | ++ try (CqlSession session = |
| 160 | ++ CqlSession.builder() |
| 161 | ++ .addContactPoint(contact) |
| 162 | ++ .withLocalDatacenter("dc1") |
| 163 | ++ .withAuthProvider( |
| 164 | ++ new ProgrammaticPlainTextAuthProvider("cassandra", "cassandra")) |
| 165 | ++ .build()) { |
| 166 | ++ return session.execute("SELECT key FROM system.local WHERE key='local'").one() |
| 167 | ++ != null; |
| 168 | ++ } catch (AllNodesFailedException e) { |
| 169 | ++ LOG.debug("ScyllaDB superuser not ready yet: {}", e.getMessage()); |
| 170 | ++ return false; |
| 171 | ++ } catch (Exception e) { |
| 172 | ++ LOG.debug("ScyllaDB auth check failed unexpectedly: {}", e.getMessage()); |
| 173 | ++ return false; |
| 174 | ++ } |
| 175 | ++ }); |
| 176 | ++ LOG.info("ScyllaDB superuser is ready"); |
| 177 | + } |
| 178 | + |
| 179 | + @Override |
| 180 | +diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java |
| 181 | +index e7b2b4e4a0..8fbbed8d95 100644 |
| 182 | +--- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java |
| 183 | ++++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java |
| 184 | +@@ -185,6 +185,7 @@ public class CcmBridge implements AutoCloseable { |
| 185 | + private final List<String> createOptions; |
| 186 | + private final List<String> dseWorkloads; |
| 187 | + private final String jvmArgs; |
| 188 | ++ private final boolean authEnabled; |
| 189 | + |
| 190 | + private CcmBridge( |
| 191 | + Path configDirectory, |
| 192 | +@@ -195,7 +196,8 @@ public class CcmBridge implements AutoCloseable { |
| 193 | + List<String> dseConfigurationRawYaml, |
| 194 | + List<String> createOptions, |
| 195 | + Collection<String> jvmArgs, |
| 196 | +- List<String> dseWorkloads) { |
| 197 | ++ List<String> dseWorkloads, |
| 198 | ++ boolean authEnabled) { |
| 199 | + this.configDirectory = configDirectory; |
| 200 | + if (nodes.length == 1) { |
| 201 | + // Hack to ensure that the default DC is always called 'dc1': pass a list ('-nX:0') even if |
| 202 | +@@ -237,6 +239,7 @@ public class CcmBridge implements AutoCloseable { |
| 203 | + } |
| 204 | + this.jvmArgs = allJvmArgs.toString(); |
| 205 | + this.dseWorkloads = dseWorkloads; |
| 206 | ++ this.authEnabled = authEnabled; |
| 207 | + } |
| 208 | + |
| 209 | + // Copied from Netty's PlatformDependent to avoid the dependency on Netty |
| 210 | +@@ -343,7 +346,15 @@ |
| 211 | + if (shouldReplace) { |
| 212 | + versionString = versionString.replace(".0-", "."); |
| 213 | + } |
| 214 | +- return "release:" + versionString; |
| 215 | ++ // When SCYLLA_UNIFIED_PACKAGE is set, a local ScyllaDB tarball is available (e.g. |
| 216 | ++ // Jenkins pre-downloads dev/unreleased builds that are not published to S3). Pass the |
| 217 | ++ // bare version string so CCM resolves via packages_from_env() and uses the local file. |
| 218 | ++ // When not set (e.g. GH Actions CI), prepend "release:" so CCM downloads the published |
| 219 | ++ // release from S3. This avoids "release:<dev-version>" S3 lookup failures on Jenkins. |
| 220 | ++ if (System.getenv("SCYLLA_UNIFIED_PACKAGE") != null) { |
| 221 | ++ return versionString; |
| 222 | ++ } |
| 223 | ++ return "release:" + versionString; |
| 224 | + } |
| 225 | + // for 4.0 or 5.0 pre-releases, the CCM version string needs to be "4.0-alpha1", "4.0-alpha2" or |
| 226 | + // "5.0-beta1" Version.toString() always adds a patch value, even if it's not specified when |
| 227 | +@@ -503,7 +508,18 @@ public class CcmBridge implements AutoCloseable { |
| 228 | + } |
| 229 | + |
| 230 | + public void addWithoutStart(int n, String dc) { |
| 231 | +- String[] initialArgs = new String[] {"add", "-i", ipPrefix + n, "-d", dc, "node" + n}; |
| 232 | ++ // Pass the JMX port explicitly so CCM cannot auto-assign a port that collides with |
| 233 | ++ // another node that was added in the same test run. CCM's auto-assign formula is |
| 234 | ++ // 7000 + nodeid * 100 + cluster.id, where cluster.id is always 0 for CCM clusters |
| 235 | ++ // created by CcmBridge (they are created fresh in a temp config directory). Using |
| 236 | ++ // the same formula here ensures the port is predictable and avoids the "JMX port is |
| 237 | ++ // already in use" error that occurs when CCM resolves the nodeid from the current |
| 238 | ++ // nodelist length rather than from the requested node number. |
| 239 | ++ int jmxPort = 7000 + n * 100; |
| 240 | ++ String[] initialArgs = |
| 241 | ++ new String[] { |
| 242 | ++ "add", "-i", ipPrefix + n, "-j", String.valueOf(jmxPort), "-d", dc, "node" + n |
| 243 | ++ }; |
| 244 | + ArrayList<String> args = new ArrayList<>(Arrays.asList(initialArgs)); |
| 245 | + args.addAll(Arrays.asList(DISTRIBUTION.getCcmOptions())); |
| 246 | + execute(args.toArray(new String[] {})); |
| 247 | +@@ -643,6 +660,15 @@ public class CcmBridge implements AutoCloseable { |
| 248 | + return ipPrefix + nodeId; |
| 249 | + } |
| 250 | + |
| 251 | ++ /** |
| 252 | ++ * Returns {@code true} if a non-AllowAll authenticator was configured for this cluster. Used by |
| 253 | ++ * {@link BaseCcmRule} to determine whether a post-start auth readiness wait is needed on |
| 254 | ++ * ScyllaDB, which provisions the default superuser asynchronously. |
| 255 | ++ */ |
| 256 | ++ boolean isAuthEnabled() { |
| 257 | ++ return authEnabled; |
| 258 | ++ } |
| 259 | ++ |
| 260 | + private static final String IN_MS_STR = "_in_ms"; |
| 261 | + private static final int IN_MS_STR_LENGTH = IN_MS_STR.length(); |
| 262 | + private static final String ENABLE_STR = "enable_"; |
| 263 | +@@ -690,6 +716,7 @@ public class CcmBridge implements AutoCloseable { |
| 264 | + private String ipPrefix; |
| 265 | + private final List<String> createOptions = new ArrayList<>(); |
| 266 | + private final List<String> dseWorkloads = new ArrayList<>(); |
| 267 | ++ private boolean authEnabled = false; |
| 268 | + |
| 269 | + private final Path configDirectory; |
| 270 | + |
| 271 | +@@ -708,6 +735,9 @@ public class CcmBridge implements AutoCloseable { |
| 272 | + |
| 273 | + public Builder withCassandraConfiguration(String key, Object value) { |
| 274 | + cassandraConfiguration.put(key, value); |
| 275 | ++ if ("authenticator".equals(key) && !"AllowAllAuthenticator".equals(value)) { |
| 276 | ++ authEnabled = true; |
| 277 | ++ } |
| 278 | + return this; |
| 279 | + } |
| 280 | + |
| 281 | +@@ -736,6 +766,12 @@ public class CcmBridge implements AutoCloseable { |
| 282 | + return this; |
| 283 | + } |
| 284 | + |
| 285 | ++ /** Sets a numeric cluster ID prefix used by {@link CustomCcmRule} to avoid IP collisions. */ |
| 286 | ++ public Builder withIdPrefix(String idPrefix) { |
| 287 | ++ this.ipPrefix = "127.0." + idPrefix + "."; |
| 288 | ++ return this; |
| 289 | ++ } |
| 290 | ++ |
| 291 | + /** Adds an option to the {@code ccm create} command. */ |
| 292 | + public Builder withCreateOption(String option) { |
| 293 | + this.createOptions.add(option); |
| 294 | +@@ -807,7 +843,8 @@ public class CcmBridge implements AutoCloseable { |
| 295 | + dseRawYaml, |
| 296 | + createOptions, |
| 297 | + jvmArgs, |
| 298 | +- dseWorkloads); |
| 299 | ++ dseWorkloads, |
| 300 | ++ authEnabled); |
| 301 | + } |
| 302 | + } |
| 303 | + |
| 304 | +diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CustomCcmRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CustomCcmRule.java |
| 305 | +index 5ea1bf7ed3..3f7f5be283 100644 |
| 306 | +--- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CustomCcmRule.java |
| 307 | ++++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CustomCcmRule.java |
| 308 | +@@ -17,6 +17,7 @@ |
| 309 | + */ |
| 310 | + package com.datastax.oss.driver.api.testinfra.ccm; |
| 311 | + |
| 312 | ++import java.util.concurrent.atomic.AtomicInteger; |
| 313 | + import java.util.concurrent.atomic.AtomicReference; |
| 314 | + import org.slf4j.Logger; |
| 315 | + import org.slf4j.LoggerFactory; |
| 316 | +@@ -33,6 +34,7 @@ import org.slf4j.LoggerFactory; |
| 317 | + public class CustomCcmRule extends BaseCcmRule { |
| 318 | + |
| 319 | + private static final Logger LOG = LoggerFactory.getLogger(CustomCcmRule.class); |
| 320 | ++ private static final AtomicInteger CLUSTER_ID = new AtomicInteger(1); |
| 321 | + private static final AtomicReference<CustomCcmRule> CURRENT = new AtomicReference<>(); |
| 322 | + |
| 323 | + CustomCcmRule(CcmBridge ccmBridge) { |
| 324 | +@@ -84,6 +86,10 @@ public class CustomCcmRule extends BaseCcmRule { |
| 325 | + |
| 326 | + private final CcmBridge.Builder bridgeBuilder = CcmBridge.builder(); |
| 327 | + |
| 328 | ++ public Builder() { |
| 329 | ++ this.withIdPrefix(Integer.toString(CLUSTER_ID.incrementAndGet())); |
| 330 | ++ } |
| 331 | ++ |
| 332 | + public Builder withNodes(int... nodes) { |
| 333 | + bridgeBuilder.withNodes(nodes); |
| 334 | + return this; |
| 335 | +@@ -134,6 +140,11 @@ public class CustomCcmRule extends BaseCcmRule { |
| 336 | + return this; |
| 337 | + } |
| 338 | + |
| 339 | ++ public Builder withIdPrefix(String idPrefix) { |
| 340 | ++ bridgeBuilder.withIdPrefix(idPrefix); |
| 341 | ++ return this; |
| 342 | ++ } |
| 343 | ++ |
| 344 | + public CustomCcmRule build() { |
| 345 | + return new CustomCcmRule(bridgeBuilder.build()); |
| 346 | + } |
| 347 | +diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionUtils.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionUtils.java |
| 348 | +index 7536c0ffdc..6e13cc5cf5 100644 |
| 349 | +--- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionUtils.java |
| 350 | ++++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/session/SessionUtils.java |
| 351 | +@@ -198,7 +198,7 @@ public class SessionUtils { |
| 352 | + SimpleStatement createKeyspace = |
| 353 | + SimpleStatement.builder( |
| 354 | + String.format( |
| 355 | +- "CREATE KEYSPACE %s WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };", |
| 356 | ++ "CREATE KEYSPACE %s WITH REPLICATION = { 'class' : 'NetworkTopologyStrategy', 'dc1' : 1 } AND tablets = {'enabled': false};", |
| 357 | + keyspace.asCql(false))) |
| 358 | + .setExecutionProfile(profile) |
| 359 | + .build(); |
0 commit comments