diff --git a/CHANGES.txt b/CHANGES.txt index be3a765b0f61..a89ce7871677 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 4.1.12 + * Fix UnsupportedOperationException in repairPaxosForTopologyChange when there are insufficient live nodes (CASSANDRA-21427) * Add Paxos v2 option and informatin in cassandra.yaml (CASSANDRA-21316) * Harden data resurrection startup check with atomic heartbeat file write with fallback (CASSANDRA-21290) Merged from 4.0: diff --git a/src/java/org/apache/cassandra/service/ActiveRepairService.java b/src/java/org/apache/cassandra/service/ActiveRepairService.java index eed52b78f8af..112014f98fab 100644 --- a/src/java/org/apache/cassandra/service/ActiveRepairService.java +++ b/src/java/org/apache/cassandra/service/ActiveRepairService.java @@ -1038,7 +1038,6 @@ public Future repairPaxosForTopologyChange(String ksName, Collection downEndpoints = replication.getNaturalReplicas(range.right).filter(e -> !endpoints.contains(e)).endpoints(); - downEndpoints.removeAll(endpoints); throw new RuntimeException(String.format("Insufficient live nodes to repair paxos for %s in %s for %s.\n" + "There must be enough live nodes to satisfy EACH_QUORUM, but the following nodes are down: %s\n" + diff --git a/test/unit/org/apache/cassandra/service/ActiveRepairServiceTest.java b/test/unit/org/apache/cassandra/service/ActiveRepairServiceTest.java index 9c1660a9c39f..8bb6df8ac087 100644 --- a/test/unit/org/apache/cassandra/service/ActiveRepairServiceTest.java +++ b/test/unit/org/apache/cassandra/service/ActiveRepairServiceTest.java @@ -83,6 +83,7 @@ public class ActiveRepairServiceTest { public static final String KEYSPACE5 = "Keyspace5"; + public static final String KEYSPACE_RF3 = "KeyspaceRf3"; public static final String CF_STANDARD1 = "Standard1"; public static final String CF_COUNTER = "Counter1"; public static final int TASK_SECONDS = 10; @@ -101,6 +102,9 @@ public static void defineSchema() throws ConfigurationException KeyspaceParams.simple(2), SchemaLoader.standardCFMD(KEYSPACE5, CF_COUNTER), SchemaLoader.standardCFMD(KEYSPACE5, CF_STANDARD1)); + SchemaLoader.createKeyspace(KEYSPACE_RF3, + KeyspaceParams.simple(3), + SchemaLoader.standardCFMD(KEYSPACE_RF3, CF_STANDARD1)); } @Before @@ -265,6 +269,38 @@ public void testParentRepairStatus() throws Throwable } + @Test + public void repairPaxosForTopologyChangeThrowsRuntimeExceptionWhenInsufficientLiveNodes() throws Throwable + { + TokenMetadata tmd = StorageService.instance.getTokenMetadata(); + tmd.clearUnsafe(); + + // Local node is the only live replica. + tmd.updateNormalToken(tmd.partitioner.getRandomToken(), FBUtilities.getBroadcastAddressAndPort()); + // Two additional natural replicas with no gossip state -> treated as down by the FailureDetector. + tmd.updateNormalToken(tmd.partitioner.getRandomToken(), InetAddressAndPort.getByName("127.0.0.3")); + tmd.updateNormalToken(tmd.partitioner.getRandomToken(), InetAddressAndPort.getByName("127.0.0.4")); + + // With exactly RF endpoints in the ring, every range's natural replicas are all three nodes. + Token token = tmd.partitioner.getMinimumToken(); + Collection> ranges = Collections.singleton(new Range<>(token, token)); + + try + { + ActiveRepairService.instance.repairPaxosForTopologyChange(KEYSPACE_RF3, ranges, "test"); + Assert.fail("Expected RuntimeException due to insufficient live nodes"); + } + catch (UnsupportedOperationException e) + { + Assert.fail("repairPaxosForTopologyChange must not throw UnsupportedOperationException: " + e); + } + catch (RuntimeException e) + { + Assert.assertTrue("Unexpected message: " + e.getMessage(), + e.getMessage() != null && e.getMessage().contains("Insufficient live nodes to repair paxos")); + } + } + Set addTokens(int max) throws Throwable { TokenMetadata tmd = StorageService.instance.getTokenMetadata();