You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by mc...@apache.org on 2021/07/12 20:57:02 UTC

[cassandra] branch cassandra-4.0 updated (51f16a3 -> 4dcf7d9)

This is an automated email from the ASF dual-hosted git repository.

mck pushed a change to branch cassandra-4.0
in repository https://gitbox.apache.org/repos/asf/cassandra.git.


    from 51f16a3  Merge branch 'cassandra-4.0.0' into cassandra-4.0
     new b3f9921  Introduce SemVer4j for version representation, parsing and handling. And correct supported upgrade paths. Add v4X to Java DTests (after cassandra-4.0 branch was created)
     new 0a84dda  Merge branch 'cassandra-2.2' into cassandra-3.0
     new 0c1e1cc  Merge branch 'cassandra-3.0' into cassandra-3.11
     new 4dcf7d9  Merge branch 'cassandra-3.11' into cassandra-4.0

The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 build.xml                                          |  2 +-
 .../distributed/impl/AbstractCluster.java          |  2 +-
 .../cassandra/distributed/impl/InstanceConfig.java | 18 +++--
 .../upgrade/CompactStorage3to4UpgradeTest.java     |  2 +-
 .../upgrade/CompactStorageUpgradeTest.java         | 10 +--
 .../cassandra/distributed/upgrade/GroupByTest.java |  2 +-
 .../upgrade/MixedModeAvailabilityTestBase.java     | 17 +++--
 .../upgrade/MixedModeAvailabilityV22Test.java      |  4 +-
 .../upgrade/MixedModeAvailabilityV30Test.java      | 10 +--
 .../upgrade/MixedModeAvailabilityV3XTest.java      |  4 +-
 .../upgrade/MixedModeBatchTestBase.java            | 12 +++-
 .../upgrade/MixedModeConsistencyTestBase.java      | 12 +++-
 .../upgrade/MixedModeConsistencyV22Test.java       |  4 +-
 .../upgrade/MixedModeConsistencyV30Test.java       | 10 +--
 .../upgrade/MixedModeConsistencyV3XTest.java       |  4 +-
 .../upgrade/MixedModeFrom2LoggedBatchTest.java     |  4 +-
 .../upgrade/MixedModeFrom2ReplicationTest.java     |  4 +-
 .../upgrade/MixedModeFrom2UnloggedBatchTest.java   |  4 +-
 .../upgrade/MixedModeFrom3LoggedBatchTest.java     | 12 +---
 .../upgrade/MixedModeFrom3ReplicationTest.java     | 12 +---
 .../upgrade/MixedModeFrom3UnloggedBatchTest.java   | 12 +---
 .../distributed/upgrade/MixedModeGossipTest.java   | 10 +--
 .../distributed/upgrade/MixedModeReadTest.java     |  5 +-
 .../distributed/upgrade/MixedModeRepairTest.java   |  4 +-
 .../upgrade/MixedModeReplicationTestBase.java      | 12 +++-
 .../cassandra/distributed/upgrade/PagingTest.java  |  2 +-
 .../upgrade/Pre40MessageFilterTest.java            |  5 +-
 .../ReadRepairCompactStorageUpgradeTest.java       |  4 +-
 .../cassandra/distributed/upgrade/UpgradeTest.java |  2 +-
 .../distributed/upgrade/UpgradeTestBase.java       | 84 ++++++++++++++--------
 30 files changed, 152 insertions(+), 137 deletions(-)

---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cassandra.apache.org
For additional commands, e-mail: commits-help@cassandra.apache.org


[cassandra] 01/01: Merge branch 'cassandra-3.11' into cassandra-4.0

Posted by mc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mck pushed a commit to branch cassandra-4.0
in repository https://gitbox.apache.org/repos/asf/cassandra.git

commit 4dcf7d9ebd5c330b0dc438054d595fe63fe60bbe
Merge: 51f16a3 0c1e1cc
Author: Mick Semb Wever <mc...@apache.org>
AuthorDate: Mon Jul 12 22:31:23 2021 +0200

    Merge branch 'cassandra-3.11' into cassandra-4.0

 build.xml                                          |  2 +-
 .../distributed/impl/AbstractCluster.java          |  2 +-
 .../cassandra/distributed/impl/InstanceConfig.java | 18 +++--
 .../upgrade/CompactStorage3to4UpgradeTest.java     |  2 +-
 .../upgrade/CompactStorageUpgradeTest.java         | 10 +--
 .../cassandra/distributed/upgrade/GroupByTest.java |  2 +-
 .../upgrade/MixedModeAvailabilityTestBase.java     | 17 +++--
 .../upgrade/MixedModeAvailabilityV22Test.java      |  4 +-
 .../upgrade/MixedModeAvailabilityV30Test.java      | 10 +--
 .../upgrade/MixedModeAvailabilityV3XTest.java      |  4 +-
 .../upgrade/MixedModeBatchTestBase.java            | 12 +++-
 .../upgrade/MixedModeConsistencyTestBase.java      | 12 +++-
 .../upgrade/MixedModeConsistencyV22Test.java       |  4 +-
 .../upgrade/MixedModeConsistencyV30Test.java       | 10 +--
 .../upgrade/MixedModeConsistencyV3XTest.java       |  4 +-
 .../upgrade/MixedModeFrom2LoggedBatchTest.java     |  4 +-
 .../upgrade/MixedModeFrom2ReplicationTest.java     |  4 +-
 .../upgrade/MixedModeFrom2UnloggedBatchTest.java   |  4 +-
 .../upgrade/MixedModeFrom3LoggedBatchTest.java     | 12 +---
 .../upgrade/MixedModeFrom3ReplicationTest.java     | 12 +---
 .../upgrade/MixedModeFrom3UnloggedBatchTest.java   | 12 +---
 .../distributed/upgrade/MixedModeGossipTest.java   | 10 +--
 .../distributed/upgrade/MixedModeReadTest.java     |  5 +-
 .../distributed/upgrade/MixedModeRepairTest.java   |  4 +-
 .../upgrade/MixedModeReplicationTestBase.java      | 12 +++-
 .../cassandra/distributed/upgrade/PagingTest.java  |  2 +-
 .../upgrade/Pre40MessageFilterTest.java            |  5 +-
 .../ReadRepairCompactStorageUpgradeTest.java       |  4 +-
 .../cassandra/distributed/upgrade/UpgradeTest.java |  2 +-
 .../distributed/upgrade/UpgradeTestBase.java       | 84 ++++++++++++++--------
 30 files changed, 152 insertions(+), 137 deletions(-)

diff --cc build.xml
index 6d15381,a2a59d8..d93992d
--- a/build.xml
+++ b/build.xml
@@@ -521,39 -406,43 +521,39 @@@
            <dependency groupId="com.fasterxml.jackson.core" artifactId="jackson-annotations" version="2.9.10"/>
            <dependency groupId="com.googlecode.json-simple" artifactId="json-simple" version="1.1"/>
            <dependency groupId="com.boundary" artifactId="high-scale-lib" version="1.0.6"/>
 -          <dependency groupId="com.github.jbellis" artifactId="jamm" version="0.3.0"/>
 -
 -          <dependency groupId="com.thinkaurelius.thrift" artifactId="thrift-server" version="0.3.7">
 -            <exclusion groupId="org.slf4j" artifactId="slf4j-log4j12"/>
 -            <exclusion groupId="junit" artifactId="junit"/>
 +          <dependency groupId="com.github.jbellis" artifactId="jamm" version="${jamm.version}"/>
 +          <dependency groupId="org.yaml" artifactId="snakeyaml" version="1.26"/>
 +          <dependency groupId="junit" artifactId="junit" version="4.12" scope="test">
 +            <exclusion groupId="org.hamcrest" artifactId="hamcrest-core"/>
            </dependency>
 -          <dependency groupId="org.yaml" artifactId="snakeyaml" version="1.11"/>
 -          <dependency groupId="org.apache.thrift" artifactId="libthrift" version="0.9.2">
 -	         <exclusion groupId="commons-logging" artifactId="commons-logging"/>
 +          <dependency groupId="org.mockito" artifactId="mockito-core" version="3.2.4" scope="test"/>
 +          <dependency groupId="org.quicktheories" artifactId="quicktheories" version="0.26" scope="test"/>
 +          <dependency groupId="com.google.code.java-allocation-instrumenter" artifactId="java-allocation-instrumenter" version="${allocation-instrumenter.version}" scope="test">
 +            <exclusion groupId="com.google.guava" artifactId="guava"/>
            </dependency>
-           <dependency groupId="org.apache.cassandra" artifactId="dtest-api" version="0.0.7" scope="test"/>
 -          <dependency groupId="junit" artifactId="junit" version="4.12" />
 -          <dependency groupId="org.mockito" artifactId="mockito-core" version="3.2.4" />
 -          <dependency groupId="org.apache.cassandra" artifactId="dtest-api" version="0.0.8" />
 -          <dependency groupId="org.reflections" artifactId="reflections" version="0.9.12" />
 -          <dependency groupId="org.quicktheories" artifactId="quicktheories" version="0.25" />
 -          <dependency groupId="org.apache.rat" artifactId="apache-rat" version="0.10">
 -             <exclusion groupId="commons-lang" artifactId="commons-lang"/>
 -          </dependency>
 -          <dependency groupId="org.apache.hadoop" artifactId="hadoop-core" version="1.0.3">
 -          	<exclusion groupId="org.mortbay.jetty" artifactId="servlet-api"/>
 -          	<exclusion groupId="commons-logging" artifactId="commons-logging"/>
 -          	<exclusion groupId="org.eclipse.jdt" artifactId="core"/>
 -		    <exclusion groupId="ant" artifactId="ant"/>
 -		    <exclusion groupId="junit" artifactId="junit"/>
++          <dependency groupId="org.apache.cassandra" artifactId="dtest-api" version="0.0.8" scope="test"/>
 +          <dependency groupId="org.reflections" artifactId="reflections" version="0.9.12" scope="test"/>
 +          <dependency groupId="org.apache.hadoop" artifactId="hadoop-core" version="1.0.3" scope="provided">
 +            <exclusion groupId="org.mortbay.jetty" artifactId="servlet-api"/>
 +            <exclusion groupId="commons-logging" artifactId="commons-logging"/>
 +            <exclusion groupId="org.eclipse.jdt" artifactId="core"/>
 +            <exclusion groupId="ant" artifactId="ant"/>
 +            <exclusion groupId="junit" artifactId="junit"/>
 +            <exclusion groupId="org.slf4j" artifactId="slf4j-api"/>
            </dependency>
 -          <dependency groupId="org.apache.hadoop" artifactId="hadoop-minicluster" version="1.0.3">
 -		    <exclusion groupId="asm" artifactId="asm"/> <!-- this is the outdated version 3.1 -->
 +          <dependency groupId="org.apache.hadoop" artifactId="hadoop-minicluster" version="1.0.3" scope="provided">
 +            <exclusion groupId="asm" artifactId="asm"/> <!-- this is the outdated version 3.1 -->
 +            <exclusion groupId="org.slf4j" artifactId="slf4j-api"/>
            </dependency>
 -          <dependency groupId="net.java.dev.jna" artifactId="jna" version="4.2.2"/>
 +          <dependency groupId="net.java.dev.jna" artifactId="jna" version="5.6.0"/>
  
 -          <dependency groupId="org.jacoco" artifactId="org.jacoco.agent" version="${jacoco.version}"/>
 -          <dependency groupId="org.jacoco" artifactId="org.jacoco.ant" version="${jacoco.version}"/>
 +          <dependency groupId="org.jacoco" artifactId="org.jacoco.agent" version="${jacoco.version}" scope="test"/>
 +          <dependency groupId="org.jacoco" artifactId="org.jacoco.ant" version="${jacoco.version}" scope="test"/>
  
 -          <dependency groupId="org.jboss.byteman" artifactId="byteman-install" version="${byteman.version}"/>
 -          <dependency groupId="org.jboss.byteman" artifactId="byteman" version="${byteman.version}"/>
 -          <dependency groupId="org.jboss.byteman" artifactId="byteman-submit" version="${byteman.version}"/>
 -          <dependency groupId="org.jboss.byteman" artifactId="byteman-bmunit" version="${byteman.version}"/>
 +          <dependency groupId="org.jboss.byteman" artifactId="byteman-install" version="${byteman.version}" scope="provided"/>
 +          <dependency groupId="org.jboss.byteman" artifactId="byteman" version="${byteman.version}" scope="provided"/>
 +          <dependency groupId="org.jboss.byteman" artifactId="byteman-submit" version="${byteman.version}" scope="provided"/>
 +          <dependency groupId="org.jboss.byteman" artifactId="byteman-bmunit" version="${byteman.version}" scope="provided"/>
  
            <dependency groupId="net.bytebuddy" artifactId="byte-buddy" version="${bytebuddy.version}" />
            <dependency groupId="net.bytebuddy" artifactId="byte-buddy-agent" version="${bytebuddy.version}" />
