Deduplicate the MixedMode* upgrade jvm-dtests

Also update method parameters to be clearer in how we declare testcase applicability

 patch by Mick Semb Wever; reviewed by David Capwell for CASSANDRA-18506
This commit is contained in:
Mick Semb Wever 2023-05-16 16:04:39 +02:00
parent 4f3cb5de37
commit 41a669a100
No known key found for this signature in database
GPG Key ID: E91335D77E3E87CB
11 changed files with 145 additions and 290 deletions

View File

@ -23,7 +23,6 @@ import java.util.concurrent.RejectedExecutionException;
import org.junit.Test;
import com.vdurmont.semver4j.Semver;
import org.apache.cassandra.distributed.api.ConsistencyLevel;
import org.apache.cassandra.distributed.api.ICoordinator;
import org.apache.cassandra.exceptions.ReadFailureException;
@ -51,13 +50,11 @@ public abstract class MixedModeAvailabilityTestBase extends UpgradeTestBase
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 Semver initial;
private final ConsistencyLevel writeConsistencyLevel;
private final ConsistencyLevel readConsistencyLevel;
public MixedModeAvailabilityTestBase(Semver initial, ConsistencyLevel writeConsistencyLevel, ConsistencyLevel readConsistencyLevel)
public MixedModeAvailabilityTestBase(ConsistencyLevel writeConsistencyLevel, ConsistencyLevel readConsistencyLevel)
{
this.initial = initial;
this.writeConsistencyLevel = writeConsistencyLevel;
this.readConsistencyLevel = readConsistencyLevel;
}
@ -65,32 +62,30 @@ public abstract class MixedModeAvailabilityTestBase extends UpgradeTestBase
@Test
public void testAvailabilityCoordinatorNotUpgraded() throws Throwable
{
testAvailability(false, initial, writeConsistencyLevel, readConsistencyLevel);
testAvailability(false, writeConsistencyLevel, readConsistencyLevel);
}
@Test
public void testAvailabilityCoordinatorUpgraded() throws Throwable
{
testAvailability(true, initial, writeConsistencyLevel, readConsistencyLevel);
testAvailability(true, writeConsistencyLevel, readConsistencyLevel);
}
protected static void testAvailability(Semver initial,
ConsistencyLevel writeConsistencyLevel,
protected static void testAvailability(ConsistencyLevel writeConsistencyLevel,
ConsistencyLevel readConsistencyLevel) throws Throwable
{
testAvailability(true, initial, writeConsistencyLevel, readConsistencyLevel);
testAvailability(false, initial, writeConsistencyLevel, readConsistencyLevel);
testAvailability(true, writeConsistencyLevel, readConsistencyLevel);
testAvailability(false, writeConsistencyLevel, readConsistencyLevel);
}
private static void testAvailability(boolean upgradedCoordinator,
Semver initial,
ConsistencyLevel writeConsistencyLevel,
ConsistencyLevel readConsistencyLevel) throws Throwable
{
new TestCase()
.nodes(NUM_NODES)
.nodesToUpgrade(upgradedCoordinator ? 1 : 2)
.upgradesToCurrentFrom(initial)
.upgradesToCurrentFrom(v30)
.withConfig(config -> config.set("read_request_timeout_in_ms", SECONDS.toMillis(5))
.set("write_request_timeout_in_ms", SECONDS.toMillis(5)))
// use retry of 10ms so that each check is consistent

View File

@ -27,6 +27,6 @@ public class MixedModeAvailabilityV30AllOneTest extends MixedModeAvailabilityTes
{
public MixedModeAvailabilityV30AllOneTest()
{
super(v30, ConsistencyLevel.ALL, ConsistencyLevel.ONE);
super(ConsistencyLevel.ALL, ConsistencyLevel.ONE);
}
}

View File

@ -27,6 +27,6 @@ public class MixedModeAvailabilityV30OneAllTest extends MixedModeAvailabilityTes
{
public MixedModeAvailabilityV30OneAllTest()
{
super(v30, ConsistencyLevel.ONE, ConsistencyLevel.ALL);
super(ConsistencyLevel.ONE, ConsistencyLevel.ALL);
}
}

View File

@ -27,6 +27,6 @@ public class MixedModeAvailabilityV30QuorumQuorumTest extends MixedModeAvailabil
{
public MixedModeAvailabilityV30QuorumQuorumTest()
{
super(v30, ConsistencyLevel.QUORUM, ConsistencyLevel.QUORUM);
super(ConsistencyLevel.QUORUM, ConsistencyLevel.QUORUM);
}
}

View File

@ -1,32 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.distributed.upgrade;
import org.apache.cassandra.distributed.api.ConsistencyLevel;
/**
* {@link MixedModeAvailabilityTestBase} for upgrades from v3X with ALL-ONE write-read consistency.
*/
public class MixedModeAvailabilityV3XAllOneTest extends MixedModeAvailabilityTestBase
{
public MixedModeAvailabilityV3XAllOneTest()
{
super(v3X, ConsistencyLevel.ALL, ConsistencyLevel.ONE);
}
}

View File

@ -1,32 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.distributed.upgrade;
import org.apache.cassandra.distributed.api.ConsistencyLevel;
/**
* {@link MixedModeAvailabilityTestBase} for upgrades from v3X with ONE-ALL write-read consistency.
*/
public class MixedModeAvailabilityV3XOneAllTest extends MixedModeAvailabilityTestBase
{
public MixedModeAvailabilityV3XOneAllTest()
{
super(v3X, ConsistencyLevel.ONE, ConsistencyLevel.ALL);
}
}

View File

@ -1,32 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.distributed.upgrade;
import org.apache.cassandra.distributed.api.ConsistencyLevel;
/**
* {@link MixedModeAvailabilityTestBase} for upgrades from v3X with QUORUM-QUORUM write-read consistency.
*/
public class MixedModeAvailabilityV3XQuorumQuorumTest extends MixedModeAvailabilityTestBase
{
public MixedModeAvailabilityV3XQuorumQuorumTest()
{
super(v3X, ConsistencyLevel.QUORUM, ConsistencyLevel.QUORUM);
}
}

View File

@ -1,124 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.distributed.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 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(Semver initial) 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)
.upgradesToCurrentFrom(initial)
.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);
}
}
}
}

