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