diff --cc test/distributed/org/apache/cassandra/distributed/impl/AbstractCluster.java
index 8026357,4de6fcd..240f080
--- a/test/distributed/org/apache/cassandra/distributed/impl/AbstractCluster.java
+++ b/test/distributed/org/apache/cassandra/distributed/impl/AbstractCluster.java
@@@ -215,8 -178,8 +215,8 @@@ public abstract class AbstractCluster<
              ClassLoader classLoader = new InstanceClassLoader(generation, config.num(), version.classpath, sharedClassLoader, SHARED_PREDICATE);
              if (instanceInitializer != null)
                  instanceInitializer.accept(classLoader, config.num());
 -            return Instance.transferAdhoc((SerializableBiFunction<IInstanceConfig, ClassLoader, IInvokableInstance>)Instance::new, classLoader)
 -                                        .apply(config, classLoader);
 +            return Instance.transferAdhoc((SerializableBiFunction<IInstanceConfig, ClassLoader, Instance>)Instance::new, classLoader)
-                                         .apply(config.forVersion(version.major), classLoader);
++                                        .apply(config.forVersion(version.version), classLoader);
          }
  
          public IInstanceConfig config()
diff --cc test/distributed/org/apache/cassandra/distributed/impl/InstanceConfig.java
index 895f2a7,b6b402b..1bbdd0b
--- a/test/distributed/org/apache/cassandra/distributed/impl/InstanceConfig.java
+++ b/test/distributed/org/apache/cassandra/distributed/impl/InstanceConfig.java
@@@ -284,15 -264,11 +282,15 @@@ public class InstanceConfig implements 
          return datadirs;
      }
  
-     public InstanceConfig forVersion(Versions.Major major)
+     public InstanceConfig forVersion(Semver version)
      {
-         switch (major)
-         {
-             case v4: return this;
-             default: return new InstanceConfig(this)
 -        return new InstanceConfig(this)
++        // Versions before 4.0 need to set 'seed_provider' without specifying the port
++        if (UpgradeTestBase.v40.compareTo(version) < 0)
++            return this;
++        else
++            return new InstanceConfig(this)
                              .set("seed_provider", new ParameterizedClass(SimpleSeedProvider.class.getName(),
                                                                           Collections.singletonMap("seeds", "127.0.0.1")));
-         }
      }
  
      public String toString()