View File

@ -18,16 +18,108 @@
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 org.junit.Test;
/**
* {@link MixedModeConsistencyTestBase} for upgrades from v30.
*/
public class MixedModeConsistencyV30Test extends MixedModeConsistencyTestBase
import org.apache.cassandra.distributed.UpgradeableCluster;
import org.apache.cassandra.distributed.api.ConsistencyLevel;
import org.apache.cassandra.distributed.api.IUpgradeableInstance;
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 MixedModeConsistencyV30Test extends UpgradeTestBase
{
@Test
public void testConsistency() throws Throwable
{
testConsistency(v30);
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)
.upgradesToCurrentFrom(v30)
.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);
}
}
}
}

View File

@ -1,33 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.distributed.upgrade;
import org.junit.Test;
/**
* {@link MixedModeConsistencyTestBase} for upgrades from v3X.
*/
public class MixedModeConsistencyV3XTest extends MixedModeConsistencyTestBase
{
@Test
public void testConsistency() throws Throwable
{
testConsistency(v3X);
}
}

View File

@ -176,26 +176,26 @@ public class UpgradeTestBase extends DistributedTestBase
return this;
}
/** performs all supported upgrade paths that exist in between from and end on CURRENT (inclusive)
/** performs all supported upgrade paths that exist in between lowerBound and end on CURRENT (inclusive)
* {@code upgradesToCurrentFrom(3.0); // produces: 3.0 -> CURRENT, 3.11 -> CURRENT, }
**/
public TestCase upgradesToCurrentFrom(Semver from)
public TestCase upgradesToCurrentFrom(Semver lowerBound)
{
return upgradesTo(from, CURRENT);
return upgradesTo(lowerBound, CURRENT);
}
/**
* performs all supported upgrade paths to the "to" target; example
* {@code upgradesTo(3.0, 4.0); // produces: 3.0 -> 4.0, 3.11 -> 4.0}
*/
public TestCase upgradesTo(Semver from, Semver to)
public TestCase upgradesTo(Semver lowerBound, Semver to)
{
List<TestVersions> upgrade = new ArrayList<>();
NavigableSet<Semver> vertices = sortedVertices(SUPPORTED_UPGRADE_PATHS);
for (Semver start : vertices.subSet(from, true, to, false))
for (Semver start : vertices.subSet(lowerBound, true, to, false))
{
// only include pairs that are allowed, and start or end on CURRENT
if (SUPPORTED_UPGRADE_PATHS.hasEdge(start, to) && contains(start, to, CURRENT))
if (SUPPORTED_UPGRADE_PATHS.hasEdge(start, to) && edgeTouchesTarget(start, to, CURRENT))
upgrade.add(new TestVersions(versions.getLatest(start), Collections.singletonList(versions.getLatest(to))));
}
logger.info("Adding upgrades of\n{}", upgrade.stream().map(TestVersions::toString).collect(Collectors.joining("\n")));
@ -207,14 +207,14 @@ public class UpgradeTestBase extends DistributedTestBase
* performs all supported upgrade paths from the "from" target; example
* {@code upgradesFrom(4.0, 4.2); // produces: 4.0 -> 4.1, 4.0 -> 4.2}
*/
public TestCase upgradesFrom(Semver from, Semver to)
public TestCase upgradesFrom(Semver from, Semver upperBound)
{
List<TestVersions> upgrade = new ArrayList<>();
NavigableSet<Semver> vertices = sortedVertices(SUPPORTED_UPGRADE_PATHS);
for (Semver end : vertices.subSet(from, false, to, true))
for (Semver end : vertices.subSet(from, false, upperBound, true))
{
// only include pairs that are allowed, and start or end on CURRENT
if (SUPPORTED_UPGRADE_PATHS.hasEdge(from, end) && contains(from, end, CURRENT))
if (SUPPORTED_UPGRADE_PATHS.hasEdge(from, end) && edgeTouchesTarget(from, end, CURRENT))
upgrade.add(new TestVersions(versions.getLatest(from), Collections.singletonList(versions.getLatest(end))));
}
logger.info("Adding upgrades of\n{}", upgrade.stream().map(TestVersions::toString).collect(Collectors.joining("\n")));
@ -226,20 +226,41 @@ public class UpgradeTestBase extends DistributedTestBase
* performs all supported upgrade paths that exist in between from and to that include the current version.
* This call is equivalent to calling {@code upgradesTo(from, CURRENT).upgradesFrom(CURRENT, to)}.
**/
public TestCase upgrades(Semver from, Semver to)
public TestCase upgrades(Semver lowerBound, Semver upperBound)
{
Assume.assumeTrue("Unable to do upgrades(" + from + ", " + to + "); does not contain CURRENT=" + CURRENT, contains(from, to, CURRENT));
if (from.compareTo(CURRENT) < 0)
upgradesTo(from, CURRENT);
if (CURRENT.compareTo(to) < 0)
upgradesFrom(CURRENT, to);
Assume.assumeTrue("Unable to do any upgrades in bounds (" + lowerBound + ", " + upperBound + "); does not cover CURRENT=" + CURRENT, rangeCoversTarget(lowerBound, upperBound, CURRENT));
if (lowerBound.compareTo(CURRENT) < 0)
upgradesTo(lowerBound, CURRENT);
if (CURRENT.compareTo(upperBound) < 0)
upgradesFrom(CURRENT, upperBound);
return this;
}
private static boolean contains(Semver from, Semver to, Semver target)
private static boolean rangeCoversTarget(Semver lowerBound, Semver upperBound, Semver target)
{
// target >= from && target <= to
return target.compareTo(from) >= 0 && target.compareTo(to) <= 0;
return target.isGreaterThanOrEqualTo(lowerBound) && target.isLowerThanOrEqualTo(upperBound);
}
/** returns true if the target version has the same major and minor as either the from or the to version **/
private static boolean edgeTouchesTarget(Semver from, Semver to, Semver target)
{
switch (from.diff(target))
{
default:
return true;
case MAJOR:
case MINOR:
// fall through
}
switch (to.diff(target))
{
default:
return true;
case MAJOR:
case MINOR:
return false;
}
}
/** Will test this specific upgrade path **/