From 8ea25e8ed991085037b8788efee89fea46f9cbae Mon Sep 17 00:00:00 2001 From: Alan Wang Date: Wed, 18 Mar 2026 10:38:30 -0700 Subject: [PATCH 1/4] test --- ...ccordMigrationDefaultPrimaryRangeTest.java | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 test/distributed/org/apache/cassandra/distributed/test/accord/AccordMigrationDefaultPrimaryRangeTest.java diff --git a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordMigrationDefaultPrimaryRangeTest.java b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordMigrationDefaultPrimaryRangeTest.java new file mode 100644 index 000000000000..ce059ec6d908 --- /dev/null +++ b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordMigrationDefaultPrimaryRangeTest.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.cassandra.distributed.test.accord; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.cassandra.distributed.api.ConsistencyLevel; +import org.apache.cassandra.distributed.api.Feature; +import org.apache.cassandra.distributed.api.NodeToolResult; +import org.apache.cassandra.distributed.api.SimpleQueryResult; +import org.apache.cassandra.schema.Schema; +import org.apache.cassandra.schema.TableId; + +public class AccordMigrationDefaultPrimaryRangeTest extends AccordTestBase +{ + private static final Logger logger = LoggerFactory.getLogger(AccordMigrationDefaultPrimaryRangeTest.class); + + @Override + protected Logger logger() + { + return logger; + } + + @BeforeClass + public static void setupClass() throws IOException + { + AccordTestBase.setupCluster(builder -> builder + .withoutVNodes() + .withConfig(config -> + config + .set("paxos_variant", "v2") + .with(Feature.NETWORK, Feature.GOSSIP)), 6); + } + + @Test + public void accordSinglePartitionKeyBatchTest() throws Throwable + { + List ddls = Arrays.asList("DROP KEYSPACE IF EXISTS " + KEYSPACE + ';', + "CREATE KEYSPACE " + KEYSPACE + " WITH REPLICATION={'class':'SimpleStrategy', 'replication_factor': 3}", + "CREATE TABLE " + qualifiedRegularTableName + " (k int PRIMARY KEY, v int)"); + test(ddls, cluster -> { + NodeToolResult result = cluster.get(1).nodetoolResult("consensus_admin", "begin-migration", KEYSPACE, regularTableName); + System.out.println(result.toString()); + }); + } +} \ No newline at end of file From 80b26af0ac10775f06f4b6c1a4e83993326ab9ed Mon Sep 17 00:00:00 2001 From: Alan Wang Date: Thu, 19 Mar 2026 14:14:15 -0700 Subject: [PATCH 2/4] initial --- .../operating/onboarding-to-accord.adoc | 11 +-- .../migration/ConsensusTableMigration.java | 13 +++- .../test/accord/AccordCQLTestBase.java | 28 ++++++++ ...ccordMigrationDefaultPrimaryRangeTest.java | 69 ------------------- 4 files changed, 45 insertions(+), 76 deletions(-) delete mode 100644 test/distributed/org/apache/cassandra/distributed/test/accord/AccordMigrationDefaultPrimaryRangeTest.java diff --git a/doc/modules/cassandra/pages/managing/operating/onboarding-to-accord.adoc b/doc/modules/cassandra/pages/managing/operating/onboarding-to-accord.adoc index e9fe5e386f41..38c969e4b999 100644 --- a/doc/modules/cassandra/pages/managing/operating/onboarding-to-accord.adoc +++ b/doc/modules/cassandra/pages/managing/operating/onboarding-to-accord.adoc @@ -229,8 +229,9 @@ Marking ranges as migrating is a lightweight operation and does not trigger the repairs that will finish the migration. The range to mark migrating needs to be explicitly -provided otherwise the entire ring will be marked migrating for the -specified keyspace and tables. If the entire range is marked migrating it is +provided otherwise the token ranges replicated on the node +you called the command on will be marked migrating. +If the entire range is marked migrating it is only necessary to invoke `begin-migration` on one node. This is only needed if @@ -247,9 +248,9 @@ systems may need to be used to execute the request. Invoking `nodetool` with `consensus++_++admin finish-migration ++[<++keyspace++>++ ++<++tables++>++...` will run the repairs needed to complete the migration for the specified -ranges. If no range is specified it will default to the primary range of -the node that `nodetool` is connecting to so you can call it once on -every node to complete migration. +ranges. If no range is specified it will default to all token ranges replicated by +the node for all keyspaces migrating to Accord.. This is the same behavior +as the default for repair. When migrating from Paxos to Accord it will run an incremental data repair and then a full data repair {plus} Paxos repair. When migrating diff --git a/src/java/org/apache/cassandra/service/consensus/migration/ConsensusTableMigration.java b/src/java/org/apache/cassandra/service/consensus/migration/ConsensusTableMigration.java index 7a954c17ef89..52d64b1e2a95 100644 --- a/src/java/org/apache/cassandra/service/consensus/migration/ConsensusTableMigration.java +++ b/src/java/org/apache/cassandra/service/consensus/migration/ConsensusTableMigration.java @@ -201,8 +201,17 @@ public static void startMigrationToConsensusProtocol(@Nullable List keys IPartitioner partitioner = DatabaseDescriptor.getPartitioner(); Optional>> maybeParsedRanges = maybeRangesStr.map(rangesStr -> ImmutableList.copyOf(RepairOption.parseRanges(rangesStr, partitioner))); - Token minToken = partitioner.getMinimumToken(); - NormalizedRanges ranges = normalizedRanges(maybeParsedRanges.orElse(ImmutableList.of(new Range(minToken, minToken)))); + + NormalizedRanges ranges; + if (maybeParsedRanges.isPresent()) + ranges = normalizedRanges(maybeParsedRanges.get()); + else + { + List> defaultRanges = new ArrayList<>(); + for (String keyspaceName : keyspaceNames) + defaultRanges.addAll(StorageService.instance.getLocalReplicas(keyspaceName).onlyFull().ranges()); + ranges = normalizedRanges(defaultRanges); + } ClusterMetadataService.instance().commit(new BeginConsensusMigrationForTableAndRange(ranges, tableIds)); } diff --git a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordCQLTestBase.java b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordCQLTestBase.java index dbf6d6553f64..e7e45c3fc093 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordCQLTestBase.java +++ b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordCQLTestBase.java @@ -70,19 +70,26 @@ import org.apache.cassandra.db.marshal.SetType; import org.apache.cassandra.db.marshal.UTF8Type; import org.apache.cassandra.dht.Murmur3Partitioner; +import org.apache.cassandra.dht.Range; +import org.apache.cassandra.dht.Token; import org.apache.cassandra.distributed.Cluster; import org.apache.cassandra.distributed.api.ConsistencyLevel; import org.apache.cassandra.distributed.api.ICoordinator; +import org.apache.cassandra.distributed.api.NodeToolResult; import org.apache.cassandra.distributed.api.QueryResults; import org.apache.cassandra.distributed.api.SimpleQueryResult; import org.apache.cassandra.distributed.shared.AssertUtils; import org.apache.cassandra.distributed.test.sai.SAIUtil; import org.apache.cassandra.distributed.util.QueryResultUtil; import org.apache.cassandra.exceptions.InvalidRequestException; +import org.apache.cassandra.schema.Schema; +import org.apache.cassandra.service.StorageService; import org.apache.cassandra.service.accord.AccordService; import org.apache.cassandra.service.accord.AccordTestUtils; import org.apache.cassandra.service.consensus.TransactionalMode; +import org.apache.cassandra.service.consensus.migration.ConsensusMigrationState; import org.apache.cassandra.service.consensus.migration.TransactionalMigrationFromMode; +import org.apache.cassandra.tcm.ClusterMetadataService; import org.apache.cassandra.utils.AssertionUtils; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.FailingConsumer; @@ -93,6 +100,7 @@ import static org.apache.cassandra.cql3.CQLTester.row; import static org.apache.cassandra.cql3.statements.schema.AlterTableStatement.ACCORD_COUNTER_COLUMN_UNSUPPORTED; import static org.apache.cassandra.cql3.statements.schema.AlterTableStatement.ACCORD_COUNTER_TABLES_UNSUPPORTED; +import static org.apache.cassandra.dht.NormalizedRanges.normalizedRanges; import static org.apache.cassandra.distributed.api.ConsistencyLevel.QUORUM; import static org.apache.cassandra.distributed.api.Feature.GOSSIP; import static org.apache.cassandra.distributed.api.Feature.NATIVE_PROTOCOL; @@ -304,6 +312,26 @@ public void testCounterAddColumnFailsWithMigration() throws Exception }); } + @Test + public void testBeginMigrationTargetsOnlyLocalRanges() throws Exception + { + List ddls = Arrays.asList("DROP KEYSPACE IF EXISTS " + KEYSPACE + ';', + "CREATE KEYSPACE " + KEYSPACE + " WITH REPLICATION={'class':'SimpleStrategy', 'replication_factor': 3}", + "CREATE TABLE " + qualifiedRegularTableName + " (k int PRIMARY KEY, v int)"); + test(ddls, cluster -> { + cluster.schemaChange("ALTER TABLE " + qualifiedRegularTableName + " WITH transactional_mode = 'full'"); + NodeToolResult result = cluster.get(1).nodetoolResult("consensus_admin", "begin-migration"); + + String keyspace = KEYSPACE; + String tableName = regularTableName; + cluster.get(1).runOnInstance(() -> { + ConsensusMigrationState state = ClusterMetadataService.instance().metadata().consensusMigrationState; + List> expectedMigratingRange = StorageService.instance.getLocalRanges(keyspace); + assert(state.tableStates.get(Schema.instance.getTableMetadata(keyspace, tableName).id()).migratingRanges().equals(normalizedRanges(expectedMigratingRange))); + }); + }); + } + @Override protected void test(FailingConsumer fn) throws Exception { diff --git a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordMigrationDefaultPrimaryRangeTest.java b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordMigrationDefaultPrimaryRangeTest.java deleted file mode 100644 index ce059ec6d908..000000000000 --- a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordMigrationDefaultPrimaryRangeTest.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.cassandra.distributed.test.accord; - -import java.io.IOException; -import java.util.Arrays; -import java.util.List; - -import org.junit.BeforeClass; -import org.junit.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.apache.cassandra.distributed.api.ConsistencyLevel; -import org.apache.cassandra.distributed.api.Feature; -import org.apache.cassandra.distributed.api.NodeToolResult; -import org.apache.cassandra.distributed.api.SimpleQueryResult; -import org.apache.cassandra.schema.Schema; -import org.apache.cassandra.schema.TableId; - -public class AccordMigrationDefaultPrimaryRangeTest extends AccordTestBase -{ - private static final Logger logger = LoggerFactory.getLogger(AccordMigrationDefaultPrimaryRangeTest.class); - - @Override - protected Logger logger() - { - return logger; - } - - @BeforeClass - public static void setupClass() throws IOException - { - AccordTestBase.setupCluster(builder -> builder - .withoutVNodes() - .withConfig(config -> - config - .set("paxos_variant", "v2") - .with(Feature.NETWORK, Feature.GOSSIP)), 6); - } - - @Test - public void accordSinglePartitionKeyBatchTest() throws Throwable - { - List ddls = Arrays.asList("DROP KEYSPACE IF EXISTS " + KEYSPACE + ';', - "CREATE KEYSPACE " + KEYSPACE + " WITH REPLICATION={'class':'SimpleStrategy', 'replication_factor': 3}", - "CREATE TABLE " + qualifiedRegularTableName + " (k int PRIMARY KEY, v int)"); - test(ddls, cluster -> { - NodeToolResult result = cluster.get(1).nodetoolResult("consensus_admin", "begin-migration", KEYSPACE, regularTableName); - System.out.println(result.toString()); - }); - } -} \ No newline at end of file From fcbb9faeb16e0e1e41d2f339549f41899fb0402c Mon Sep 17 00:00:00 2001 From: Alan Wang Date: Thu, 19 Mar 2026 14:25:12 -0700 Subject: [PATCH 3/4] fix typo --- .../pages/managing/operating/onboarding-to-accord.adoc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/modules/cassandra/pages/managing/operating/onboarding-to-accord.adoc b/doc/modules/cassandra/pages/managing/operating/onboarding-to-accord.adoc index 38c969e4b999..e42699720168 100644 --- a/doc/modules/cassandra/pages/managing/operating/onboarding-to-accord.adoc +++ b/doc/modules/cassandra/pages/managing/operating/onboarding-to-accord.adoc @@ -249,8 +249,7 @@ Invoking `nodetool` with `consensus++_++admin finish-migration ++[<++keyspace++>++ ++<++tables++>++...` will run the repairs needed to complete the migration for the specified ranges. If no range is specified it will default to all token ranges replicated by -the node for all keyspaces migrating to Accord.. This is the same behavior -as the default for repair. +the node. This is the same behavior as the default for repair. When migrating from Paxos to Accord it will run an incremental data repair and then a full data repair {plus} Paxos repair. When migrating From d0a3966eb79822ac310758f3e6d462ef7f023898 Mon Sep 17 00:00:00 2001 From: Alan Wang Date: Mon, 23 Mar 2026 14:58:33 -0700 Subject: [PATCH 4/4] update begin and finish migration to only work on the primary range of the node nodetool was called on --- .../operating/onboarding-to-accord.adoc | 12 ++--- .../migration/ConsensusTableMigration.java | 4 +- .../test/accord/AccordCQLTestBase.java | 28 ------------ .../test/accord/AccordMigrationTest.java | 44 +++++++++++++++++++ 4 files changed, 52 insertions(+), 36 deletions(-) diff --git a/doc/modules/cassandra/pages/managing/operating/onboarding-to-accord.adoc b/doc/modules/cassandra/pages/managing/operating/onboarding-to-accord.adoc index e42699720168..e105aeacdf9d 100644 --- a/doc/modules/cassandra/pages/managing/operating/onboarding-to-accord.adoc +++ b/doc/modules/cassandra/pages/managing/operating/onboarding-to-accord.adoc @@ -229,10 +229,9 @@ Marking ranges as migrating is a lightweight operation and does not trigger the repairs that will finish the migration. The range to mark migrating needs to be explicitly -provided otherwise the token ranges replicated on the node -you called the command on will be marked migrating. -If the entire range is marked migrating it is -only necessary to invoke `begin-migration` on one node. +provided otherwise it will default to the primary range +of the node that `nodetool` is connecting to. +This is similar to running repair with the -pr flag. This is only needed if `accord.default++_++transactional++_++mode=explicit` is set in @@ -248,8 +247,9 @@ systems may need to be used to execute the request. Invoking `nodetool` with `consensus++_++admin finish-migration ++[<++keyspace++>++ ++<++tables++>++...` will run the repairs needed to complete the migration for the specified -ranges. If no range is specified it will default to all token ranges replicated by -the node. This is the same behavior as the default for repair. +ranges. If no range is specified it will default to the primary range of +the node that `nodetool` is connecting to so you can call it once on +every node to complete migration. When migrating from Paxos to Accord it will run an incremental data repair and then a full data repair {plus} Paxos repair. When migrating diff --git a/src/java/org/apache/cassandra/service/consensus/migration/ConsensusTableMigration.java b/src/java/org/apache/cassandra/service/consensus/migration/ConsensusTableMigration.java index 52d64b1e2a95..ffd1677722a5 100644 --- a/src/java/org/apache/cassandra/service/consensus/migration/ConsensusTableMigration.java +++ b/src/java/org/apache/cassandra/service/consensus/migration/ConsensusTableMigration.java @@ -209,7 +209,7 @@ public static void startMigrationToConsensusProtocol(@Nullable List keys { List> defaultRanges = new ArrayList<>(); for (String keyspaceName : keyspaceNames) - defaultRanges.addAll(StorageService.instance.getLocalReplicas(keyspaceName).onlyFull().ranges()); + defaultRanges.addAll(StorageService.instance.getPrimaryRanges(keyspaceName)); ranges = normalizedRanges(defaultRanges); } @@ -225,7 +225,7 @@ public static Integer finishMigrationToConsensusProtocol(@Nonnull String keyspac checkNotNull(target); ClusterMetadata cm = ClusterMetadata.current(); - Optional>> localKeyspaceRanges = Optional.of(ImmutableList.copyOf(StorageService.instance.getLocalReplicas(keyspace).onlyFull().ranges())); + Optional>> localKeyspaceRanges = Optional.of(ImmutableList.copyOf(StorageService.instance.getPrimaryRanges(keyspace))); List> ranges = maybeRangesToRanges(maybeRangesStr, localKeyspaceRanges); Map allTableMigrationStates = ClusterMetadata.current().consensusMigrationState.tableStates; List tableIds = keyspacesAndTablesToTableIds(cm, ImmutableList.of(keyspace), maybeTables, Optional.of(allTableMigrationStates::containsKey)); diff --git a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordCQLTestBase.java b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordCQLTestBase.java index e7e45c3fc093..dbf6d6553f64 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordCQLTestBase.java +++ b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordCQLTestBase.java @@ -70,26 +70,19 @@ import org.apache.cassandra.db.marshal.SetType; import org.apache.cassandra.db.marshal.UTF8Type; import org.apache.cassandra.dht.Murmur3Partitioner; -import org.apache.cassandra.dht.Range; -import org.apache.cassandra.dht.Token; import org.apache.cassandra.distributed.Cluster; import org.apache.cassandra.distributed.api.ConsistencyLevel; import org.apache.cassandra.distributed.api.ICoordinator; -import org.apache.cassandra.distributed.api.NodeToolResult; import org.apache.cassandra.distributed.api.QueryResults; import org.apache.cassandra.distributed.api.SimpleQueryResult; import org.apache.cassandra.distributed.shared.AssertUtils; import org.apache.cassandra.distributed.test.sai.SAIUtil; import org.apache.cassandra.distributed.util.QueryResultUtil; import org.apache.cassandra.exceptions.InvalidRequestException; -import org.apache.cassandra.schema.Schema; -import org.apache.cassandra.service.StorageService; import org.apache.cassandra.service.accord.AccordService; import org.apache.cassandra.service.accord.AccordTestUtils; import org.apache.cassandra.service.consensus.TransactionalMode; -import org.apache.cassandra.service.consensus.migration.ConsensusMigrationState; import org.apache.cassandra.service.consensus.migration.TransactionalMigrationFromMode; -import org.apache.cassandra.tcm.ClusterMetadataService; import org.apache.cassandra.utils.AssertionUtils; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.FailingConsumer; @@ -100,7 +93,6 @@ import static org.apache.cassandra.cql3.CQLTester.row; import static org.apache.cassandra.cql3.statements.schema.AlterTableStatement.ACCORD_COUNTER_COLUMN_UNSUPPORTED; import static org.apache.cassandra.cql3.statements.schema.AlterTableStatement.ACCORD_COUNTER_TABLES_UNSUPPORTED; -import static org.apache.cassandra.dht.NormalizedRanges.normalizedRanges; import static org.apache.cassandra.distributed.api.ConsistencyLevel.QUORUM; import static org.apache.cassandra.distributed.api.Feature.GOSSIP; import static org.apache.cassandra.distributed.api.Feature.NATIVE_PROTOCOL; @@ -312,26 +304,6 @@ public void testCounterAddColumnFailsWithMigration() throws Exception }); } - @Test - public void testBeginMigrationTargetsOnlyLocalRanges() throws Exception - { - List ddls = Arrays.asList("DROP KEYSPACE IF EXISTS " + KEYSPACE + ';', - "CREATE KEYSPACE " + KEYSPACE + " WITH REPLICATION={'class':'SimpleStrategy', 'replication_factor': 3}", - "CREATE TABLE " + qualifiedRegularTableName + " (k int PRIMARY KEY, v int)"); - test(ddls, cluster -> { - cluster.schemaChange("ALTER TABLE " + qualifiedRegularTableName + " WITH transactional_mode = 'full'"); - NodeToolResult result = cluster.get(1).nodetoolResult("consensus_admin", "begin-migration"); - - String keyspace = KEYSPACE; - String tableName = regularTableName; - cluster.get(1).runOnInstance(() -> { - ConsensusMigrationState state = ClusterMetadataService.instance().metadata().consensusMigrationState; - List> expectedMigratingRange = StorageService.instance.getLocalRanges(keyspace); - assert(state.tableStates.get(Schema.instance.getTableMetadata(keyspace, tableName).id()).migratingRanges().equals(normalizedRanges(expectedMigratingRange))); - }); - }); - } - @Override protected void test(FailingConsumer fn) throws Exception { diff --git a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordMigrationTest.java b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordMigrationTest.java index 869fb3487b5e..2dd3016e71c4 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordMigrationTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordMigrationTest.java @@ -21,6 +21,8 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; @@ -91,6 +93,7 @@ import org.apache.cassandra.service.paxos.Commit.Proposal; import org.apache.cassandra.service.paxos.PaxosState; import org.apache.cassandra.tcm.ClusterMetadata; +import org.apache.cassandra.tcm.ClusterMetadataService; import org.apache.cassandra.tcm.Epoch; import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.utils.ByteArrayUtil; @@ -569,6 +572,47 @@ public void testPaxosToAccordSerialRead() throws Exception }); } + @Test + public void testBeginMigrationTargetsOnlyLocalRanges() throws Exception + { + List ddls = Arrays.asList("DROP KEYSPACE IF EXISTS " + KEYSPACE + ';', + "CREATE KEYSPACE " + KEYSPACE + " WITH REPLICATION={'class':'SimpleStrategy', 'replication_factor': 2}", + "CREATE TABLE " + qualifiedRegularTableName + " (k int PRIMARY KEY, v int)"); + test(ddls, cluster -> { + cluster.schemaChange("ALTER TABLE " + qualifiedRegularTableName + " WITH " + transactionalMode.asCqlParam()); + cluster.get(1).nodetoolResult("consensus_admin", "begin-migration"); + + String keyspace = KEYSPACE; + String tableName = regularTableName; + cluster.get(1).runOnInstance(() -> { + ConsensusMigrationState state = ClusterMetadataService.instance().metadata().consensusMigrationState; + Collection> expectedMigratingRange = StorageService.instance.getPrimaryRanges(keyspace); + assertEquals(normalizedRanges(expectedMigratingRange), state.tableStates.get(Schema.instance.getTableMetadata(keyspace, tableName).id()).migratingRanges()); + }); + }); + } + + @Test + public void testFinishMigrationTargetsOnlyLocalRanges() throws Exception + { + List ddls = Arrays.asList("DROP KEYSPACE IF EXISTS " + KEYSPACE + ';', + "CREATE KEYSPACE " + KEYSPACE + " WITH REPLICATION={'class':'SimpleStrategy', 'replication_factor': 2}", + "CREATE TABLE " + qualifiedRegularTableName + " (k int PRIMARY KEY, v int)"); + test(ddls, cluster -> { + cluster.schemaChange("ALTER TABLE " + qualifiedRegularTableName + " WITH " + transactionalMode.asCqlParam()); + cluster.get(1).nodetoolResult("consensus_admin", "begin-migration"); + cluster.get(1).nodetoolResult("consensus_admin", "finish-migration"); + + String keyspace = KEYSPACE; + String tableName = regularTableName; + cluster.get(1).runOnInstance(() -> { + ConsensusMigrationState state = ClusterMetadataService.instance().metadata().consensusMigrationState; + Collection> expectedMigratingRange = StorageService.instance.getPrimaryRanges(keyspace); + assertEquals(normalizedRanges(expectedMigratingRange), state.tableStates.get(Schema.instance.getTableMetadata(keyspace, tableName).id()).migratedRanges); + }); + }); + } + private void assertTransactionalModes(String keyspace, String table, TransactionalMode mode, TransactionalMigrationFromMode migration) { forEach(() -> {