diff --cc test/distributed/org/apache/cassandra/distributed/upgrade/CompactStorage3to4UpgradeTest.java
index 9d0f5af,0000000..08c4c99
mode 100644,000000..100644
--- a/test/distributed/org/apache/cassandra/distributed/upgrade/CompactStorage3to4UpgradeTest.java
+++ b/test/distributed/org/apache/cassandra/distributed/upgrade/CompactStorage3to4UpgradeTest.java
@@@ -1,61 -1,0 +1,61 @@@
 +/*
 + * 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.upgrade;
 +
 +import org.junit.Test;
 +
 +import org.apache.cassandra.distributed.shared.Versions;
 +
 +import static org.apache.cassandra.distributed.api.Feature.GOSSIP;
 +import static org.apache.cassandra.distributed.api.Feature.NATIVE_PROTOCOL;
 +import static org.apache.cassandra.distributed.api.Feature.NETWORK;
 +import static org.apache.cassandra.distributed.shared.AssertUtils.assertRows;
 +
 +public class CompactStorage3to4UpgradeTest extends UpgradeTestBase
 +{
 +    public static final String TABLE_NAME = "cs_tbl";
 +
 +    @Test
 +    public void testNullClusteringValues() throws Throwable
 +    {
 +        new TestCase().nodes(1)
-                       .upgrade(Versions.Major.v30, Versions.Major.v4)
++                      .upgradesFrom(v30)
 +                      .withConfig(config -> config.with(GOSSIP, NETWORK, NATIVE_PROTOCOL).set("enable_drop_compact_storage", true))
 +                      .setup(cluster -> {
 +                          String create = "CREATE TABLE %s.%s(k int, c1 int, c2 int, v int, PRIMARY KEY (k, c1, c2)) " +
 +                                          "WITH compaction = { 'class':'LeveledCompactionStrategy', 'enabled':'false'} AND COMPACT STORAGE";
 +                          cluster.schemaChange(String.format(create, KEYSPACE, TABLE_NAME));
 +
 +                          String insert = "INSERT INTO %s.%s(k, c1, v) values (?, ?, ?)";
 +                          cluster.get(1).executeInternal(String.format(insert, KEYSPACE, TABLE_NAME), 1, 1, 1);
 +                          cluster.get(1).flush(KEYSPACE);
 +
 +                          cluster.get(1).executeInternal(String.format(insert, KEYSPACE, TABLE_NAME), 2, 2, 2);
 +                          cluster.get(1).flush(KEYSPACE);
 +
 +                          cluster.schemaChange(String.format("ALTER TABLE %s.%s DROP COMPACT STORAGE", KEYSPACE, TABLE_NAME));
 +                      })
 +                      .runAfterNodeUpgrade((cluster, node) -> {
 +                          cluster.get(1).forceCompact(KEYSPACE, TABLE_NAME);
 +                          Object[][] actual = cluster.get(1).executeInternal(String.format("SELECT * FROM %s.%s", KEYSPACE, TABLE_NAME));
 +                          assertRows(actual, new Object[] {1, 1, null, 1}, new Object[] {2, 2, null, 2});
 +                      })
 +                      .run();
 +    }
 +}
diff --cc test/distributed/org/apache/cassandra/distributed/upgrade/CompactStorageUpgradeTest.java
index baa9dee,0000000..9e68934
mode 100644,000000..100644
--- a/test/distributed/org/apache/cassandra/distributed/upgrade/CompactStorageUpgradeTest.java
+++ b/test/distributed/org/apache/cassandra/distributed/upgrade/CompactStorageUpgradeTest.java
@@@ -1,161 -1,0 +1,161 @@@
 +/*
 + * 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.upgrade;
 +
 +import java.util.Iterator;
 +
 +import org.junit.Assert;
 +import org.junit.Test;
 +
 +import org.apache.cassandra.distributed.api.ConsistencyLevel;
 +import static org.apache.cassandra.distributed.api.Feature.GOSSIP;
 +import static org.apache.cassandra.distributed.api.Feature.NETWORK;
 +import org.apache.cassandra.distributed.shared.Versions;
 +import static org.apache.cassandra.distributed.shared.AssertUtils.*;
 +
 +public class CompactStorageUpgradeTest extends UpgradeTestBase
 +{
 +    @Test
 +    public void compactStorageColumnDeleteTest() throws Throwable
 +    {
 +        new TestCase()
 +        .nodes(2)
 +        .nodesToUpgrade(2)
-         .upgrade(Versions.Major.v30, Versions.Major.v4)
++        .upgradesFrom(v30)
 +        .setup((cluster) -> {
 +            cluster.schemaChange("CREATE TABLE " + KEYSPACE + ".tbl (pk int, ck int, v int, PRIMARY KEY (pk, ck)) WITH COMPACT STORAGE");
 +        })
 +        .runAfterNodeUpgrade((cluster, i) -> {
 +            for (int coord = 1; coord <= 2; coord++)
 +            {
 +                int v1 = coord * 10;
 +                int v2 = coord * 10;
 +
 +                cluster.coordinator(coord).execute("INSERT INTO " + KEYSPACE + ".tbl (pk, ck, v) VALUES (?, ?, ?)", ConsistencyLevel.ALL, v1, v1, v1);
 +                cluster.coordinator(coord).execute("DELETE v FROM " + KEYSPACE + ".tbl WHERE pk = ? AND ck = ?", ConsistencyLevel.ALL, v1, v1);
 +                assertRows(cluster.coordinator(coord).execute("SELECT * FROM " + KEYSPACE + ".tbl WHERE pk = ?",
 +                                                          ConsistencyLevel.ALL,
 +                                                          v1));
 +
 +                cluster.coordinator(coord).execute("INSERT INTO " + KEYSPACE + ".tbl (pk, ck, v) VALUES (?, ?, ?)", ConsistencyLevel.ALL, v2, v2, v2);
 +                assertRows(cluster.coordinator(coord).execute("SELECT * FROM " + KEYSPACE + ".tbl WHERE pk = ?",
 +                                                              ConsistencyLevel.ALL,
 +                                                              v2),
 +                           row(v2, v2, v2));
 +            }
 +        }).run();
 +    }
 +
 +    @Test
 +    public void compactStoragePagingTest() throws Throwable
 +    {
 +        new TestCase()
 +        .nodes(2)
 +        .nodesToUpgrade(2)
-         .upgrade(Versions.Major.v30, Versions.Major.v4)
++        .upgradesFrom(v30)
 +        .setup((cluster) -> {
 +            cluster.schemaChange("CREATE TABLE " + KEYSPACE + ".tbl (pk int, ck int, v int, PRIMARY KEY (pk, ck)) WITH COMPACT STORAGE");
 +            for (int i = 1; i < 10; i++)
 +                cluster.coordinator(1).execute("INSERT INTO " + KEYSPACE + ".tbl (pk, ck, v) VALUES (?, ?, ?)", ConsistencyLevel.ALL, 1, i, i);
 +        })
 +        .runAfterNodeUpgrade((cluster, i) -> {
 +            for (int coord = 1; coord <= 2; coord++)
 +            {
 +                Iterator<Object[]> iter = cluster.coordinator(coord).executeWithPaging("SELECT * FROM " + KEYSPACE + ".tbl WHERE pk = 1", ConsistencyLevel.ALL, 2);
 +                for (int j = 1; j < 10; j++)
 +                {
 +                    Assert.assertTrue(iter.hasNext());
 +                    Assert.assertArrayEquals(new Object[]{ 1, j, j }, iter.next());
 +                }
 +                Assert.assertFalse(iter.hasNext());
 +            }
 +        }).run();
 +    }
 +
 +    @Test
 +    public void compactStorageImplicitNullInClusteringTest() throws Throwable
 +    {
 +        new TestCase()
 +        .nodes(2)
 +        .nodesToUpgrade(2)
-         .upgrade(Versions.Major.v30, Versions.Major.v4)
++        .upgradesFrom(v30)
 +        .setup((cluster) -> {
 +            cluster.schemaChange("CREATE TABLE " + KEYSPACE + ".tbl (pk int, ck1 int, ck2 int, v int, PRIMARY KEY (pk, ck1, ck2)) WITH COMPACT STORAGE");
 +        })
 +        .runAfterClusterUpgrade((cluster) -> {
 +            cluster.coordinator(1).execute("INSERT INTO " + KEYSPACE + ".tbl (pk, ck1, v) VALUES (2, 2, 2)", ConsistencyLevel.ALL);
 +            assertRows(cluster.coordinator(1).execute("SELECT * FROM " + KEYSPACE + ".tbl WHERE pk = ?",
 +                                                      ConsistencyLevel.ALL,
 +                                                      2),
 +                       row(2, 2, null, 2));
 +        }).run();
 +    }
 +
 +    @Test
 +    public void compactStorageHiddenColumnTest() throws Throwable
 +    {
 +        new TestCase()
 +        .nodes(2)
 +        .nodesToUpgrade(2)
-         .upgrade(Versions.Major.v30, Versions.Major.v4)
++        .upgradesFrom(v30)
 +        .setup((cluster) -> {
 +            cluster.schemaChange("CREATE TABLE " + KEYSPACE + ".tbl (pk int, ck int, PRIMARY KEY (pk, ck)) WITH COMPACT STORAGE");
 +        })
 +        .runAfterNodeUpgrade((cluster, node) -> {
 +
 +            for (int coord = 1; coord <= 2; coord++)
 +            {
 +                int v1 = coord * 10;
 +                int v2 = coord * 10;
 +
 +                cluster.coordinator(coord).execute("INSERT INTO " + KEYSPACE + ".tbl (pk, ck) VALUES (?, ?)", ConsistencyLevel.ALL, v1, v1);
 +                cluster.coordinator(coord).execute("DELETE FROM " + KEYSPACE + ".tbl WHERE pk = ? AND ck = ?", ConsistencyLevel.ALL, v1, v1);
 +                assertRows(cluster.coordinator(coord).execute("SELECT * FROM " + KEYSPACE + ".tbl WHERE pk = ?",
 +                                                          ConsistencyLevel.ALL,
 +                                                          v1));
 +
 +                cluster.coordinator(coord).execute("INSERT INTO " + KEYSPACE + ".tbl (pk, ck) VALUES (?, ?)", ConsistencyLevel.ALL, v2, v2);
 +                assertRows(cluster.coordinator(coord).execute("SELECT * FROM " + KEYSPACE + ".tbl WHERE pk = ?",
 +                                                          ConsistencyLevel.ALL,
 +                                                          v2),
 +                           row(v2, v2));
 +            }
 +        }).run();
 +    }
 +
 +    @Test
 +    public void compactStorageUpgradeTest() throws Throwable
 +    {
 +        new TestCase()
 +        .nodes(2)
 +        .nodesToUpgrade(1, 2)
-         .upgrade(Versions.Major.v30, Versions.Major.v4)
++        .upgradesFrom(v30)
 +        .withConfig(config -> config.with(GOSSIP, NETWORK).set("enable_drop_compact_storage", true))
 +        .setup((cluster) -> {
 +            cluster.schemaChange("CREATE TABLE " + KEYSPACE + ".tbl (pk int, ck int, PRIMARY KEY (pk, ck)) WITH COMPACT STORAGE");
 +            cluster.coordinator(1).execute("INSERT INTO " + KEYSPACE + ".tbl (pk, ck) VALUES (1,1)", ConsistencyLevel.ALL);
 +        })
 +        .runAfterClusterUpgrade((cluster) -> {
 +            cluster.schemaChange("ALTER TABLE " + KEYSPACE + ".tbl DROP COMPACT STORAGE");
 +            assertRows(cluster.coordinator(1).execute("SELECT * FROM " + KEYSPACE + ".tbl WHERE pk = 1",
 +                                                      ConsistencyLevel.ALL),
 +                       row(1, 1, null));
 +        }).run();
 +    }
 +}
diff --cc test/distributed/org/apache/cassandra/distributed/upgrade/GroupByTest.java
index 520838d,0000000..2e67497
mode 100644,000000..100644
--- a/test/distributed/org/apache/cassandra/distributed/upgrade/GroupByTest.java
+++ b/test/distributed/org/apache/cassandra/distributed/upgrade/GroupByTest.java
@@@ -1,63 -1,0 +1,63 @@@
 +/*
 + * 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.upgrade;
 +
 +import org.junit.Test;
 +
 +import org.apache.cassandra.distributed.api.ConsistencyLevel;
 +import org.apache.cassandra.distributed.shared.Versions;
 +
 +import static org.apache.cassandra.distributed.api.Feature.GOSSIP;
 +import static org.apache.cassandra.distributed.api.Feature.NATIVE_PROTOCOL;
 +import static org.apache.cassandra.distributed.api.Feature.NETWORK;
 +import static org.apache.cassandra.distributed.shared.AssertUtils.assertRows;
 +import static org.apache.cassandra.distributed.shared.AssertUtils.row;
 +
 +public class GroupByTest extends UpgradeTestBase
 +{
 +    @Test
 +    public void testReads() throws Throwable
 +    {
 +        // CASSANDRA-16582: group-by across mixed version cluster would fail with ArrayIndexOutOfBoundException
 +        new UpgradeTestBase.TestCase()
 +        .nodes(2)
-         .upgrade(Versions.Major.v3X, Versions.Major.v4)
++        .upgradesFrom(v3X)
 +        .nodesToUpgrade(1)
 +        .withConfig(config -> config.with(GOSSIP, NETWORK))
 +        .setup(cluster -> {
 +            cluster.schemaChange(withKeyspace("CREATE TABLE %s.t (a int, b int, c int, v int, primary key (a, b, c))"));
 +            String insert = withKeyspace("INSERT INTO %s.t (a, b, c, v) VALUES (?, ?, ?, ?)");
 +            cluster.coordinator(1).execute(insert, ConsistencyLevel.ALL, 1, 1, 1, 3);
 +            cluster.coordinator(1).execute(insert, ConsistencyLevel.ALL, 1, 2, 1, 6);
 +            cluster.coordinator(1).execute(insert, ConsistencyLevel.ALL, 1, 2, 2, 12);
 +            cluster.coordinator(1).execute(insert, ConsistencyLevel.ALL, 1, 3, 2, 12);
 +        })
 +        .runAfterNodeUpgrade((cluster, node) -> {
 +            String query = withKeyspace("SELECT a, b, count(c) FROM %s.t GROUP BY a,b");
 +            Object[][] expectedResult = {
 +            row(1, 1, 1L),
 +            row(1, 2, 2L),
 +            row(1, 3, 1L)
 +            };
 +            assertRows(cluster.coordinator(1).execute(query, ConsistencyLevel.ALL), expectedResult);
 +            assertRows(cluster.coordinator(2).execute(query, ConsistencyLevel.ALL), expectedResult);
 +        })
 +        .run();
 +    }
 +}
diff --cc test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeAvailabilityTestBase.java
index 2ff2a9a,0000000..c1ae153
mode 100644,000000..100644
--- a/test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeAvailabilityTestBase.java
+++ b/test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeAvailabilityTestBase.java
@@@ -1,179 -1,0 +1,186 @@@
 +/*
 + * 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.upgrade;
 +
 +import java.util.Arrays;
 +import java.util.List;
 +import java.util.UUID;
 +
++import com.vdurmont.semver4j.Semver;
++
 +import org.apache.cassandra.distributed.api.ConsistencyLevel;
 +import org.apache.cassandra.distributed.api.ICoordinator;
- import org.apache.cassandra.distributed.shared.Versions;
 +import org.apache.cassandra.exceptions.ReadTimeoutException;
 +import org.apache.cassandra.exceptions.WriteTimeoutException;
 +import org.apache.cassandra.net.Verb;
 +
 +import static java.util.concurrent.TimeUnit.SECONDS;
 +import static org.apache.cassandra.distributed.api.ConsistencyLevel.ALL;
 +import static org.apache.cassandra.distributed.api.ConsistencyLevel.ONE;
 +import static org.apache.cassandra.distributed.api.ConsistencyLevel.QUORUM;
 +import static org.apache.cassandra.distributed.shared.AssertUtils.assertRows;
 +import static org.apache.cassandra.distributed.shared.AssertUtils.row;
 +import static org.apache.cassandra.net.Verb.READ_REQ;
 +import static org.junit.Assert.assertEquals;
 +import static org.junit.Assert.assertFalse;
 +import static java.lang.String.format;
 +
++
 +public class MixedModeAvailabilityTestBase extends UpgradeTestBase
 +{
 +    private static final int NUM_NODES = 3;
 +    private static final int COORDINATOR = 1;
 +    private static final List<Tester> TESTERS = Arrays.asList(new Tester(ONE, ALL),
 +                                                              new Tester(QUORUM, QUORUM),
 +                                                              new Tester(ALL, ONE));
 +
 +
-     protected static void testAvailability(Versions.Major initial, Versions.Major... upgrade) throws Throwable
++    protected static void testAvailability(Semver initial) throws Throwable
++    {
++        testAvailability(initial, UpgradeTestBase.CURRENT);
++    }
++
++    protected static void testAvailability(Semver initial, Semver upgrade) throws Throwable
 +    {
 +        testAvailability(true, initial, upgrade);
 +        testAvailability(false, initial, upgrade);
 +    }
 +
 +    private static void testAvailability(boolean upgradedCoordinator,
-                                          Versions.Major initial,
-                                          Versions.Major... upgrade) throws Throwable
++                                         Semver initial,
++                                         Semver upgrade) throws Throwable
 +    {
 +        new TestCase()
 +        .nodes(NUM_NODES)
 +        .nodesToUpgrade(upgradedCoordinator ? 1 : 2)
-         .upgrade(initial, upgrade)
++        .upgrades(initial, upgrade)
 +        .withConfig(config -> config.set("read_request_timeout_in_ms", SECONDS.toMillis(2))
 +                                    .set("write_request_timeout_in_ms", SECONDS.toMillis(2)))
 +        .setup(c -> c.schemaChange(withKeyspace("CREATE TABLE %s.t (k uuid, c int, v int, PRIMARY KEY (k, c))")))
 +        .runAfterNodeUpgrade((cluster, n) -> {
 +
 +            // using 0 to 2 down nodes...
 +            for (int numNodesDown = 0; numNodesDown < NUM_NODES; numNodesDown++)
 +            {
 +                // disable communications to the down nodes
 +                if (numNodesDown > 0)
 +                {
 +                    cluster.filters().outbound().verbs(READ_REQ.id).to(replica(COORDINATOR, numNodesDown)).drop();
 +                    cluster.filters().outbound().verbs(Verb.MUTATION_REQ.id).to(replica(COORDINATOR, numNodesDown)).drop();
 +                }
 +
 +                // run the test cases that are compatible with the number of down nodes
 +                ICoordinator coordinator = cluster.coordinator(COORDINATOR);
 +                for (Tester tester : TESTERS)
 +                    tester.test(coordinator, numNodesDown, upgradedCoordinator);
 +            }
 +        }).run();
 +    }
 +
 +    private static int replica(int node, int depth)
 +    {
 +        assert depth >= 0;
 +        return depth == 0 ? node : replica(node == NUM_NODES ? 1 : node + 1, depth - 1);
 +    }
 +
 +    private static class Tester
 +    {
 +        private static final String INSERT = withKeyspace("INSERT INTO %s.t (k, c, v) VALUES (?, ?, ?)");
 +        private static final String SELECT = withKeyspace("SELECT * FROM %s.t WHERE k = ?");
 +
 +        private final ConsistencyLevel writeConsistencyLevel;
 +        private final ConsistencyLevel readConsistencyLevel;
 +
 +        private Tester(ConsistencyLevel writeConsistencyLevel, ConsistencyLevel readConsistencyLevel)
 +        {
 +            this.writeConsistencyLevel = writeConsistencyLevel;
 +            this.readConsistencyLevel = readConsistencyLevel;
 +        }
 +
 +        public void test(ICoordinator coordinator, int numNodesDown, boolean upgradedCoordinator)
 +        {
 +            UUID key = UUID.randomUUID();
 +            Object[] row1 = row(key, 1, 10);
 +            Object[] row2 = row(key, 2, 20);
 +
 +            boolean wrote = false;
 +            try
 +            {
 +                // test write
 +                maybeFail(WriteTimeoutException.class, numNodesDown > maxNodesDown(writeConsistencyLevel), () -> {
 +                    coordinator.execute(INSERT, writeConsistencyLevel, row1);
 +                    coordinator.execute(INSERT, writeConsistencyLevel, row2);
 +                });
 +
 +                wrote = true;
 +
 +                // test read
 +                maybeFail(ReadTimeoutException.class, numNodesDown > maxNodesDown(readConsistencyLevel), () -> {
 +                    Object[][] rows = coordinator.execute(SELECT, readConsistencyLevel, key);
 +                    if (numNodesDown <= maxNodesDown(writeConsistencyLevel))
 +                        assertRows(rows, row1, row2);
 +                });
 +            }
 +            catch (Throwable t)
 +            {
 +                throw new AssertionError(format("Unexpected error while %s in case write-read consistency %s-%s with %s coordinator and %d nodes down",
 +                                                wrote ? "reading" : "writing",
 +                                                writeConsistencyLevel,
 +                                                readConsistencyLevel,
 +                                                upgradedCoordinator ? "upgraded" : "not upgraded",
 +                                                numNodesDown), t);
 +            }
 +        }
 +
 +        private static <E extends Exception> void maybeFail(Class<E> exceptionClass, boolean shouldFail, Runnable test)
 +        {
 +            try
 +            {
 +                test.run();
 +                assertFalse(shouldFail);
 +            }
 +            catch (Exception e)
 +            {
 +                // we should use exception class names due to the different classpaths
 +                String className = e.getClass().getCanonicalName();
 +                if (e instanceof RuntimeException && e.getCause() != null)
 +                    className = e.getCause().getClass().getCanonicalName();
 +
 +                if (shouldFail)
 +                    assertEquals(exceptionClass.getCanonicalName(), className);
 +                else
 +                    throw e;
 +            }
 +        }
 +
 +        private static int maxNodesDown(ConsistencyLevel cl)
 +        {
 +            if (cl == ONE)
 +                return 2;
 +
 +            if (cl == QUORUM)
 +                return 1;
 +
 +            if (cl == ALL)
 +                return 0;
 +
 +            throw new IllegalArgumentException("Unsupported consistency level: " + cl);
 +        }
 +    }
 +}
diff --cc test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeAvailabilityV22Test.java
index 367ef5f,0000000..f756574
mode 100644,000000..100644
--- a/test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeAvailabilityV22Test.java
+++ b/test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeAvailabilityV22Test.java
@@@ -1,41 -1,0 +1,41 @@@
 +/*
 + * 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.upgrade;
 +
 +import org.junit.Test;
 +
 +import org.apache.cassandra.distributed.shared.Versions;
 +
 +/**
 + * {@link MixedModeAvailabilityTestBase} for upgrades from v22.
 + */
 +public class MixedModeAvailabilityV22Test extends MixedModeAvailabilityTestBase
 +{
 +    @Test
 +    public void testAvailabilityV22ToV30() throws Throwable
 +    {
-         testAvailability(Versions.Major.v22, Versions.Major.v30);
++        testAvailability(v22, v30);
 +    }
 +
 +    @Test
 +    public void testAvailabilityV22ToV3X() throws Throwable
 +    {
-         testAvailability(Versions.Major.v22, Versions.Major.v3X);
++        testAvailability(v22, v3X);
 +    }
 +}
