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 <m...@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-unsubscr...@cassandra.apache.org
For additional commands, e-mail: commits-h...@cassandra.apache.org

Reply via email to