This is an automated email from the ASF dual-hosted git repository. asf-gitbox-commits pushed a commit to branch cassandra-6.0 in repository https://gitbox.apache.org/repos/asf/cassandra.git
commit 1166e54ad4a0ecefd1b3be721831838f1dcab104 Author: Marcus Eriksson <[email protected]> AuthorDate: Mon Aug 17 11:57:55 2026 +0200 Node with empty data directory is allowed to rejoin the ring on the second start after the first start is correctly rejected Patch by marcuse; reviewed by Sam Tunnicliffe for CASSANDRA-21633 --- CHANGES.txt | 1 + src/java/org/apache/cassandra/tcm/Startup.java | 10 ++++- .../test/log/BounceResetHostIdTest.java | 46 ++++++++++++++++++++-- 3 files changed, 52 insertions(+), 5 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 4c8ab6276d..c9a8a07948 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 6.0-alpha3 + * Node with empty data directory is allowed to rejoin the ring on the second start after the first start is correctly rejected (CASSANDRA-21633) * Megamorphic call in ClusteringPrefix.Deserializer.deserializeOne slows down reads (CASSANDRA-21587) * Throw InvalidRequestException instead of crashing on malformed IN marker values (CASSANDRA-21648) * Avoid serialization and deserialization for coordinator-local single partition data read (CASSANDRA-21354) diff --git a/src/java/org/apache/cassandra/tcm/Startup.java b/src/java/org/apache/cassandra/tcm/Startup.java index 0035b46936..0f450c5819 100644 --- a/src/java/org/apache/cassandra/tcm/Startup.java +++ b/src/java/org/apache/cassandra/tcm/Startup.java @@ -232,10 +232,15 @@ import static org.apache.cassandra.utils.FBUtilities.getBroadcastAddressAndPort; UUID currentHostId = SystemKeyspace.getLocalHostId(); if (nodeId != NodeId.UNREGISTERED && !Objects.equals(nodeId.toUUID(), currentHostId)) { + //Returning here if no host id was found in the system.local table means startup will fall + //through to the path taken by a new node attempting to join the cluster with a broadcast + //address that is already associated with an existing member. We could choose to report an + //IP collision here unless StorageService.isReplacingSameAddress() is true, but that would + //lead to inconsistent messages/logging across multiple restarts as it would be dependent + //on whether the node had performed catch up from a peer or not. if (currentHostId == null) { - logger.info("Taking over the host ID: {}, replacing address {}", nodeId.toUUID(), FBUtilities.getBroadcastAddressAndPort()); - SystemKeyspace.setLocalHostId(nodeId.toUUID()); + logger.debug("No host id set for node {} in system.local", nodeId); return; } @@ -689,6 +694,7 @@ import static org.apache.cassandra.utils.FBUtilities.getBroadcastAddressAndPort; throw new IllegalStateException("Cannot replace same address when accord transactions are enabled."); } + SystemKeyspace.setLocalHostId(self.toUUID()); ReplaceSameAddress.streamData(self, metadata, shouldBootstrap, finishJoiningRing); } diff --git a/test/distributed/org/apache/cassandra/distributed/test/log/BounceResetHostIdTest.java b/test/distributed/org/apache/cassandra/distributed/test/log/BounceResetHostIdTest.java index 0a8a31e3a2..ad0810b48f 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/log/BounceResetHostIdTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/log/BounceResetHostIdTest.java @@ -18,12 +18,14 @@ package org.apache.cassandra.distributed.test.log; +import java.io.IOException; import java.net.InetAddress; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Arrays; import java.util.Comparator; import java.util.concurrent.TimeUnit; -import org.junit.Assert; import org.junit.Test; import org.apache.cassandra.distributed.Cluster; @@ -31,12 +33,15 @@ import org.apache.cassandra.distributed.api.ConsistencyLevel; import org.apache.cassandra.distributed.api.Feature; import org.apache.cassandra.distributed.shared.AssertUtils; import org.apache.cassandra.distributed.shared.ClusterUtils; +import org.apache.cassandra.distributed.shared.Uninterruptibles; import org.apache.cassandra.distributed.test.TestBaseImpl; import org.apache.cassandra.locator.NoOpProximity; import org.apache.cassandra.locator.SimpleLocationProvider; import org.apache.cassandra.tcm.membership.NodeId; +import static org.apache.cassandra.distributed.Constants.KEY_DTEST_API_STARTUP_FAILURE_AS_SHUTDOWN; import static org.apache.cassandra.distributed.shared.AssertUtils.row; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; public class BounceResetHostIdTest extends TestBaseImpl @@ -120,7 +125,7 @@ public class BounceResetHostIdTest extends TestBaseImpl } catch (Throwable t) { - Assert.assertTrue(t.getMessage().contains("NodeId does not match locally set one")); + assertTrue(t.getMessage().contains("NodeId does not match locally set one")); } try { @@ -129,7 +134,7 @@ public class BounceResetHostIdTest extends TestBaseImpl } catch (Throwable t) { - Assert.assertTrue(t.getMessage().contains("NodeId does not match locally set one")); + assertTrue(t.getMessage().contains("NodeId does not match locally set one")); } } } @@ -138,4 +143,39 @@ public class BounceResetHostIdTest extends TestBaseImpl Arrays.sort(rows, Comparator.comparing(r -> ((InetAddress)r[0]).getHostAddress())); return rows; } + + @Test + public void bounceWipedDatadirectoryTest() throws Exception + { + try (Cluster cluster = init(builder().withNodes(3) + .withDataDirCount(1) + .withConfig(c -> c.with(Feature.GOSSIP, Feature.NATIVE_PROTOCOL) + .set(KEY_DTEST_API_STARTUP_FAILURE_AS_SHUTDOWN, false)) + .start())) + { + cluster.get(3).shutdown().get(); + wipeDir(((String[]) cluster.get(3).config().get("data_file_directories"))[0]); + wipeDir((String) cluster.get(3).config().get("commitlog_directory")); + for (int i = 0; i < 5; i++) + { + try + { + cluster.get(3).startup(); + fail(); + } + catch (Exception e) + { + assertTrue(e.getMessage(), e.getMessage().contains("Use cassandra.replace_address if you want to replace this node")); + cluster.get(3).shutdown().get(); + } + Uninterruptibles.sleepUninterruptibly(5, TimeUnit.SECONDS); + } + } + } + private static void wipeDir(String dir) throws IOException + { + Path source = Path.of(dir); + Files.move(source, Path.of(source+"backup")); // remove potential trailing / from dir + Files.createDirectories(source); + } } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
