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]

Reply via email to