diff --cc test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeAvailabilityV30Test.java
index 4e25a64,0000000..984df3b
mode 100644,000000..100644
--- a/test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeAvailabilityV30Test.java
+++ b/test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeAvailabilityV30Test.java
@@@ -1,41 -1,0 +1,35 @@@
 +/*
 + * 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.upgrade;
 +
 +import org.junit.Test;
 +
 +import org.apache.cassandra.distributed.shared.Versions;
 +
 +/**
 + * {@link MixedModeAvailabilityTestBase} for upgrades from v30.
 + */
 +public class MixedModeAvailabilityV30Test extends MixedModeAvailabilityTestBase
 +{
 +    @Test
-     public void testAvailabilityV30ToV3X() throws Throwable
++    public void testAvailability() throws Throwable
 +    {
-         testAvailability(Versions.Major.v30, Versions.Major.v3X);
-     }
- 
-     @Test
-     public void testAvailabilityV30ToV4() throws Throwable
-     {
-         testAvailability(Versions.Major.v30, Versions.Major.v4);
++        testAvailability(v30);
 +    }
 +}
diff --cc test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeAvailabilityV3XTest.java
index 622d6c6,0000000..70230f5
mode 100644,000000..100644
--- a/test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeAvailabilityV3XTest.java
+++ b/test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeAvailabilityV3XTest.java
@@@ -1,35 -1,0 +1,35 @@@
 +/*
 + * 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.upgrade;
 +
 +import org.junit.Test;
 +
 +import org.apache.cassandra.distributed.shared.Versions;
 +
 +/**
 + * {@link MixedModeAvailabilityTestBase} for upgrades from v3X.
 + */
 +public class MixedModeAvailabilityV3XTest extends MixedModeAvailabilityTestBase
 +{
 +    @Test
-     public void testAvailabilityV3XToV4() throws Throwable
++    public void testAvailability() throws Throwable
 +    {
-         testAvailability(Versions.Major.v3X, Versions.Major.v4);
++        testAvailability(v3X);
 +    }
 +}
diff --cc test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeBatchTestBase.java
index f18647c,0000000..427eb61
mode 100644,000000..100644
--- a/test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeBatchTestBase.java
+++ b/test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeBatchTestBase.java
@@@ -1,157 -1,0 +1,163 @@@
 +/*
 + * 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.upgrade;
 +
 +import java.util.ArrayList;
 +import java.util.List;
 +
++import com.vdurmont.semver4j.Semver;
++
 +import org.apache.cassandra.distributed.UpgradeableCluster;
 +import org.apache.cassandra.distributed.api.ConsistencyLevel;
 +import org.apache.cassandra.distributed.api.IMessageFilters;
- import org.apache.cassandra.distributed.shared.Versions;
 +import org.apache.cassandra.exceptions.WriteFailureException;
 +import org.apache.cassandra.exceptions.WriteTimeoutException;
 +
 +import static org.apache.cassandra.distributed.shared.AssertUtils.assertRows;
 +import static org.apache.cassandra.distributed.shared.AssertUtils.row;
 +import static org.apache.cassandra.net.Verb.BATCH_STORE_REQ;
 +import static org.apache.cassandra.net.Verb.REQUEST_RSP;
 +
 +/**
 + * A base class for testing the replication of logged/unlogged batches on mixed-version clusters.
 + * 
 + * The tests based on this class partially replace the Python dtests in batch_test.py created for CASSANDRA-9673.
 + */
 +public class MixedModeBatchTestBase extends UpgradeTestBase
 +{
 +    private static final int KEYS_PER_BATCH = 10;
 +
-     protected void testSimpleStrategy(Versions.Major from, Versions.Major to, boolean isLogged) throws Throwable
++    protected void testSimpleStrategy(Semver from, boolean isLogged) throws Throwable
++    {
++        testSimpleStrategy(from, UpgradeTestBase.CURRENT, isLogged);
++    }
++
++    protected void testSimpleStrategy(Semver from, Semver to, boolean isLogged) throws Throwable
 +    {
 +        String insert = "INSERT INTO test_simple.names (key, name) VALUES (%d, '%s')";
 +        String select = "SELECT * FROM test_simple.names WHERE key = ?";
 +
 +        new TestCase()
 +        .nodes(3)
 +        .nodesToUpgrade(1, 2)
-         .upgrade(from, to)
++        .upgrades(from, to)
 +        .setup(cluster -> {
 +            cluster.schemaChange("CREATE KEYSPACE test_simple WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 2};");
 +            cluster.schemaChange("CREATE TABLE test_simple.names (key int PRIMARY KEY, name text)");
 +        })
 +        .runAfterNodeUpgrade((cluster, upgraded) -> {
 +            if (isLogged)
 +            {
 +                // If we're testing logged batches, exercise the case were batchlog writes fail.
 +                IMessageFilters.Filter dropBatchlogWrite = cluster.filters().inbound().verbs(BATCH_STORE_REQ.id, REQUEST_RSP.id).drop();
 +                dropBatchlogWrite.on();
 +                testBatches(true, true, insert, select, cluster, upgraded);
 +                cluster.filters().reset();
 +            }
 +
 +            cluster.coordinator(1).execute("TRUNCATE test_simple.names", ConsistencyLevel.ALL);
 +            testBatches(isLogged, false, insert, select, cluster, upgraded);
 +            cluster.coordinator(1).execute("TRUNCATE test_simple.names", ConsistencyLevel.ALL);
 +        })
 +        .run();
 +    }
 +
 +    private void testBatches(boolean isLogged, boolean failBatchlog, String insert, String select, UpgradeableCluster cluster, int upgraded)
 +    {
 +        List<Long> initialTokens = new ArrayList<>(cluster.size());
 +
 +        for (int i = 1; i <= cluster.size(); i++)
 +            initialTokens.add(Long.valueOf(cluster.get(i).config().get("initial_token").toString()));
 +
 +        // Exercise all the coordinators...
 +        for (int i = 1; i <= cluster.size(); i++)
 +        {
 +            StringBuilder batchBuilder = new StringBuilder("BEGIN " + (isLogged ? "" : "UNLOGGED ") + "BATCH\n");
 +            String name = "Julia";
 +            Runnable[] tests = new Runnable[KEYS_PER_BATCH];
 +
 +            // ...and sample enough keys that we cover the ring.
 +            for (int j = 0; j < KEYS_PER_BATCH; j++)
 +            {
 +                int key = j + (i * KEYS_PER_BATCH);
 +                batchBuilder.append(String.format(insert, key, name)).append('\n');
 +
 +                // Track the test that will later verify that this mutation was replicated properly.
 +                tests[j] = () -> {
 +                    Object[] row = row(key, name);
 +                    Long token = tokenFrom(key);
 +                    int node = primaryReplica(initialTokens, token);
 +                    
 +                    Object[][] primaryResult = cluster.get(node).executeInternal(select, key);
 +                    
 +                    // We shouldn't expect to see any results if the batchlog write failed.
 +                    if (failBatchlog)
 +                        assertRows(primaryResult);
 +                    else
 +                        assertRows(primaryResult, row);
 +
 +                    node = nextNode(node, cluster.size());
 +
 +                    Object[][] nextResult = cluster.get(node).executeInternal(select, key);
 +
 +                    if (failBatchlog)
 +                        assertRows(nextResult);
 +                    else
 +                        assertRows(nextResult, row);
 +
 +                    // At RF=2, this node should not have received the write.
 +                    node = nextNode(node, cluster.size());
 +                    assertRows(cluster.get(node).executeInternal(select, key));
 +                };
 +            }
 +
 +            String batch = batchBuilder.append("APPLY BATCH").toString();
 +            
 +            try
 +            {
 +                cluster.coordinator(i).execute(batch, ConsistencyLevel.ALL);
 +            }
 +            catch (Throwable t)
 +            {
 +                if (!failBatchlog || !exceptionMatches(t, WriteTimeoutException.class))
 +                {
 +                    // The standard write failure exception won't give us any context for what actually failed.
 +                    if (exceptionMatches(t, WriteFailureException.class))
 +                    {
 +                        String message = "Failed to write following batch to coordinator %d after upgrading node %d:\n%s";
 +                        throw new AssertionError(String.format(message, i, upgraded, batch), t);
 +                    }
 +
 +                    throw t;
 +                }
 +                
 +                // Failing the batchlog write will involve a timeout, so that's expected. Just continue...
 +            }
 +            
 +            for (Runnable test : tests)
 +                test.run();
 +        }
 +    }
 +
 +    private boolean exceptionMatches(Throwable t, Class<?> clazz)
 +    {
 +        return t.getClass().getSimpleName().equals(clazz.getSimpleName()) 
 +               || t.getCause() != null && t.getCause().getClass().getSimpleName().equals(clazz.getSimpleName());
 +    }
 +}
