You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by ma...@apache.org on 2026/09/21 08:52:31 UTC
(cassandra) 01/04: Node with empty data directory is allowed to rejoin the ring on the second start after the first start is correctly rejected
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 <ma...@apache.org>
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: commits-unsubscribe@cassandra.apache.org
For additional commands, e-mail: commits-help@cassandra.apache.org