diff --cc test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeConsistencyTestBase.java
index ef54d61,0000000..f98fc8a
mode 100644,000000..100644
--- a/test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeConsistencyTestBase.java
+++ b/test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeConsistencyTestBase.java
@@@ -1,123 -1,0 +1,129 @@@
 +/*
 + * 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.upgrade;
 +
 +import java.util.ArrayList;
 +import java.util.List;
 +import java.util.UUID;
 +import java.util.stream.Collectors;
 +import java.util.stream.Stream;
 +
++import com.vdurmont.semver4j.Semver;
++
 +import org.apache.cassandra.distributed.UpgradeableCluster;
 +import org.apache.cassandra.distributed.api.ConsistencyLevel;
 +import org.apache.cassandra.distributed.api.IUpgradeableInstance;
- import org.apache.cassandra.distributed.shared.Versions;
 +
 +import static java.lang.String.format;
 +import static java.util.concurrent.TimeUnit.SECONDS;
 +import static org.apache.cassandra.distributed.api.ConsistencyLevel.ALL;
 +import static org.apache.cassandra.distributed.api.ConsistencyLevel.ONE;
 +import static org.apache.cassandra.distributed.api.ConsistencyLevel.QUORUM;
 +import static org.apache.cassandra.distributed.shared.AssertUtils.assertRows;
 +import static org.apache.cassandra.distributed.shared.AssertUtils.row;
 +
 +public class MixedModeConsistencyTestBase extends UpgradeTestBase
 +{
-     protected static void testConsistency(Versions.Major initial, Versions.Major... upgrade) throws Throwable
++    protected static void testConsistency(Semver initial) throws Throwable
++    {
++        testConsistency(initial, UpgradeTestBase.CURRENT);
++    }
++
++    protected static void testConsistency(Semver initial, Semver upgrade) throws Throwable
 +    {
 +        List<Tester> testers = new ArrayList<>();
 +        testers.addAll(Tester.create(1, ALL));
 +        testers.addAll(Tester.create(2, ALL, QUORUM));
 +        testers.addAll(Tester.create(3, ALL, QUORUM, ONE));
 +
 +        new TestCase()
 +        .nodes(3)
 +        .nodesToUpgrade(1)
-         .upgrade(initial, upgrade)
++        .upgrades(initial, upgrade)
 +        .withConfig(config -> config.set("read_request_timeout_in_ms", SECONDS.toMillis(30))
 +                                    .set("write_request_timeout_in_ms", SECONDS.toMillis(30)))
 +        .setup(cluster -> {
 +            Tester.createTable(cluster);
 +            for (Tester tester : testers)
 +                tester.writeRows(cluster);
 +        }).runAfterNodeUpgrade((cluster, node) -> {
 +            for (Tester tester : testers)
 +                tester.readRows(cluster);
 +        }).run();
 +    }
 +
 +    private static class Tester
 +    {
 +        private final int numWrittenReplicas;
 +        private final ConsistencyLevel readConsistencyLevel;
 +        private final UUID partitionKey;
 +
 +        private Tester(int numWrittenReplicas, ConsistencyLevel readConsistencyLevel)
 +        {
 +            this.numWrittenReplicas = numWrittenReplicas;
 +            this.readConsistencyLevel = readConsistencyLevel;
 +            partitionKey = UUID.randomUUID();
 +        }
 +
 +        private static List<Tester> create(int numWrittenReplicas, ConsistencyLevel... readConsistencyLevels)
 +        {
 +            return Stream.of(readConsistencyLevels)
 +                         .map(readConsistencyLevel -> new Tester(numWrittenReplicas, readConsistencyLevel))
 +                         .collect(Collectors.toList());
 +        }
 +
 +        private static void createTable(UpgradeableCluster cluster)
 +        {
 +            cluster.schemaChange(withKeyspace("CREATE TABLE %s.t (k uuid, c int, v int, PRIMARY KEY (k, c))"));
 +        }
 +
 +        private void writeRows(UpgradeableCluster cluster)
 +        {
 +            String query = withKeyspace("INSERT INTO %s.t (k, c, v) VALUES (?, ?, ?)");
 +            for (int i = 1; i <= numWrittenReplicas; i++)
 +            {
 +                IUpgradeableInstance node = cluster.get(i);
 +                node.executeInternal(query, partitionKey, 1, 10);
 +                node.executeInternal(query, partitionKey, 2, 20);
 +                node.executeInternal(query, partitionKey, 3, 30);
 +            }
 +        }
 +
 +        private void readRows(UpgradeableCluster cluster)
 +        {
 +            String query = withKeyspace("SELECT * FROM %s.t WHERE k = ?");
 +            int coordinator = 1;
 +            try
 +            {
 +                for (coordinator = 1; coordinator <= cluster.size(); coordinator++)
 +                {
 +                    assertRows(cluster.coordinator(coordinator).execute(query, readConsistencyLevel, partitionKey),
 +                               row(partitionKey, 1, 10),
 +                               row(partitionKey, 2, 20),
 +                               row(partitionKey, 3, 30));
 +                }
 +            }
 +            catch (Throwable t)
 +            {
 +                String format = "Unexpected error reading rows with %d written replicas, CL=%s and coordinator=%s";
 +                throw new AssertionError(format(format, numWrittenReplicas, readConsistencyLevel, coordinator), t);
 +            }
 +        }
 +    }
 +}
diff --cc test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeConsistencyV22Test.java
index ef9f766,0000000..deb0863
mode 100644,000000..100644
--- a/test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeConsistencyV22Test.java
+++ b/test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeConsistencyV22Test.java
@@@ -1,41 -1,0 +1,41 @@@
 +/*
 + * 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.upgrade;
 +
 +import org.junit.Test;
 +
 +import org.apache.cassandra.distributed.shared.Versions;
 +
 +/**
 + * {@link MixedModeConsistencyTestBase} for upgrades from v22.
 + */
 +public class MixedModeConsistencyV22Test extends MixedModeConsistencyTestBase
 +{
 +    @Test
 +    public void testConsistencyV22ToV30() throws Throwable
 +    {
-         testConsistency(Versions.Major.v22, Versions.Major.v30);
++        testConsistency(v22, v30);
 +    }
 +
 +    @Test
 +    public void testConsistencyV22ToV3X() throws Throwable
 +    {
-         testConsistency(Versions.Major.v22, Versions.Major.v3X);
++        testConsistency(v22, v3X);
 +    }
 +}
diff --cc test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeConsistencyV30Test.java
index efe94ba,0000000..8687c55
mode 100644,000000..100644
--- a/test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeConsistencyV30Test.java
+++ b/test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeConsistencyV30Test.java
@@@ -1,41 -1,0 +1,35 @@@
 +/*
 + * 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.upgrade;
 +
 +import org.junit.Test;
 +
 +import org.apache.cassandra.distributed.shared.Versions;
 +
 +/**
 + * {@link MixedModeConsistencyTestBase} for upgrades from v30.
 + */
 +public class MixedModeConsistencyV30Test extends MixedModeConsistencyTestBase
 +{
 +    @Test
-     public void testConsistencyV30ToV3X() throws Throwable
++    public void testConsistency() throws Throwable
 +    {
-         testConsistency(Versions.Major.v30, Versions.Major.v3X);
-     }
- 
-     @Test
-     public void testConsistencyV30ToV4() throws Throwable
-     {
-         testConsistency(Versions.Major.v30, Versions.Major.v4);
++        testConsistency(v30);
 +    }
 +}
diff --cc test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeConsistencyV3XTest.java
index 0405716,0000000..9e4ec6a
mode 100644,000000..100644
--- a/test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeConsistencyV3XTest.java
+++ b/test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeConsistencyV3XTest.java
@@@ -1,35 -1,0 +1,35 @@@
 +/*
 + * 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.upgrade;
 +
 +import org.junit.Test;
 +
 +import org.apache.cassandra.distributed.shared.Versions;
 +
 +/**
 + * {@link MixedModeConsistencyTestBase} for upgrades from v3X.
 + */
 +public class MixedModeConsistencyV3XTest extends MixedModeConsistencyTestBase
 +{
 +    @Test
-     public void testConsistencyV3XToV4() throws Throwable
++    public void testConsistency() throws Throwable
 +    {
-         testConsistency(Versions.Major.v3X, Versions.Major.v4);
++        testConsistency(v3X);
 +    }
 +}
diff --cc test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeFrom2LoggedBatchTest.java
index 575642c,0000000..3835521
mode 100644,000000..100644
--- a/test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeFrom2LoggedBatchTest.java
+++ b/test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeFrom2LoggedBatchTest.java
@@@ -1,38 -1,0 +1,38 @@@
 +/*
 + * 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.upgrade;
 +
 +import org.junit.Test;
 +
 +import org.apache.cassandra.distributed.shared.Versions;
 +
 +public class MixedModeFrom2LoggedBatchTest extends MixedModeBatchTestBase
 +{
 +    @Test
 +    public void testSimpleStrategy22to30() throws Throwable
 +    {
-         testSimpleStrategy(Versions.Major.v22, Versions.Major.v30, true);
++        testSimpleStrategy(v22, v30, true);
 +    }
 +
 +    @Test
 +    public void testSimpleStrategy22to3X() throws Throwable
 +    {
-         testSimpleStrategy(Versions.Major.v22, Versions.Major.v3X, true);
++        testSimpleStrategy(v22, v3X, true);
 +    }
 +}
diff --cc test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeFrom2ReplicationTest.java
index 38efbaf,0000000..ea56415
mode 100644,000000..100644
--- a/test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeFrom2ReplicationTest.java
+++ b/test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeFrom2ReplicationTest.java
@@@ -1,38 -1,0 +1,38 @@@
 +/*
 + * 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.upgrade;
 +
 +import org.junit.Test;
 +
 +import org.apache.cassandra.distributed.shared.Versions;
 +
 +public class MixedModeFrom2ReplicationTest extends MixedModeReplicationTestBase
 +{
 +    @Test
 +    public void testSimpleStrategy22to30() throws Throwable
 +    {
-         testSimpleStrategy(Versions.Major.v22, Versions.Major.v30);
++        testSimpleStrategy(v22, v30);
 +    }
 +
 +    @Test
 +    public void testSimpleStrategy22to3X() throws Throwable
 +    {
-         testSimpleStrategy(Versions.Major.v22, Versions.Major.v3X);
++        testSimpleStrategy(v22, v3X);
 +    }
 +}
diff --cc test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeFrom2UnloggedBatchTest.java
index f01958e,0000000..4f4b722
mode 100644,000000..100644
--- a/test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeFrom2UnloggedBatchTest.java
+++ b/test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeFrom2UnloggedBatchTest.java
@@@ -1,38 -1,0 +1,38 @@@
 +/*
 + * 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.upgrade;
 +
 +import org.junit.Test;
 +
 +import org.apache.cassandra.distributed.shared.Versions;
 +
 +public class MixedModeFrom2UnloggedBatchTest extends MixedModeBatchTestBase
 +{
 +    @Test
 +    public void testSimpleStrategy22to30() throws Throwable
 +    {
-         testSimpleStrategy(Versions.Major.v22, Versions.Major.v30, false);
++        testSimpleStrategy(v22, v30, false);
 +    }
 +
 +    @Test
 +    public void testSimpleStrategy22to3X() throws Throwable
 +    {
-         testSimpleStrategy(Versions.Major.v22, Versions.Major.v3X, false);
++        testSimpleStrategy(v22, v3X, false);
 +    }
 +}
diff --cc test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeFrom3LoggedBatchTest.java
index bb2008e,0000000..77eb058
mode 100644,000000..100644
--- a/test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeFrom3LoggedBatchTest.java
+++ b/test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeFrom3LoggedBatchTest.java
@@@ -1,44 -1,0 +1,38 @@@
 +/*
 + * 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.upgrade;
 +
 +import org.junit.Test;
 +
 +import org.apache.cassandra.distributed.shared.Versions;
 +
 +public class MixedModeFrom3LoggedBatchTest extends MixedModeBatchTestBase
 +{
 +    @Test
 +    public void testSimpleStrategy30to3X() throws Throwable
 +    {
-         testSimpleStrategy(Versions.Major.v30, Versions.Major.v3X, true);
++        testSimpleStrategy(v30, v3X, true);
 +    }
 +
 +    @Test
-     public void testSimpleStrategy30to4() throws Throwable
++    public void testSimpleStrategy() throws Throwable
 +    {
-         testSimpleStrategy(Versions.Major.v30, Versions.Major.v4, true);
-     }
- 
-     @Test
-     public void testSimpleStrategy3Xto4() throws Throwable
-     {
-         testSimpleStrategy(Versions.Major.v3X, Versions.Major.v4, true);
++        testSimpleStrategy(v30, true);
 +    }
 +}
diff --cc test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeFrom3ReplicationTest.java
index 8eae4b4,0000000..a38e25d
mode 100644,000000..100644
--- a/test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeFrom3ReplicationTest.java
+++ b/test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeFrom3ReplicationTest.java
@@@ -1,44 -1,0 +1,38 @@@
 +/*
 + * 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.upgrade;
 +
 +import org.junit.Test;
 +
 +import org.apache.cassandra.distributed.shared.Versions;
 +
 +public class MixedModeFrom3ReplicationTest extends MixedModeReplicationTestBase
 +{
 +    @Test
 +    public void testSimpleStrategy30to3X() throws Throwable
 +    {
-         testSimpleStrategy(Versions.Major.v30, Versions.Major.v3X);
++        testSimpleStrategy(v30, v3X);
 +    }
 +
 +    @Test
-     public void testSimpleStrategy30to4() throws Throwable
++    public void testSimpleStrategy() throws Throwable
 +    {
-         testSimpleStrategy(Versions.Major.v30, Versions.Major.v4);
-     }
- 
-     @Test
-     public void testSimpleStrategy3Xto4() throws Throwable
-     {
-         testSimpleStrategy(Versions.Major.v3X, Versions.Major.v4);
++        testSimpleStrategy(v30);
 +    }
 +}
diff --cc test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeFrom3UnloggedBatchTest.java
index b39177b,0000000..7256d2e
mode 100644,000000..100644
--- a/test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeFrom3UnloggedBatchTest.java
+++ b/test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeFrom3UnloggedBatchTest.java
@@@ -1,44 -1,0 +1,38 @@@
 +/*
 + * 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.upgrade;
 +
 +import org.junit.Test;
 +
 +import org.apache.cassandra.distributed.shared.Versions;
 +
 +public class MixedModeFrom3UnloggedBatchTest extends MixedModeBatchTestBase
 +{
 +    @Test
 +    public void testSimpleStrategy30to3X() throws Throwable
 +    {
-         testSimpleStrategy(Versions.Major.v30, Versions.Major.v3X, false);
++        testSimpleStrategy(v30, v3X, false);
 +    }
 +
 +    @Test
-     public void testSimpleStrategy30to4() throws Throwable
++    public void testSimpleStrategy() throws Throwable
 +    {
-         testSimpleStrategy(Versions.Major.v30, Versions.Major.v4, false);
-     }
- 
-     @Test
-     public void testSimpleStrategy3Xto4() throws Throwable
-     {
-         testSimpleStrategy(Versions.Major.v3X, Versions.Major.v4, false);
++        testSimpleStrategy(v30, false);
 +    }
 +}
diff --cc test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeGossipTest.java
index 83e911d,0000000..e706bda
mode 100644,000000..100644
--- a/test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeGossipTest.java
+++ b/test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeGossipTest.java
@@@ -1,168 -1,0 +1,170 @@@
 +/*
 + * 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.upgrade;
 +
 +import java.util.Arrays;
 +import java.util.HashSet;
 +import java.util.Set;
 +import java.util.concurrent.TimeUnit;
 +import java.util.concurrent.atomic.AtomicReference;
 +import java.util.function.BiConsumer;
 +import java.util.regex.Pattern;
 +import java.util.stream.Collectors;
 +
 +import com.google.common.util.concurrent.Uninterruptibles;
 +import org.junit.Test;
 +
 +import org.apache.cassandra.distributed.UpgradeableCluster;
 +import org.apache.cassandra.distributed.api.Feature;
 +import org.apache.cassandra.distributed.api.IMessageFilters;
 +import org.apache.cassandra.distributed.shared.Versions;
 +import org.apache.cassandra.net.Verb;
 +import org.assertj.core.api.Assertions;
 +
 +public class MixedModeGossipTest extends UpgradeTestBase
 +{
 +    Pattern expectedNormalStatus = Pattern.compile("STATUS:\\d+:NORMAL,-?\\d+");
 +    Pattern expectedNormalStatusWithPort = Pattern.compile("STATUS_WITH_PORT:\\d+:NORMAL,-?\\d+");
 +    Pattern expectedNormalX3 = Pattern.compile("X3:\\d+:NORMAL,-?\\d+");
 +
 +    @Test
 +    public void testStatusFieldShouldExistInOldVersionNodes() throws Throwable
 +    {
 +        new TestCase()
 +        .withConfig(c -> c.with(Feature.GOSSIP, Feature.NETWORK))
 +        .nodes(3)
 +        .nodesToUpgradeOrdered(1, 2, 3)
-         .upgrade(Versions.Major.v30, Versions.Major.v4)
-         .upgrade(Versions.Major.v3X, Versions.Major.v4)
++        // all upgrades from v30 up, excluding v30->v3X
++        .singleUpgrade(v30, v40)
++        .upgradesFrom(v3X)
 +        .setup(c -> {})
 +        .runAfterNodeUpgrade((cluster, node) -> {
 +            if (node == 1) {
 +                checkPeerGossipInfoShouldContainNormalStatus(cluster, 2);
 +                checkPeerGossipInfoShouldContainNormalStatus(cluster, 3);
 +            }
 +            if (node == 2) {
 +                checkPeerGossipInfoShouldContainNormalStatus(cluster, 3);
 +            }
 +        })
 +        .runAfterClusterUpgrade(cluster -> {
 +            // wait 1 minute for `org.apache.cassandra.gms.Gossiper.upgradeFromVersionSupplier` to update
 +            Uninterruptibles.sleepUninterruptibly(1, TimeUnit.MINUTES);
 +            checkPeerGossipInfoOfAllNodesShouldContainNewStatusAfterUpgrade(cluster);
 +        })
 +        .run();
 +    }
 +
 +    /**
 +     * Similar to {@link #testStatusFieldShouldExistInOldVersionNodes}, but in an edge case that
 +     * 1) node2 and node3 cannot gossip with each other.
 +     * 2) node2 sends SYN to node1 first when upgrading.
 +     * 3) node3 is at the lower version during the cluster upgrade
 +     * In this case, node3 gossip info does not contain STATUS field for node2
 +     */
 +    @Test
 +    public void testStatusFieldShouldExistInOldVersionNodesEdgeCase() throws Throwable
 +    {
 +        AtomicReference<IMessageFilters.Filter> n1GossipSynBlocker = new AtomicReference<>();
 +        new TestCase()
 +        .withConfig(c -> c.with(Feature.GOSSIP, Feature.NETWORK))
 +        .nodes(3)
 +        .nodesToUpgradeOrdered(1, 2, 3)
-         .upgrade(Versions.Major.v30, Versions.Major.v4)
-         .upgrade(Versions.Major.v3X, Versions.Major.v4)
++        // all upgrades from v30 up, excluding v30->v3X
++        .singleUpgrade(v30, v40)
++        .upgradesFrom(v3X)
 +        .setup(cluster -> {
 +            // node2 and node3 gossiper cannot talk with each other
 +            cluster.filters().verbs(Verb.GOSSIP_DIGEST_SYN.id).from(2).to(3).drop();
 +            cluster.filters().verbs(Verb.GOSSIP_DIGEST_SYN.id).from(3).to(2).drop();
 +        })
 +        .runAfterNodeUpgrade((cluster, node) -> {
 +            // let node2 sends the SYN to node1 first
 +            if (node == 1)
 +            {
 +                IMessageFilters.Filter filter = cluster.filters().verbs(Verb.GOSSIP_DIGEST_SYN.id).from(1).to(2).drop();
 +                n1GossipSynBlocker.set(filter);
 +            }
 +            else if (node == 2)
 +            {
 +                n1GossipSynBlocker.get().off();
 +                String node3GossipView = cluster.get(3).nodetoolResult("gossipinfo").getStdout();
 +                String node2GossipState = getGossipStateOfNode(node3GossipView, "/127.0.0.2");
 +                Assertions.assertThat(node2GossipState)
 +                          .as("The node2's gossip state from node3's perspective should contain status. " +
 +                              "And it should carry an unrecognized field X3 with NORMAL.")
 +                          .containsPattern(expectedNormalStatus)
 +                          .containsPattern(expectedNormalX3);
 +            }
 +        })
 +        .runAfterClusterUpgrade(cluster -> {
 +            // wait 1 minute for `org.apache.cassandra.gms.Gossiper.upgradeFromVersionSupplier` to update
 +            Uninterruptibles.sleepUninterruptibly(1, TimeUnit.MINUTES);
 +            checkPeerGossipInfoOfAllNodesShouldContainNewStatusAfterUpgrade(cluster);
 +        })
 +        .run();
 +    }
 +
 +    private void checkPeerGossipInfoOfAllNodesShouldContainNewStatusAfterUpgrade(UpgradeableCluster cluster)
 +    {
 +        for (int i = 1; i <= 3; i++)
 +        {
 +            int n = i;
 +            checkPeerGossipInfo(cluster, i, (gossipInfo, peers) -> {
 +                for (String p : peers)
 +                {
 +                    Assertions.assertThat(getGossipStateOfNode(gossipInfo, p))
 +                              .as(String.format("%s gossip state in node%s should not contain STATUS " +
 +                                                "and should contain STATUS_WITH_PORT.", p, n))
 +                              .doesNotContain("STATUS:")
 +                              .containsPattern(expectedNormalStatusWithPort);
 +                }
 +            });
 +        }
 +    }
 +
 +    private void checkPeerGossipInfoShouldContainNormalStatus(UpgradeableCluster cluster, int node)
 +    {
 +        checkPeerGossipInfo(cluster, node, (gossipInfo, peers) -> {
 +            for (String n : peers)
 +            {
 +                Assertions.assertThat(getGossipStateOfNode(gossipInfo, n))
 +                          .containsPattern(expectedNormalStatus);
 +            }
 +        });
 +    }
 +
 +    private void checkPeerGossipInfo(UpgradeableCluster cluster, int node, BiConsumer<String, Set<String>> verifier)
 +    {
 +        Set<Integer> peers = new HashSet<>(Arrays.asList(1, 2, 3));
 +        peers.remove(node);
 +        String gossipInfo = cluster.get(node).nodetoolResult("gossipinfo").getStdout();
 +        verifier.accept(gossipInfo, peers.stream().map(i -> "127.0.0." + i).collect(Collectors.toSet()));
 +    }
 +
 +    private String getGossipStateOfNode(String rawOutput, String nodeInterested)
 +    {
 +        String temp = rawOutput.substring(rawOutput.indexOf(nodeInterested));
 +        int nextSlashIndex = temp.indexOf('/', 1);
 +        if (nextSlashIndex != -1)
 +            return temp.substring(0, nextSlashIndex);
 +        else
 +            return temp;
 +    }
 +}
diff --cc test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeReadTest.java
index f1883b0,756f894..c95aede
--- a/test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeReadTest.java
+++ b/test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeReadTest.java
@@@ -36,11 -35,10 +36,12 @@@ public class MixedModeReadTest extends 
      public void mixedModeReadColumnSubsetDigestCheck() throws Throwable
      {
          new TestCase()
 +        .withConfig(c -> c.with(Feature.GOSSIP, Feature.NETWORK))
          .nodes(2)
          .nodesToUpgrade(1)
-         .upgrade(Versions.Major.v30, Versions.Major.v4)
-         .upgrade(Versions.Major.v3X, Versions.Major.v4)
 -        .singleUpgrade(v30, v3X)
 -        .withConfig(config -> config.with(Feature.GOSSIP, Feature.NETWORK))
++        // all upgrades from v30 up, excluding v30->v3X
++        .singleUpgrade(v30, v40)
++        .upgradesFrom(v3X)
          .setup(cluster -> {
              cluster.schemaChange(CREATE_TABLE);
              insertData(cluster.coordinator(1));
diff --cc test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeRepairTest.java
index 39f6c95,0000000..adcfd1f
mode 100644,000000..100644
--- a/test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeRepairTest.java
+++ b/test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeRepairTest.java
@@@ -1,126 -1,0 +1,124 @@@
 +/*
 + * 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.upgrade;
 +
 +import java.util.UUID;
 +import java.util.concurrent.CompletableFuture;
 +import java.util.concurrent.RejectedExecutionException;
 +import java.util.concurrent.TimeUnit;
 +import java.util.concurrent.TimeoutException;
 +
 +import org.junit.Assert;
 +import org.junit.Test;
 +
 +import org.apache.cassandra.distributed.UpgradeableCluster;
 +import org.apache.cassandra.distributed.api.IUpgradeableInstance;
 +
 +import static org.apache.cassandra.distributed.api.Feature.GOSSIP;
 +import static org.apache.cassandra.distributed.api.Feature.NETWORK;
 +import static org.apache.cassandra.distributed.shared.AssertUtils.assertRows;
 +import static org.apache.cassandra.distributed.shared.AssertUtils.fail;
 +import static org.apache.cassandra.distributed.shared.AssertUtils.row;
- import static org.apache.cassandra.distributed.shared.Versions.Major;
 +
 +public class MixedModeRepairTest extends UpgradeTestBase
 +{
 +    public static final int UPGRADED_NODE = 1;
 +    public static final String CREATE_TABLE = withKeyspace("CREATE TABLE %s.t (k uuid, c int, v int, PRIMARY KEY (k, c))");
 +    public static final String INSERT = withKeyspace("INSERT INTO %s.t (k, c, v) VALUES (?, ?, ?)");
 +    public static final String SELECT = withKeyspace("SELECT * FROM %s.t WHERE k=?");
 +
 +    /**
 +     * Test that repairs fail during a major upgrade. If the repaired node is >= 4.0 thanks to CASSANDRA-13944 there
 +     * will be an informative message. Otherwise, if the repaired node is below 4.0, there won't be such an informative
 +     * message and the repair will take very long to timeout.
 +     */
 +    @Test
 +    public void testRepairDuringMajorUpgrade() throws Throwable
 +    {
 +        new UpgradeTestBase.TestCase()
 +        .nodes(2)
 +        .nodesToUpgrade(UPGRADED_NODE)
-         .upgrade(Major.v30, Major.v4)
-         .upgrade(Major.v3X, Major.v4)
++        .upgradesFrom(v3X)
 +        .withConfig(config -> config.with(NETWORK, GOSSIP))
 +        .setup(cluster -> {
 +            cluster.schemaChange(CREATE_TABLE);
 +            cluster.setUncaughtExceptionsFilter(throwable -> throwable instanceof RejectedExecutionException);
 +        })
 +        .runAfterNodeUpgrade((cluster, node) -> {
 +
 +            // run the repair scenario in both the upgraded and the not upgraded node
 +            for (int repairedNode = 1; repairedNode <= cluster.size(); repairedNode++)
 +            {
 +                UUID key = UUID.randomUUID();
 +
 +                // only in node 1, create a sstable with a version of a partition
 +                Object[] row1 = row(key, 10, 100);
 +                cluster.get(1).executeInternal(INSERT, row1);
 +                cluster.get(1).flush(KEYSPACE);
 +
 +                // only in node 2, create a sstable with another version of the partition
 +                Object[] row2 = row(key, 20, 200);
 +                cluster.get(2).executeInternal(INSERT, row2);
 +                cluster.get(2).flush(KEYSPACE);
 +
 +                // in case of repairing the upgraded node the repair should be rejected with a decriptive error in both
 +                // nodetool output and logs (see CASSANDRA-13944)
 +                if (repairedNode == UPGRADED_NODE)
 +                {
 +                    String errorMessage = "Repair is not supported in mixed major version clusters";
 +                    cluster.get(repairedNode)
 +                           .nodetoolResult("repair", "--full", KEYSPACE)
 +                           .asserts()
 +                           .errorContains(errorMessage);
 +                    assertLogHas(cluster, repairedNode, errorMessage);
 +                }
 +                // if the node issuing the repair is the not updated node we don't have specific error management,
 +                // so the repair will produce a failure in the upgraded node, and it will take one hour to time out in
 +                // the not upgraded node. Since we don't want to wait that long, we only wait a few seconds for the
 +                // repair before verifying the "unknown verb id" error in the upgraded node.
 +                else
 +                {
 +                    try
 +                    {
 +                        IUpgradeableInstance instance = cluster.get(repairedNode);
 +                        CompletableFuture.supplyAsync(() -> instance.nodetoolResult("repair", "--full", KEYSPACE))
 +                                         .get(10, TimeUnit.SECONDS);
 +                        fail("Repair in the not upgraded node should have timed out");
 +                    }
 +                    catch (TimeoutException e)
 +                    {
 +                        assertLogHas(cluster, UPGRADED_NODE, "unexpected exception caught while processing inbound messages");
 +                        assertLogHas(cluster, UPGRADED_NODE, "java.lang.IllegalArgumentException: Unknown verb id");
 +                    }
 +                }
 +
 +                // verify that the previous failed repair hasn't repaired the data
 +                assertRows(cluster.get(1).executeInternal(SELECT, key), row1);
 +                assertRows(cluster.get(2).executeInternal(SELECT, key), row2);
 +            }
 +        })
 +        .run();
 +    }
 +
 +    private static void assertLogHas(UpgradeableCluster cluster, int node, String msg)
 +    {
 +        Assert.assertFalse("Unable to find '" + msg + "' in the logs of node " + node,
 +                           cluster.get(node).logs().grep(msg).getResult().isEmpty());
 +    }
 +}
diff --cc test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeReplicationTestBase.java
index 153a8a5,0000000..3f2da7a
mode 100644,000000..100644
--- a/test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeReplicationTestBase.java
+++ b/test/distributed/org/apache/cassandra/distributed/upgrade/MixedModeReplicationTestBase.java
@@@ -1,83 -1,0 +1,89 @@@
 +/*
 + * 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.upgrade;
 +
 +import java.util.ArrayList;
 +import java.util.List;
 +
++import com.vdurmont.semver4j.Semver;
++
 +import org.apache.cassandra.distributed.api.ConsistencyLevel;
- import org.apache.cassandra.distributed.shared.Versions;
 +
 +import static org.apache.cassandra.distributed.shared.AssertUtils.assertRows;
 +import static org.apache.cassandra.distributed.shared.AssertUtils.row;
 +
 +/**
 + * A base class for testing basic replication on mixed-version clusters.
 + */
 +public class MixedModeReplicationTestBase extends UpgradeTestBase
 +{
-     protected void testSimpleStrategy(Versions.Major from, Versions.Major to) throws Throwable
++    protected void testSimpleStrategy(Semver from) throws Throwable
++    {
++        testSimpleStrategy(from, UpgradeTestBase.CURRENT);
++    }
++
++    protected void testSimpleStrategy(Semver from, Semver to) throws Throwable
 +    {
 +        String insert = "INSERT INTO test_simple.names (key, name) VALUES (?, ?)";
 +        String select = "SELECT * FROM test_simple.names WHERE key = ?";
 +
 +        new TestCase()
 +        .nodes(3)
 +        .nodesToUpgrade(1, 2)
-         .upgrade(from, to)
++        .upgrades(from, to)
 +        .setup(cluster -> {
 +            cluster.schemaChange("CREATE KEYSPACE test_simple WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 2};");
 +            cluster.schemaChange("CREATE TABLE test_simple.names (key int PRIMARY KEY, name text)");
 +        })
 +        .runAfterNodeUpgrade((cluster, upgraded) -> {
 +            List<Long> initialTokens = new ArrayList<>(cluster.size() + 1);
 +            initialTokens.add(null); // The first valid token is at 1 to avoid offset math below.
 +
 +            for (int i = 1; i <= cluster.size(); i++)
 +                initialTokens.add(Long.valueOf(cluster.get(i).config().get("initial_token").toString()));
 +
 +            List<Long> validTokens = initialTokens.subList(1, cluster.size() + 1);
 +
 +            // Exercise all the coordinators...
 +            for (int i = 1; i <= cluster.size(); i++)
 +            {
 +                // ...and sample enough keys that we cover the ring.
 +                for (int j = 0; j < 10; j++)
 +                {
 +                    int key = j + (i * 10);
 +                    Object[] row = row(key, "Nero");
 +                    Long token = tokenFrom(key);
 +
 +                    cluster.coordinator(i).execute(insert, ConsistencyLevel.ALL, row);
 +
 +                    int node = primaryReplica(validTokens, token);
 +                    assertRows(cluster.get(node).executeInternal(select, key), row);
 +
 +                    node = nextNode(node, cluster.size());
 +                    assertRows(cluster.get(node).executeInternal(select, key), row);
 +
 +                    // At RF=2, this node should not have received the write.
 +                    node = nextNode(node, cluster.size());
 +                    assertRows(cluster.get(node).executeInternal(select, key));
 +                }
 +            }
 +        })
 +        .run();
 +    }
 +}
diff --cc test/distributed/org/apache/cassandra/distributed/upgrade/Pre40MessageFilterTest.java
index 628e8fc,0000000..3e6c9ca
mode 100644,000000..100644
--- a/test/distributed/org/apache/cassandra/distributed/upgrade/Pre40MessageFilterTest.java
+++ b/test/distributed/org/apache/cassandra/distributed/upgrade/Pre40MessageFilterTest.java
@@@ -1,64 -1,0 +1,65 @@@
 +/*
 + * 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.upgrade;
 +
 +import org.apache.cassandra.distributed.api.Feature;
 +import org.apache.cassandra.distributed.api.IInstanceConfig;
 +import org.junit.Test;
 +
 +import org.apache.cassandra.distributed.api.ConsistencyLevel;
- import org.apache.cassandra.distributed.shared.Versions;
 +
 +import java.util.function.Consumer;
 +
 +public class Pre40MessageFilterTest extends UpgradeTestBase
 +{
 +    public void reserializePre40RequestPaxosTest(Consumer<IInstanceConfig> configConsumer) throws Throwable
 +    {
 +        new UpgradeTestBase.TestCase()
 +        .nodes(2)
 +        .withConfig(configConsumer)
 +        .nodesToUpgrade(1)
-         .upgrade(Versions.Major.v30, Versions.Major.v4)
++        // all upgrades from v30 up, excluding v30->v3X
++        .singleUpgrade(v30, v40)
++        .upgradesFrom(v3X)
 +        .setup((cluster) -> {
 +            cluster.filters().outbound().allVerbs().messagesMatching((f,t,m) -> false).drop();
 +            cluster.schemaChange("CREATE TABLE " + KEYSPACE + ".tbl (pk int, ck int, v int, PRIMARY KEY (pk, ck))");
 +            cluster.coordinator(1).execute("INSERT INTO " + KEYSPACE + ".tbl(pk,ck,v) VALUES (1, 1, 1) IF NOT EXISTS",
 +                                           ConsistencyLevel.QUORUM,
 +                                           1);
 +        })
 +        .runAfterNodeUpgrade((cluster, node) -> {
 +            cluster.coordinator(node).execute("UPDATE " + KEYSPACE + ".tbl SET v = ? WHERE pk = ? AND ck = ?  IF v = ?",
 +                                              ConsistencyLevel.QUORUM,
 +                                              2, 1, 1, 1);
 +        }).run();
 +    }
 +
 +    @Test
 +    public void reserializePre40RequestPaxosWithoutNetworkTest() throws Throwable
 +    {
 +        reserializePre40RequestPaxosTest(config -> {});
 +    }
 +
 +    @Test
 +    public void reserializePre40RequestPaxosWithNetworkTest() throws Throwable
 +    {
 +        reserializePre40RequestPaxosTest(config -> config.with(Feature.NETWORK, Feature.GOSSIP));
 +    }
 +}
diff --cc test/distributed/org/apache/cassandra/distributed/upgrade/ReadRepairCompactStorageUpgradeTest.java
index 2d19e7a,0000000..5567d40
mode 100644,000000..100644
--- a/test/distributed/org/apache/cassandra/distributed/upgrade/ReadRepairCompactStorageUpgradeTest.java
+++ b/test/distributed/org/apache/cassandra/distributed/upgrade/ReadRepairCompactStorageUpgradeTest.java
@@@ -1,59 -1,0 +1,57 @@@
 +/*
 + * 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.upgrade;
 +
 +import org.junit.Test;
 +
 +import org.apache.cassandra.distributed.api.ConsistencyLevel;
 +import org.apache.cassandra.distributed.shared.Versions;
 +
 +public class ReadRepairCompactStorageUpgradeTest extends UpgradeTestBase
 +{
 +    /**
 +     * Tests {@code COMPACT STORAGE} behaviour with mixed replica versions.
 +     * <p>
 +     * See CASSANDRA-15363 for further details.
 +     */
 +    @Test
 +    public void mixedModeReadRepairCompactStorage() throws Throwable
 +    {
 +        new TestCase()
 +        .nodes(2)
-         .upgrade(Versions.Major.v22, Versions.Major.v30)
-         .upgrade(Versions.Major.v22, Versions.Major.v3X)
-         .upgrade(Versions.Major.v30, Versions.Major.v3X)
++        .upgrades(v22, v3X)
 +        .setup((cluster) -> cluster.schemaChange(withKeyspace("CREATE TABLE %s.tbl" +
 +                                                              " (pk ascii, b boolean, v blob, PRIMARY KEY (pk))" +
 +                                                              " WITH COMPACT STORAGE")))
 +        .runAfterNodeUpgrade((cluster, node) -> {
 +            if (node != 1)
 +                return;
 +            // now node1 is 3.0/3.x and node2 is 2.2
 +            // make sure 2.2 side does not get the mutation
 +            cluster.get(1).executeInternal(withKeyspace("DELETE FROM %s.tbl WHERE pk = ?"), "something");
 +            // trigger a read repair
 +            cluster.coordinator(2).execute(withKeyspace("SELECT * FROM %s.tbl WHERE pk = ?"),
 +                                           ConsistencyLevel.ALL,
 +                                           "something");
 +            cluster.get(2).flush(KEYSPACE);
 +        })
 +        .runAfterClusterUpgrade((cluster) -> cluster.get(2).forceCompact(KEYSPACE, "tbl"))
 +        .run();
 +    }
 +}
diff --cc test/distributed/org/apache/cassandra/distributed/upgrade/UpgradeTest.java
index 01ac40d,0932eb1..691d8af
--- a/test/distributed/org/apache/cassandra/distributed/upgrade/UpgradeTest.java
+++ b/test/distributed/org/apache/cassandra/distributed/upgrade/UpgradeTest.java
@@@ -27,25 -32,58 +27,25 @@@ import static org.apache.cassandra.dist
  
  public class UpgradeTest extends UpgradeTestBase
  {
 -
      @Test
 -    public void upgradeTest() throws Throwable
 +    public void simpleUpgradeWithNetworkAndGossipTest() throws Throwable
      {
          new TestCase()
 -        .upgradesFrom(v22)
 -        .setup((cluster) -> {
 -            cluster.schemaChange("CREATE TABLE " + KEYSPACE + ".tbl (pk int, ck int, v int, PRIMARY KEY (pk, ck))");
 -
 -            cluster.get(1).executeInternal("INSERT INTO " + KEYSPACE + ".tbl (pk, ck, v) VALUES (1, 1, 1)");
 -            cluster.get(2).executeInternal("INSERT INTO " + KEYSPACE + ".tbl (pk, ck, v) VALUES (1, 2, 2)");
 -            cluster.get(3).executeInternal("INSERT INTO " + KEYSPACE + ".tbl (pk, ck, v) VALUES (1, 3, 3)");
 -        })
 -        .runAfterClusterUpgrade((cluster) -> {
 -            assertRows(cluster.coordinator(1).execute("SELECT * FROM " + KEYSPACE + ".tbl WHERE pk = ?",
 -                                                                          ConsistencyLevel.ALL,
 -                                                                          1),
 -                                           row(1, 1, 1),
 -                                           row(1, 2, 2),
 -                                           row(1, 3, 3));
 -        }).run();
 -    }
 -
 -    @Test
 -    public void mixedModePagingTest() throws Throwable
 -    {
 -        new TestCase()
 -        .singleUpgrade(v22, v30)
          .nodes(2)
 -        .nodesToUpgrade(2)
 +        .nodesToUpgrade(1)
 +        .withConfig((cfg) -> cfg.with(Feature.NETWORK, Feature.GOSSIP))
-         .upgrade(Versions.Major.v3X, Versions.Major.v4)
++        .upgradesFrom(v3X)
          .setup((cluster) -> {
 -            cluster.schemaChange("ALTER KEYSPACE " + KEYSPACE + " WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}");
 -            cluster.schemaChange("CREATE TABLE " + KEYSPACE + ".tbl (pk int, ck int, v int, PRIMARY KEY (pk, ck)) with compact storage");
 -            for (int i = 0; i < 100; i++)
 -                for (int j = 0; j < 200; j++)
 -                    cluster.coordinator(2).execute("INSERT INTO " + KEYSPACE + ".tbl (pk, ck, v) VALUES (?, ?, 1)", ConsistencyLevel.ALL, i, j);
 -            cluster.forEach((i) -> i.flush(KEYSPACE));
 -            for (int i = 0; i < 100; i++)
 -                for (int j = 10; j < 30; j++)
 -                    cluster.coordinator(2).execute("DELETE FROM " + KEYSPACE + ".tbl where pk=? and ck=?", ConsistencyLevel.ALL, i, j);
 -            cluster.forEach((i) -> i.flush(KEYSPACE));
 +            cluster.schemaChange("CREATE TABLE " + KEYSPACE + ".tbl (pk int, ck int, v int, PRIMARY KEY (pk, ck))");
 +            cluster.coordinator(1).execute("INSERT INTO " + KEYSPACE + ".tbl (pk, ck, v) VALUES (1, 1, 1)", ConsistencyLevel.ALL);
          })
 -        .runAfterClusterUpgrade((cluster) -> {
 -            for (int i = 0; i < 100; i++)
 +        .runAfterNodeUpgrade((cluster, node) -> {
 +            for (int i : new int[]{ 1, 2 })
              {
 -                for (int pageSize = 10; pageSize < 100; pageSize++)
 -                {
 -                    Iterator<Object[]> res = cluster.coordinator(1).executeWithPaging("SELECT * FROM " + KEYSPACE + ".tbl WHERE pk = ?",
 -                                                                                      ConsistencyLevel.ALL,
 -                                                                                      pageSize, i);
 -                    Assert.assertEquals(180, Iterators.size(res));
 -                }
 +                assertRows(cluster.coordinator(i).execute("SELECT * FROM " + KEYSPACE + ".tbl WHERE pk = ?",
 +                                                          ConsistencyLevel.ALL,
 +                                                          1),
 +                           row(1, 1, 1));
              }
          }).run();
      }
diff --cc test/distributed/org/apache/cassandra/distributed/upgrade/UpgradeTestBase.java
index 90254d1,6aa6f61..c2afc2d
--- a/test/distributed/org/apache/cassandra/distributed/upgrade/UpgradeTestBase.java
+++ b/test/distributed/org/apache/cassandra/distributed/upgrade/UpgradeTestBase.java
@@@ -19,14 -19,16 +19,16 @@@
  package org.apache.cassandra.distributed.upgrade;
  
  import java.util.ArrayList;
- import java.util.Arrays;
  import java.util.HashSet;
 +import java.util.LinkedHashSet;
  import java.util.List;
  import java.util.Set;
  import java.util.function.Consumer;
  
- import com.google.common.base.Preconditions;
+ import com.google.common.collect.ImmutableList;
+ import com.vdurmont.semver4j.Semver;
+ import com.vdurmont.semver4j.Semver.SemverType;
+ 
 -import com.google.common.collect.ImmutableList;
  import org.junit.After;
  import org.junit.BeforeClass;
  
@@@ -39,12 -40,12 +41,13 @@@ import org.apache.cassandra.distributed
  import org.apache.cassandra.distributed.shared.DistributedTestBase;
  import org.apache.cassandra.distributed.shared.Versions;
  import org.apache.cassandra.utils.ByteBufferUtil;
+ import org.apache.cassandra.utils.Pair;
  
- import static org.apache.cassandra.distributed.shared.Versions.Major;
  import static org.apache.cassandra.distributed.shared.Versions.Version;
  import static org.apache.cassandra.distributed.shared.Versions.find;
- import static org.apache.commons.lang3.ArrayUtils.isNotEmpty;
+ 
+ 
 +
  public class UpgradeTestBase extends DistributedTestBase
  {
      @After
@@@ -76,6 -77,18 +79,21 @@@
          public void run(UpgradeableCluster cluster, int node) throws Throwable;
      }
  
+     public static final Semver v22 = new Semver("2.2.0-beta1", SemverType.LOOSE);
+     public static final Semver v30 = new Semver("3.0.0-alpha1", SemverType.LOOSE);
+     public static final Semver v3X = new Semver("3.11.0", SemverType.LOOSE);
++    public static final Semver v40 = new Semver("4.0-alpha1", SemverType.LOOSE);
+ 
+     protected static final List<Pair<Semver,Semver>> SUPPORTED_UPGRADE_PATHS = ImmutableList.of(
+         Pair.create(v22, v30),
+         Pair.create(v22, v3X),
 -        Pair.create(v30, v3X));
++        Pair.create(v30, v3X),
++        Pair.create(v30, v40),
++        Pair.create(v3X, v40));
+ 
+     // the last is always the current
+     public static final Semver CURRENT = SUPPORTED_UPGRADE_PATHS.get(SUPPORTED_UPGRADE_PATHS.size() - 1).right;
+ 
      public static class TestVersions
      {
          final Version initial;
@@@ -186,20 -210,18 +215,17 @@@
                  {
                      setup.run(cluster);
  
-                     for (Version version : upgrade.upgrade)
+                     for (int n : nodesToUpgrade)
                      {
-                         for (int n : nodesToUpgrade)
-                         {
-                             cluster.get(n).shutdown().get();
-                             cluster.get(n).setVersion(version);
-                             runBeforeNodeRestart.run(cluster, n);
-                             cluster.get(n).startup();
-                             runAfterNodeUpgrade.run(cluster, n);
-                         }
- 
-                         runAfterClusterUpgrade.run(cluster);
+                         cluster.get(n).shutdown().get();
+                         cluster.get(n).setVersion(upgrade.upgrade);
+                         runBeforeNodeRestart.run(cluster, n);
+                         cluster.get(n).startup();
+                         runAfterNodeUpgrade.run(cluster, n);
                      }
+ 
+                     runAfterClusterUpgrade.run(cluster);
                  }
 -
              }
          }
          public TestCase nodesToUpgrade(int ... nodes)

---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cassandra.apache.org
For additional commands, e-mail: commits-help@cassandra.apache.org