You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by if...@apache.org on 2023/12/23 14:16:27 UTC
(cassandra) branch trunk updated: Bring Harry into C* Tree
This is an automated email from the ASF dual-hosted git repository.
ifesdjeen pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra.git
The following commit(s) were added to refs/heads/trunk by this push:
new 439d1b122a Bring Harry into C* Tree
439d1b122a is described below
commit 439d1b122af334bf68c159b82ef4e4879c210bd5
Author: Alex Petrov <ol...@gmail.com>
AuthorDate: Sun Dec 10 18:30:26 2023 +0100
Bring Harry into C* Tree
Patch by Alex Petrov; reviewed by Caleb Rackliffe and Marcus Eriksson for CASSANDRA-19210.
---
.build/cassandra-build-deps-template.xml | 4 -
.build/parent-pom-template.xml | 6 -
build.xml | 2 +
ide/idea-iml-file.xml | 1 +
ide/nbproject/project.xml | 13 +-
.../config/CassandraRelevantProperties.java | 1 +
test/conf/harry-generic.yaml | 92 --
.../ConcurrentQuiescentCheckerIntegrationTest.java | 123 --
.../cassandra/distributed/fuzz/FuzzTestBase.java | 132 --
.../fuzz/InJVMTokenAwareVisitorExecutor.java | 111 --
.../distributed/fuzz/QueryingNoOpChecker.java | 66 -
.../distributed/fuzz/SSTableGenerator.java | 363 ------
.../distributed/fuzz/SSTableLoadingVisitor.java | 113 --
.../fuzz/test/SSTableGeneratorTest.java | 131 --
.../cassandra/distributed/shared/ClusterUtils.java | 7 +-
.../distributed/test/log/BounceGossipTest.java | 41 +-
.../distributed/test/log/CMSTestBase.java | 5 +-
.../distributed/test/log/CoordinatorPathTest.java | 32 +-
.../test/log/CoordinatorPathTestBase.java | 26 +-
.../distributed/test/log/FailedLeaveTest.java | 38 +-
.../distributed/test/log/FuzzTestBase.java | 96 +-
.../test/log/MetadataChangeSimulationTest.java | 86 +-
.../cassandra/distributed/test/log/ModelState.java | 76 +-
.../test/log/OperationalEquivalenceTest.java | 43 +-
.../distributed/test/log/PlacementSimulator.java | 765 +-----------
.../test/log/PlacementSimulatorTest.java | 33 +-
.../distributed/test/log/ResumableStartupTest.java | 47 +-
.../distributed/test/log/SimulatedOperation.java | 6 +-
.../test/log/SystemKeyspaceStorageTest.java | 3 +-
.../harry/examples/RangeTombstoneBurnTest.java | 144 +++
.../fuzz/harry/gen/DataGeneratorsTest.java | 504 ++++++++
.../fuzz/harry/gen/EntropySourceTest.java | 158 +++
.../harry/gen/ExtensionsTest.java} | 35 +-
.../cassandra/fuzz/harry/gen/SurjectionsTest.java | 63 +
.../harry/integration/QuickTheoriesAdapter.java | 100 ++
.../fuzz/harry/integration/ddl/SchemaGenTest.java | 216 ++++
.../dsl/HistoryBuilderIntegrationTest.java | 148 +++
.../generators/DataGeneratorsIntegrationTest.java | 179 +++
.../ConcurrentQuiescentCheckerIntegrationTest.java | 71 ++
.../model/InJVMTokenAwareExecutorTest.java | 98 ++
.../integration/model/IntegrationTestBase.java | 116 ++
.../fuzz/harry/integration/model/MockSchema.java | 117 ++
.../harry/integration/model/ModelTestBase.java | 138 +++
.../model/QuerySelectorNegativeTest.java | 157 +++
.../harry/integration/model/QuerySelectorTest.java | 163 +++
.../model/QuiescentCheckerIntegrationTest.java | 224 ++++
.../QuiescentLocalStateCheckerIntegrationTest.java | 79 ++
.../model/ReconcilerIntegrationTest.java | 94 ++
.../model/reconciler/SimpleReconcilerTest.java | 341 ++++++
.../fuzz/harry/integration/op/RowVisitorTest.java | 90 ++
.../fuzz/harry/model/ApproximateClockTest.java | 180 +++
.../fuzz/harry/model/OpSelectorsTest.java | 418 +++++++
.../fuzz/harry/operations/RelationTest.java | 204 ++++
.../fuzz/harry/runner/LockingDataTrackerTest.java | 133 +++
.../cassandra/fuzz/harry/util/BitSetTest.java | 66 +
.../cassandra/fuzz/harry/util/RangesTest.java | 102 ++
.../ring}/ClusterMetadataUpgradeHarryTest.java | 55 +-
.../ring/ConsistentBootstrapTest.java | 142 +--
.../log => fuzz/ring}/ConsistentLeaveTest.java | 63 +-
.../test/log => fuzz/ring}/ConsistentMoveTest.java | 67 +-
test/harry/main/README.md | 647 ++++++++++
.../org/apache/cassandra/harry}/HarryHelper.java | 52 +-
.../cassandra/harry/checker/ModelChecker.java | 230 ++++
.../cassandra/harry/clock/ApproximateClock.java | 279 +++++
.../apache/cassandra/harry/clock/OffsetClock.java | 94 ++
.../apache/cassandra/harry/core/Configuration.java | 1255 ++++++++++++++++++++
.../cassandra/harry/core/MetricReporter.java | 51 +
.../main/org/apache/cassandra/harry/core/Run.java | 75 ++
.../harry/corruptor/AddExtraRowCorruptor.java | 90 ++
.../harry/corruptor/ChangeValueCorruptor.java | 85 ++
.../harry/corruptor/HideRowCorruptor.java | 49 +
.../harry/corruptor/HideValueCorruptor.java | 109 ++
.../harry/corruptor/QueryResponseCorruptor.java | 108 ++
.../cassandra/harry/corruptor/RowCorruptor.java | 56 +
.../harry/corruptor/ShowValueCorruptor.java | 77 ++
.../apache/cassandra/harry/data/ResultSetRow.java | 137 +++
.../org/apache/cassandra/harry/ddl/ColumnSpec.java | 438 +++++++
.../cassandra/harry/ddl/SchemaGenerators.java | 512 ++++++++
.../org/apache/cassandra/harry/ddl/SchemaSpec.java | 492 ++++++++
.../harry/dsl/BatchOperationBuilder.java} | 14 +-
.../cassandra/harry/dsl/BatchVisitBuilder.java | 138 +++
.../apache/cassandra/harry/dsl/HistoryBuilder.java | 598 ++++++++++
.../cassandra/harry/dsl/PartitionVisitState.java | 35 +
.../harry/dsl/ReplayingHistoryBuilder.java | 117 ++
.../harry/dsl/SingleOperationBuilder.java | 53 +
.../harry/dsl/SingleOperationVisitBuilder.java | 295 +++++
.../harry/dsl/ValueDescriptorIndexGenerator.java | 78 ++
.../org/apache/cassandra/harry/gen/Bijections.java | 457 +++++++
.../cassandra/harry/gen/BooleanGenerator.java | 29 +
.../main/org/apache/cassandra/harry/gen/Bytes.java | 42 +
.../apache/cassandra/harry/gen/Collections.java | 249 ++++
.../apache/cassandra/harry/gen/DataGenerators.java | 499 ++++++++
.../apache/cassandra/harry/gen/EntropySource.java | 95 ++
.../org/apache/cassandra/harry/gen/Generator.java | 133 +++
.../org/apache/cassandra/harry/gen/Generators.java | 86 ++
.../cassandra/harry/gen/StringBijection.java | 172 +++
.../apache/cassandra/harry/gen/Surjections.java | 170 +++
.../harry/gen/distribution/Distribution.java | 136 +++
.../harry/gen/rng/JdkRandomEntropySource.java | 83 ++
.../cassandra/harry/gen/rng/PCGFastPure.java | 145 +++
.../apache/cassandra/harry/gen/rng/PcgRSUFast.java | 133 +++
.../apache/cassandra/harry/gen/rng/RngUtils.java | 172 +++
.../cassandra/harry/model/AgainstSutChecker.java | 99 ++
.../harry/model/AlwaysSamePartitionSelector.java | 92 ++
.../org/apache/cassandra/harry/model/Model.java | 47 +
.../apache/cassandra/harry/model/NoOpChecker.java | 40 +
.../apache/cassandra/harry/model/OpSelectors.java | 903 ++++++++++++++
.../harry/model/QueryingNoOpValidator.java | 65 +
.../cassandra/harry/model/QuiescentChecker.java | 296 +++++
.../model/QuiescentLocalStateCheckerBase.java | 123 ++
.../harry/model/RepairingLocalStateValidator.java | 92 ++
.../apache/cassandra/harry/model/SelectHelper.java | 337 ++++++
.../harry/model/reconciler/PartitionState.java | 311 +++++
.../harry/model/reconciler/Reconciler.java | 331 ++++++
.../harry/model/ring/PlacementSimulator.java | 1052 ++++++++++++++++
.../harry/operations/CompiledStatement.java | 88 ++
.../cassandra/harry/operations/DeleteHelper.java | 190 +++
.../cassandra/harry/operations/FilteringQuery.java | 68 ++
.../apache/cassandra/harry/operations/Query.java | 501 ++++++++
.../cassandra/harry/operations/QueryGenerator.java | 176 +++
.../cassandra/harry/operations/Relation.java | 299 +++++
.../cassandra/harry/operations/WriteHelper.java | 166 +++
.../cassandra/harry/runner/EarlyExitException.java | 27 +
.../cassandra/harry/runner}/FlaggedRunner.java | 19 +-
.../apache/cassandra/harry/runner/HarryRunner.java | 115 ++
.../org/apache/cassandra/harry/runner/Runner.java | 479 ++++++++
.../cassandra/harry/runner/StagedRunner.java | 92 ++
.../cassandra/harry/runner/TrivialShrinker.java | 189 +++
.../cassandra/harry/runner/UpToLtsRunner.java | 111 ++
.../cassandra/harry/sut/DoubleWritingSut.java | 60 +
.../org/apache/cassandra/harry/sut/PrintlnSut.java | 67 ++
.../cassandra/harry/sut/QueryModifyingSut.java | 80 ++
.../cassandra/harry/sut/SystemUnderTest.java | 105 ++
.../cassandra/harry/sut/TokenPlacementModel.java | 891 ++++++++++++++
.../cassandra/harry/sut/injvm}/ClusterState.java | 2 +-
.../harry/sut/injvm}/ExistingClusterSUT.java | 6 +-
.../sut/injvm/InJVMTokenAwareVisitExecutor.java | 163 +++
.../cassandra/harry/sut/injvm}/InJvmSut.java | 16 +-
.../cassandra/harry/sut/injvm}/InJvmSutBase.java | 50 +-
.../sut/injvm/QuiescentLocalStateChecker.java | 87 ++
.../cassandra/harry/tracker/DataTracker.java | 66 +
.../harry/tracker/DefaultDataTracker.java | 193 +++
.../harry/tracker/LockingDataTracker.java | 286 +++++
.../org/apache/cassandra/harry/util/BitSet.java | 244 ++++
.../org/apache/cassandra/harry/util/ByteUtils.java | 162 +++
.../cassandra/harry/util/DescriptorRanges.java | 150 +++
.../apache/cassandra/harry/util/StringUtils.java | 35 +
.../apache/cassandra/harry/util/TestRunner.java | 106 ++
.../cassandra/harry/util/ThrowingRunnable.java | 33 +
.../org/apache/cassandra/harry/util/TokenUtil.java | 150 +++
.../harry/visitors/AllPartitionsValidator.java | 150 +++
.../harry/visitors/CorruptingVisitor.java | 89 ++
.../harry/visitors/DoubleWriteVisitExecutor.java | 56 +
.../harry/visitors/FaultInjectingVisitor.java | 97 ++
.../harry/visitors/GeneratingVisitor.java | 232 ++++
.../cassandra/harry/visitors/LoggingVisitor.java | 85 ++
.../cassandra/harry/visitors/LtsVisitor.java | 83 ++
.../harry/visitors/MutatingRowVisitor.java | 115 ++
.../cassandra/harry/visitors/MutatingVisitor.java | 220 ++++
.../harry/visitors/OperationExecutor.java | 78 ++
.../harry/visitors/ParallelRecentValidator.java | 149 +++
.../harry/visitors/ParallelValidator.java | 103 ++
.../cassandra/harry/visitors/QueryLogger.java | 97 ++
.../harry/visitors/RandomPartitionValidator.java | 58 +
.../cassandra/harry/visitors/RandomValidator.java | 93 ++
.../cassandra/harry/visitors/RecentValidator.java | 96 ++
.../cassandra/harry/visitors/ReplayingVisitor.java | 73 ++
.../apache/cassandra/harry/visitors/Sampler.java | 97 ++
.../cassandra/harry/visitors/SingleValidator.java | 66 +
.../cassandra/harry/visitors/SkippingVisitor.java | 56 +
.../cassandra/harry/visitors/VisitExecutor.java | 122 ++
.../apache/cassandra/harry/visitors/Visitor.java | 33 +
.../simulator/AlwaysDeliverNetworkScheduler.java | 6 +-
.../simulator/cluster/KeyspaceActions.java | 50 +-
.../simulator/harry/HarryValidatingQuery.java | 18 +-
.../simulator/test/HarrySimulatorTest.java | 57 +-
.../simulator/test/SimulatedVisitExectuor.java | 29 +-
.../apache/cassandra/tcm/ClusterMetadataTest.java | 4 +-
.../UniformRangePlacementIntegrationTest.java | 14 +-
.../tcm/sequences/ProgressBarrierTest.java | 46 +-
180 files changed, 25046 insertions(+), 2578 deletions(-)
diff --git a/.build/cassandra-build-deps-template.xml b/.build/cassandra-build-deps-template.xml
index b620cda8f5..9f9a70000f 100644
--- a/.build/cassandra-build-deps-template.xml
+++ b/.build/cassandra-build-deps-template.xml
@@ -106,10 +106,6 @@
<groupId>org.apache.ant</groupId>
<artifactId>ant-junit</artifactId>
</dependency>
- <dependency>
- <groupId>org.apache.cassandra</groupId>
- <artifactId>harry-core</artifactId>
- </dependency>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
diff --git a/.build/parent-pom-template.xml b/.build/parent-pom-template.xml
index 4b55268bfd..5440085c97 100644
--- a/.build/parent-pom-template.xml
+++ b/.build/parent-pom-template.xml
@@ -513,12 +513,6 @@
</exclusion>
</exclusions>
</dependency>
- <dependency>
- <groupId>org.apache.cassandra</groupId>
- <artifactId>harry-core</artifactId>
- <version>0.0.2</version>
- <scope>test</scope>
- </dependency>
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
diff --git a/build.xml b/build.xml
index 162c137984..79fdbda8d9 100644
--- a/build.xml
+++ b/build.xml
@@ -87,6 +87,7 @@
<property name="test.simulator-asm.src" value="${test.dir}/simulator/asm"/>
<property name="test.simulator-bootstrap.src" value="${test.dir}/simulator/bootstrap"/>
<property name="test.simulator-test.src" value="${test.dir}/simulator/test"/>
+ <property name="test.harry.src" value="${test.dir}/harry/main"/>
<property name="test.driver.connection_timeout_ms" value="10000"/>
<property name="test.driver.read_timeout_ms" value="24000"/>
<property name="test.jvm.args" value="" />
@@ -1043,6 +1044,7 @@
<src path="${test.simulator-asm.src}"/>
<src path="${test.simulator-bootstrap.src}"/>
<src path="${test.simulator-test.src}"/>
+ <src path="${test.harry.src}"/>
<compilerarg line="${jdk11plus-javac-exports}"/>
</javac>
diff --git a/ide/idea-iml-file.xml b/ide/idea-iml-file.xml
index 01c071a728..13e66fa613 100644
--- a/ide/idea-iml-file.xml
+++ b/ide/idea-iml-file.xml
@@ -41,6 +41,7 @@
<sourceFolder url="file://$MODULE_DIR$/test/simulator/bootstrap" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/test/simulator/main" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/test/simulator/test" isTestSource="true" />
+ <sourceFolder url="file://$MODULE_DIR$/test/harry/main" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/test/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/test/conf" type="java-test-resource" />
<excludeFolder url="file://$MODULE_DIR$/.idea" />
diff --git a/ide/nbproject/project.xml b/ide/nbproject/project.xml
index 3250cb78db..ea9a486aa5 100644
--- a/ide/nbproject/project.xml
+++ b/ide/nbproject/project.xml
@@ -7,7 +7,7 @@
<properties>
<property name="project.dir">..</property>
<!-- the compile classpaths should be distinct per compilation unit… but it is kept simple and the build will catch errors -->
- <property name="cassandra.classpath.jars">${project.dir}/build/lib/jars/AmazonCorrettoCryptoProvider-2.2.0-linux-x86_64.jar:${project.dir}/build/lib/jars/HdrHistogram-2.1.9.jar:${project.dir}/build/lib/jars/ST4-4.0.8.jar:${project.dir}/build/lib/jars/affinity-3.23.3.jar:${project.dir}/build/lib/jars/agrona-1.17.1.jar:${project.dir}/build/lib/jars/airline-0.8.jar:${project.dir}/build/lib/jars/antlr-3.5.2.jar:${project.dir}/build/lib/jars/antlr-runtime-3.5.2.jar:${project.d [...]
+ <property name="cassandra.classpath.jars">${project.dir}/build/lib/jars/AmazonCorrettoCryptoProvider-2.2.0-linux-x86_64.jar:${project.dir}/build/lib/jars/HdrHistogram-2.1.9.jar:${project.dir}/build/lib/jars/ST4-4.0.8.jar:${project.dir}/build/lib/jars/affinity-3.23.3.jar:${project.dir}/build/lib/jars/agrona-1.17.1.jar:${project.dir}/build/lib/jars/airline-0.8.jar:${project.dir}/build/lib/jars/antlr-3.5.2.jar:${project.dir}/build/lib/jars/antlr-runtime-3.5.2.jar:${project.d [...]
</properties>
<folders>
<source-folder>
@@ -112,6 +112,12 @@
<location>${project.dir}/test/simulator/test</location>
<encoding>UTF-8</encoding>
</source-folder>
+ <source-folder>
+ <label>test/fuzz/main</label>
+ <type>java</type>
+ <location>${project.dir}/test/harry/main</location>
+ <encoding>UTF-8</encoding>
+ </source-folder>
<source-folder>
<label>apache-cassandra</label>
<location>${project.dir}</location>
@@ -259,6 +265,10 @@
<label>test/simulator/test</label>
<location>${project.dir}/test/simulator/test</location>
</source-folder>
+ <source-folder style="packages">
+ <label>test/harry/main</label>
+ <location>${project.dir}/test/harry/main</location>
+ </source-folder>
<source-file>
<location>${project.dir}/build.xml</location>
</source-file>
@@ -300,6 +310,7 @@
<package-root>${project.dir}/test/simulator/bootstrap</package-root>
<package-root>${project.dir}/test/simulator/main</package-root>
<package-root>${project.dir}/test/simulator/test</package-root>
+ <package-root>${project.dir}/test/harry/main</package-root>
<unit-tests/>
<classpath mode="compile">${cassandra.classpath.jars}:${project.dir}/build/classes/main:${project.dir}/build/classes/fqltool/:${project.dir}/build/classes/stress/</classpath>
<built-to>${project.dir}/build/test/classes</built-to>
diff --git a/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java b/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java
index db18c45eb8..5f9289e048 100644
--- a/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java
+++ b/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java
@@ -566,6 +566,7 @@ public enum CassandraRelevantProperties
* faster. Note that this is disabled for unit tests but if an individual test requires schema to be flushed, it
* can be also done manually for that particular case: {@code flush(SchemaConstants.SCHEMA_KEYSPACE_NAME);}. */
TEST_FLUSH_LOCAL_SCHEMA_CHANGES("cassandra.test.flush_local_schema_changes", "true"),
+ TEST_HARRY_SWITCH_AFTER("cassandra.test.harry.progression.switch-after", "1"),
TEST_IGNORE_SIGAR("cassandra.test.ignore_sigar"),
TEST_INVALID_LEGACY_SSTABLE_ROOT("invalid-legacy-sstable-root"),
TEST_JVM_DTEST_DISABLE_SSL("cassandra.test.disable_ssl"),
diff --git a/test/conf/harry-generic.yaml b/test/conf/harry-generic.yaml
deleted file mode 100644
index a0a34bbb30..0000000000
--- a/test/conf/harry-generic.yaml
+++ /dev/null
@@ -1,92 +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.
-#
-#
-seed: 1
-
-# Default schema provider generates random schema
-schema_provider:
- default: {}
-
-drop_schema: false
-create_schema: true
-truncate_table: false
-
-clock:
- approximate_monotonic:
- history_size: 7300
- epoch_length: 1
- epoch_time_unit: "SECONDS"
-
-system_under_test:
- println: {}
-
-partition_descriptor_selector:
- default:
- window_size: 100
- slide_after_repeats: 10
-
-clustering_descriptor_selector:
- default:
- modifications_per_lts:
- type: "constant"
- constant: 2
- rows_per_modification:
- type: "constant"
- constant: 2
- operation_kind_weights:
- DELETE_RANGE: 1
- DELETE_SLICE: 1
- DELETE_ROW: 1
- DELETE_COLUMN: 1
- DELETE_PARTITION: 1
- INSERT: 50
- UPDATE: 50
- DELETE_COLUMN_WITH_STATICS: 1
- INSERT_WITH_STATICS: 1
- UPDATE_WITH_STATICS: 1
- column_mask_bitsets: null
- max_partition_size: 100
-
-data_tracker:
- default:
- max_seen_lts: -1
- max_complete_lts: -1
-
-runner:
- sequential:
- run_time: 60
- run_time_unit: "MINUTES"
- visitors:
- - logging:
- row_visitor:
- mutating: {}
- - sampler:
- trigger_after: 100000
- sample_partitions: 10
- - validate_recent_partitions:
- partition_count: 5
- trigger_after: 10000
- model:
- querying_no_op_checker: {}
- - validate_all_partitions:
- concurrency: 5
- trigger_after: 10000
- model:
- querying_no_op_checker: {}
-
-metric_reporter:
- no_op: {}
\ No newline at end of file
diff --git a/test/distributed/org/apache/cassandra/distributed/fuzz/ConcurrentQuiescentCheckerIntegrationTest.java b/test/distributed/org/apache/cassandra/distributed/fuzz/ConcurrentQuiescentCheckerIntegrationTest.java
deleted file mode 100644
index 8c43b46a84..0000000000
--- a/test/distributed/org/apache/cassandra/distributed/fuzz/ConcurrentQuiescentCheckerIntegrationTest.java
+++ /dev/null
@@ -1,123 +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.fuzz;
-
-import java.io.File;
-import java.util.concurrent.TimeUnit;
-import java.util.function.Supplier;
-
-import org.junit.Test;
-
-import harry.core.Configuration;
-import harry.core.Run;
-import harry.ddl.SchemaSpec;
-import harry.generators.Surjections;
-import harry.model.sut.injvm.InJvmSut;
-import harry.runner.LockingDataTracker;
-import harry.runner.Runner;
-import harry.visitors.MutatingVisitor;
-import harry.visitors.QueryLogger;
-import harry.visitors.RandomPartitionValidator;
-import org.apache.cassandra.distributed.api.ICluster;
-import org.apache.cassandra.distributed.api.IInvokableInstance;
-import org.apache.cassandra.distributed.test.TestBaseImpl;
-
-import static harry.core.Configuration.VisitorPoolConfiguration.pool;
-import static java.util.Arrays.asList;
-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;
-
-public class ConcurrentQuiescentCheckerIntegrationTest extends TestBaseImpl
-{
-
- private static Supplier<SchemaSpec> schemaSupplier()
- {
- Surjections.Surjection<SchemaSpec> schemaGen = HarryHelper.schemaSpecGen("harry", "table_");
- return new Supplier<SchemaSpec>()
- {
- int schemaIdx = 0;
- public SchemaSpec get()
- {
- while (true)
- {
- SchemaSpec schema = schemaGen.inflate(schemaIdx++);
- try
- {
- schema.validate();
- return schema;
- }
- catch (AssertionError e)
- {
- continue;
- }
- }
- }
- };
- }
-
- @Test
- public void testWithConcurrentQuiescentChecker() throws Throwable
- {
- final int writeThreads = 2;
- final int readThreads = 2;
-
- try (ICluster<IInvokableInstance> cluster = builder().withNodes(3)
- .withConfig(config -> config.with(GOSSIP, NETWORK, NATIVE_PROTOCOL))
- .start())
- {
- Supplier<SchemaSpec> schemaSupplier = schemaSupplier();
-
- for (int i = 0; i < 3; i++)
- {
- SchemaSpec schema = schemaSupplier.get();
- Configuration config = HarryHelper
- .defaultConfiguration()
- .setSeed(1L)
- .setClock(new Configuration.ApproximateMonotonicClockConfiguration((int) TimeUnit.MINUTES.toSeconds(20), 1, TimeUnit.SECONDS))
- .setSUT(() -> new InJvmSut(cluster))
- .setKeyspaceDdl("CREATE KEYSPACE IF NOT EXISTS " + schema.keyspace + " WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 3};")
- .setSchemaProvider((seed, sut) -> schema)
- .setCreateSchema(true)
- .setDropSchema(true)
- .setDataTracker(LockingDataTracker::new)
- .build();
-
- Run run = config.createRun();
- try
- {
- new Runner.ConcurrentRunner(config.createRun(), config,
- asList(pool("Writer", writeThreads, MutatingVisitor::new),
- pool("Reader", readThreads, (run_) -> new RandomPartitionValidator(run_, new Configuration.QuiescentCheckerConfig(), QueryLogger.NO_OP))),
- 1, TimeUnit.MINUTES)
- .run();
- }
- catch (Throwable e)
- {
- Configuration.toFile(new File(String.format("failure-%d.yaml", config.seed)),
- config.unbuild()
- .setClock(run.clock.toConfig())
- .setDataTracker(run.tracker.toConfig())
- .build());
- throw e;
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/test/distributed/org/apache/cassandra/distributed/fuzz/FuzzTestBase.java b/test/distributed/org/apache/cassandra/distributed/fuzz/FuzzTestBase.java
deleted file mode 100644
index 13e4f8ce64..0000000000
--- a/test/distributed/org/apache/cassandra/distributed/fuzz/FuzzTestBase.java
+++ /dev/null
@@ -1,132 +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.fuzz;
-
-import java.util.Collection;
-
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-
-import harry.core.Configuration;
-import harry.core.Run;
-import harry.ddl.SchemaSpec;
-import harry.model.clock.OffsetClock;
-import org.apache.cassandra.Util;
-import org.apache.cassandra.db.ColumnFamilyStore;
-import org.apache.cassandra.db.Keyspace;
-import org.apache.cassandra.distributed.Cluster;
-import org.apache.cassandra.distributed.test.TestBaseImpl;
-import org.apache.cassandra.io.sstable.format.SSTableReader;
-
-public abstract class FuzzTestBase extends TestBaseImpl
-{
- protected static Configuration configuration;
- public static final int RF = 2;
- static
- {
- try
- {
- HarryHelper.init();
- configuration = HarryHelper.defaultConfiguration().build();
- }
- catch (Exception e)
- {
- throw new RuntimeException(e);
- }
- }
-
- protected static Cluster cluster;
-
- @BeforeClass
- public static void beforeClassOverride() throws Throwable
- {
- cluster = Cluster.build(2)
- .start();
-
- init(cluster, RF);
- }
-
- @AfterClass
- public static void afterClass()
- {
- if (cluster != null)
- cluster.close();
- }
-
- public void reset()
- {
- cluster.schemaChange("DROP KEYSPACE IF EXISTS " + KEYSPACE);
- init(cluster, RF);
- }
-
- /**
- * Helped method to generate a {@code number} of sstables for the given {@code schemaSpec}.
- */
- @SuppressWarnings("unused")
- public static void generateTables(SchemaSpec schemaSpec, int number)
- {
- Run run = configuration.unbuild()
- .setSeed(1)
- .setSchemaProvider(new FixedSchemaProviderConfiguration(schemaSpec))
- .setClock(() -> new OffsetClock(10000L))
- .setDropSchema(false)
- .setCreateSchema(false)
- .build()
- .createRun();
-
- ColumnFamilyStore store = Keyspace.open(schemaSpec.keyspace).getColumnFamilyStore(schemaSpec.table);
- store.disableAutoCompaction();
-
- SSTableGenerator gen = new SSTableGenerator(run, store);
- SSTableGenerator largePartitionGen = new SSTableWithLargePartition(run, store);
- for (int i = 0; i < number; i++)
- {
- if (i % 3 == 0)
- largePartitionGen.gen(1_000);
- else
- gen.gen(1_000);
- }
- }
-
- /**
- * Helper class to force generation of a fixed partition size.
- */
- private static class SSTableWithLargePartition extends SSTableGenerator
- {
- public SSTableWithLargePartition(Run run, ColumnFamilyStore store)
- {
- super(run, store);
- }
-
- @Override
- public Collection<SSTableReader> gen(int rows)
- {
- long lts = 0;
- for (int i = 0; i < rows; i++)
- {
- long current = lts++;
- write(current, current, current, current, true).applyUnsafe();
- if (schema.staticColumns != null)
- writeStatic(current, 0, current, current, true).applyUnsafe();
- }
- Util.flush(store);
- return null;
- }
- }
-}
diff --git a/test/distributed/org/apache/cassandra/distributed/fuzz/InJVMTokenAwareVisitorExecutor.java b/test/distributed/org/apache/cassandra/distributed/fuzz/InJVMTokenAwareVisitorExecutor.java
deleted file mode 100644
index 05e48ef25a..0000000000
--- a/test/distributed/org/apache/cassandra/distributed/fuzz/InJVMTokenAwareVisitorExecutor.java
+++ /dev/null
@@ -1,111 +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.fuzz;
-
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.TimeUnit;
-
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.annotation.JsonTypeName;
-import harry.core.Run;
-import harry.ddl.SchemaSpec;
-import harry.model.sut.SystemUnderTest;
-import harry.operations.CompiledStatement;
-import harry.visitors.*;
-import org.apache.cassandra.distributed.test.log.RngUtils;
-
-public class InJVMTokenAwareVisitorExecutor extends LoggingVisitor.LoggingVisitorExecutor
-{
- public static void init()
- {
- harry.core.Configuration.registerSubtypes(Configuration.class);
- }
-
- private final InJvmSut sut;
- private final SystemUnderTest.ConsistencyLevel cl;
- private final SchemaSpec schema;
- private final int maxRetries = 10;
-
- // TODO: as of now, token aware executor only performs operations against natrual, but not pending replicas.
- public InJVMTokenAwareVisitorExecutor(Run run,
- OperationExecutor.RowVisitorFactory rowVisitorFactory,
- SystemUnderTest.ConsistencyLevel cl)
- {
- super(run, rowVisitorFactory.make(run));
- this.sut = (InJvmSut) run.sut;
- this.schema = run.schemaSpec;
- this.cl = cl;
- }
-
- @Override
- protected void executeAsyncWithRetries(long lts, long pd, CompletableFuture<Object[][]> future, CompiledStatement statement)
- {
- executeAsyncWithRetries(lts, pd, future, statement, 0);
- }
-
- private void executeAsyncWithRetries(long lts, long pd, CompletableFuture<Object[][]> future, CompiledStatement statement, int retries)
- {
- if (sut.isShutdown())
- throw new IllegalStateException("System under test is shut down");
-
- if (retries > this.maxRetries)
- throw new IllegalStateException(String.format("Can not execute statement %s after %d retries", statement, retries));
-
- Object[] partitionKey = schema.inflatePartitionKey(pd);
- int[] replicas = sut.getReadReplicasFor(partitionKey, schema.keyspace, schema.table);
- int replica = replicas[RngUtils.asInt(lts, 0, replicas.length - 1)];
- if (cl == SystemUnderTest.ConsistencyLevel.NODE_LOCAL)
- {
- future.complete(sut.cluster.get(replica).executeInternal(statement.cql(), statement.bindings()));
- }
- else
- {
- CompletableFuture.supplyAsync(() -> sut.cluster.coordinator(replica).execute(statement.cql(), InJvmSut.toApiCl(cl), statement.bindings()), executor)
- .whenComplete((res, t) ->
- {
- if (t != null)
- executor.schedule(() -> executeAsyncWithRetries(lts, pd, future, statement, retries + 1), 1, TimeUnit.SECONDS);
- else
- future.complete(res);
- });
- }
- }
-
- @JsonTypeName("in_jvm_token_aware")
- public static class Configuration implements harry.core.Configuration.VisitorConfiguration
- {
- public final harry.core.Configuration.RowVisitorConfiguration row_visitor;
- public final SystemUnderTest.ConsistencyLevel consistency_level;
-
- @JsonCreator
- public Configuration(@JsonProperty("row_visitor") harry.core.Configuration.RowVisitorConfiguration rowVisitor,
- @JsonProperty("consistency_level") SystemUnderTest.ConsistencyLevel consistencyLevel)
- {
- this.row_visitor = rowVisitor;
- this.consistency_level = consistencyLevel;
- }
-
- @Override
- public Visitor make(Run run)
- {
- return new GeneratingVisitor(run, new InJVMTokenAwareVisitorExecutor(run, row_visitor, consistency_level));
- }
- }
-}
\ No newline at end of file
diff --git a/test/distributed/org/apache/cassandra/distributed/fuzz/QueryingNoOpChecker.java b/test/distributed/org/apache/cassandra/distributed/fuzz/QueryingNoOpChecker.java
deleted file mode 100644
index 77d19a2a7e..0000000000
--- a/test/distributed/org/apache/cassandra/distributed/fuzz/QueryingNoOpChecker.java
+++ /dev/null
@@ -1,66 +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.fuzz;
-
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonTypeName;
-import harry.core.Configuration;
-import harry.core.Run;
-import harry.model.Model;
-import harry.model.sut.SystemUnderTest;
-import harry.operations.CompiledStatement;
-import harry.operations.Query;
-
-public class QueryingNoOpChecker implements Model
-{
- public static void init()
- {
- Configuration.registerSubtypes(QueryingNoOpCheckerConfig.class);
- }
-
- private final Run run;
-
- public QueryingNoOpChecker(Run run)
- {
- this.run = run;
- }
-
- @Override
- public void validate(Query query)
- {
- CompiledStatement compiled = query.toSelectStatement();
- run.sut.execute(compiled.cql(),
- SystemUnderTest.ConsistencyLevel.ALL,
- compiled.bindings());
- }
-
- @JsonTypeName("querying_no_op_checker")
- public static class QueryingNoOpCheckerConfig implements Configuration.ModelConfiguration
- {
- @JsonCreator
- public QueryingNoOpCheckerConfig()
- {
- }
-
- public Model make(Run run)
- {
- return new QueryingNoOpChecker(run);
- }
- }
-}
diff --git a/test/distributed/org/apache/cassandra/distributed/fuzz/SSTableGenerator.java b/test/distributed/org/apache/cassandra/distributed/fuzz/SSTableGenerator.java
deleted file mode 100644
index 54fe2303ad..0000000000
--- a/test/distributed/org/apache/cassandra/distributed/fuzz/SSTableGenerator.java
+++ /dev/null
@@ -1,363 +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.fuzz;
-
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.SortedSet;
-
-import harry.core.Run;
-import harry.ddl.SchemaSpec;
-import harry.model.OpSelectors;
-import harry.operations.Relation;
-import harry.operations.Query;
-import harry.operations.QueryGenerator;
-import harry.util.BitSet;
-import org.apache.cassandra.Util;
-import org.apache.cassandra.cql3.AbstractMarker;
-import org.apache.cassandra.cql3.ColumnIdentifier;
-import org.apache.cassandra.cql3.Operator;
-import org.apache.cassandra.cql3.QueryOptions;
-import org.apache.cassandra.cql3.SingleColumnRelation;
-import org.apache.cassandra.cql3.VariableSpecifications;
-import org.apache.cassandra.cql3.WhereClause;
-import org.apache.cassandra.cql3.restrictions.StatementRestrictions;
-import org.apache.cassandra.cql3.statements.Bound;
-import org.apache.cassandra.cql3.statements.DeleteStatement;
-import org.apache.cassandra.cql3.statements.StatementType;
-import org.apache.cassandra.db.ClusteringBound;
-import org.apache.cassandra.db.ColumnFamilyStore;
-import org.apache.cassandra.db.ConsistencyLevel;
-import org.apache.cassandra.db.DecoratedKey;
-import org.apache.cassandra.db.DeletionTime;
-import org.apache.cassandra.db.Mutation;
-import org.apache.cassandra.db.RangeTombstone;
-import org.apache.cassandra.db.RowUpdateBuilder;
-import org.apache.cassandra.db.Slices;
-import org.apache.cassandra.db.marshal.ByteBufferAccessor;
-import org.apache.cassandra.db.marshal.CompositeType;
-import org.apache.cassandra.db.partitions.PartitionUpdate;
-import org.apache.cassandra.io.sstable.format.SSTableReader;
-import org.apache.cassandra.schema.ColumnMetadata;
-import org.apache.cassandra.schema.TableMetadata;
-import org.apache.cassandra.utils.ByteBufferUtil;
-import org.apache.cassandra.utils.FBUtilities;
-
-import static harry.generators.DataGenerators.UNSET_VALUE;
-
-public class SSTableGenerator
-{
- protected final SchemaSpec schema;
- protected final OpSelectors.DescriptorSelector descriptorSelector;
- protected final OpSelectors.MonotonicClock clock;
- protected final ColumnFamilyStore store;
- protected final TableMetadata metadata;
- protected final QueryGenerator rangeSelector;
-
- private final Set<SSTableReader> sstables = new HashSet<>();
-
- private long lts = 0;
-
- public SSTableGenerator(Run run,
- ColumnFamilyStore store)
- {
- this.schema = run.schemaSpec;
- this.descriptorSelector = run.descriptorSelector;
- this.clock = run.clock;
- this.store = store;
- // We assume metadata can not change over the lifetime of sstable generator
- this.metadata = store.metadata.get();
- this.rangeSelector = new QueryGenerator(schema, run.pdSelector, descriptorSelector, run.rng);
- store.disableAutoCompaction();
- }
-
- public Collection<SSTableReader> gen(int rows)
- {
- mark();
- for (int i = 0; i < rows; i++)
- {
- long current = lts++;
- write(current, current, current, current, true).applyUnsafe();
- if (schema.staticColumns != null)
- writeStatic(current, current, current, current, true).applyUnsafe();
- }
-
- return flush();
- }
-
- public void mark()
- {
- sstables.clear();
- sstables.addAll(store.getLiveSSTables());
- }
-
- public Collection<SSTableReader> flush()
- {
- Util.flush(store);
- sstables.removeAll(store.getLiveSSTables());
-
- Set<SSTableReader> ret = new HashSet<>(sstables);
- mark();
- return ret;
- }
-
- public Mutation write(long lts, long pd, long cd, long opId, boolean withRowMarker)
- {
- long[] vds = descriptorSelector.vds(pd, cd, lts, opId, OpSelectors.OperationKind.INSERT, schema);
-
- Object[] partitionKey = schema.inflatePartitionKey(pd);
- Object[] clusteringKey = schema.inflateClusteringKey(cd);
- Object[] regularColumns = schema.inflateRegularColumns(vds);
-
- RowUpdateBuilder builder = new RowUpdateBuilder(metadata,
- FBUtilities.nowInSeconds(),
- clock.rts(lts),
- metadata.params.defaultTimeToLive,
- serializePartitionKey(store, partitionKey))
- .clustering(clusteringKey);
-
- if (!withRowMarker)
- builder.noRowMarker();
-
- for (int i = 0; i < regularColumns.length; i++)
- {
- Object value = regularColumns[i];
- if (value == UNSET_VALUE)
- continue;
-
- ColumnMetadata def = metadata.getColumn(new ColumnIdentifier(schema.regularColumns.get(i).name, false));
- builder.add(def, value);
- }
-
- return builder.build();
- }
-
- public Mutation writeStatic(long lts, long pd, long cd, long opId, boolean withRowMarker)
- {
- long[] sds = descriptorSelector.sds(pd, cd, lts, opId, OpSelectors.OperationKind.INSERT_WITH_STATICS, schema);
-
- Object[] partitionKey = schema.inflatePartitionKey(pd);
- Object[] staticColumns = schema.inflateStaticColumns(sds);
-
- RowUpdateBuilder builder = new RowUpdateBuilder(metadata,
- FBUtilities.nowInSeconds(),
- clock.rts(lts),
- metadata.params.defaultTimeToLive,
- serializePartitionKey(store, partitionKey));
-
- if (!withRowMarker)
- builder.noRowMarker();
-
- for (int i = 0; i < staticColumns.length; i++)
- {
- Object value = staticColumns[i];
- if (value == UNSET_VALUE)
- continue;
- ColumnMetadata def = metadata.getColumn(new ColumnIdentifier(schema.staticColumns.get(i).name, false));
- builder.add(def, staticColumns[i]);
- }
-
- return builder.build();
- }
-
- public Mutation deleteColumn(long lts, long pd, long cd, long opId)
- {
- Object[] partitionKey = schema.inflatePartitionKey(pd);
- Object[] clusteringKey = schema.inflateClusteringKey(cd);
-
- RowUpdateBuilder builder = new RowUpdateBuilder(metadata,
- FBUtilities.nowInSeconds(),
- clock.rts(lts),
- metadata.params.defaultTimeToLive,
- serializePartitionKey(store, partitionKey))
- .noRowMarker()
- .clustering(clusteringKey);
-
- BitSet columns = descriptorSelector.columnMask(pd, lts, opId, OpSelectors.OperationKind.DELETE_COLUMN);
- BitSet mask = schema.regularColumnsMask();
-
- if (columns == null || columns.allUnset(mask))
- throw new IllegalArgumentException("Can't have a delete column query with no columns set. Column mask: " + columns);
-
- columns.eachSetBit((idx) -> {
- if (idx < schema.regularColumnsOffset)
- throw new RuntimeException("Can't delete parts of partition or clustering key");
-
- if (idx > schema.allColumns.size())
- throw new IndexOutOfBoundsException(String.format("Index %d is out of bounds. Max index: %d", idx, schema.allColumns.size()));
-
- builder.delete(schema.allColumns.get(idx).name);
- }, mask);
-
- return builder.build();
- }
-
- public Mutation deleteStatic(long lts, long pd, long opId)
- {
- Object[] partitionKey = schema.inflatePartitionKey(pd);
-
- RowUpdateBuilder builder = new RowUpdateBuilder(metadata,
- FBUtilities.nowInSeconds(),
- clock.rts(lts),
- metadata.params.defaultTimeToLive,
- serializePartitionKey(store, partitionKey))
- .noRowMarker();
-
-
- BitSet columns = descriptorSelector.columnMask(pd, lts, opId, OpSelectors.OperationKind.DELETE_COLUMN_WITH_STATICS);
- BitSet mask = schema.staticColumnsMask();
-
- if (columns == null || columns.allUnset(mask))
- throw new IllegalArgumentException("Can't have a delete column query with no columns set. Column mask: " + columns);
-
- columns.eachSetBit((idx) -> {
- if (idx < schema.staticColumnsOffset)
- throw new RuntimeException(String.format("Can't delete parts of partition or clustering key %d (%s)",
- idx, schema.allColumns.get(idx)));
-
- if (idx > schema.allColumns.size())
- throw new IndexOutOfBoundsException(String.format("Index %d is out of bounds. Max index: %d", idx, schema.allColumns.size()));
-
- builder.delete(schema.allColumns.get(idx).name);
- }, mask);
-
- return builder.build();
- }
-
- public Mutation deletePartition(long lts, long pd)
- {
- Object[] partitionKey = schema.inflatePartitionKey(pd);
-
- PartitionUpdate update = PartitionUpdate.fullPartitionDelete(metadata,
- serializePartitionKey(store, partitionKey),
- clock.rts(lts),
- FBUtilities.nowInSeconds());
-
- return new Mutation(update);
- }
-
- public Mutation deleteRow(long lts, long pd, long cd)
- {
- Object[] partitionKey = schema.inflatePartitionKey(pd);
- Object[] clusteringKey = schema.inflateClusteringKey(cd);
-
- return RowUpdateBuilder.deleteRow(metadata,
- clock.rts(lts),
- serializePartitionKey(store, partitionKey),
- clusteringKey);
- }
-
- public Mutation deleteSlice(long lts, long pd, long opId)
- {
- return delete(lts, pd, rangeSelector.inflate(lts, opId, Query.QueryKind.CLUSTERING_SLICE));
- }
-
- public Mutation deleteRange(long lts, long pd, long opId)
- {
- return delete(lts, pd, rangeSelector.inflate(lts, opId, Query.QueryKind.CLUSTERING_RANGE));
- }
-
- Mutation delete(long lts, long pd, Query query)
- {
- Object[] partitionKey = schema.inflatePartitionKey(pd);
- WhereClause.Builder builder = new WhereClause.Builder();
- List<ColumnIdentifier> variableNames = new ArrayList<>();
-
- List<ByteBuffer> values = new ArrayList<>();
- for (int i = 0; i < partitionKey.length; i++)
- {
- String name = schema.partitionKeys.get(i).name;
- ColumnMetadata columnDef = metadata.getColumn(ByteBufferUtil.bytes(name));
- variableNames.add(columnDef.name);
- values.add(ByteBufferUtil.objectToBytes(partitionKey[i]));
- builder.add(new SingleColumnRelation(ColumnIdentifier.getInterned(name, true),
- toOperator(Relation.RelationKind.EQ),
- new AbstractMarker.Raw(values.size() - 1)));
- }
-
- for (Relation relation : query.relations)
- {
- String name = relation.column();
- ColumnMetadata columnDef = metadata.getColumn(ByteBufferUtil.bytes(relation.column()));
- variableNames.add(columnDef.name);
- values.add(ByteBufferUtil.objectToBytes(relation.value()));
- builder.add(new SingleColumnRelation(ColumnIdentifier.getInterned(name, false),
- toOperator(relation.kind),
- new AbstractMarker.Raw(values.size() - 1)));
- }
-
- StatementRestrictions restrictions = new StatementRestrictions(null,
- StatementType.DELETE,
- metadata,
- builder.build(),
- new VariableSpecifications(variableNames),
- Collections.emptyList(),
- false,
- false,
- false,
- false);
-
- QueryOptions options = QueryOptions.forInternalCalls(ConsistencyLevel.QUORUM, values);
- SortedSet<ClusteringBound<?>> startBounds = restrictions.getClusteringColumnsBounds(Bound.START, options);
- SortedSet<ClusteringBound<?>> endBounds = restrictions.getClusteringColumnsBounds(Bound.END, options);
-
- Slices slices = DeleteStatement.toSlices(metadata, startBounds, endBounds);
- assert slices.size() == 1;
- long deletionTime = FBUtilities.nowInSeconds();
- long rts = clock.rts(lts);
-
- return new RowUpdateBuilder(metadata,
- deletionTime,
- rts,
- metadata.params.defaultTimeToLive,
- serializePartitionKey(store, partitionKey))
- .noRowMarker()
- .addRangeTombstone(new RangeTombstone(slices.get(0), DeletionTime.build(rts, deletionTime)))
- .build();
- }
-
- public static Operator toOperator(Relation.RelationKind kind)
- {
- switch (kind)
- {
- case LT: return Operator.LT;
- case GT: return Operator.GT;
- case LTE: return Operator.LTE;
- case GTE: return Operator.GTE;
- case EQ: return Operator.EQ;
- default: throw new IllegalArgumentException("Unsupported " + kind);
- }
- }
-
- public static DecoratedKey serializePartitionKey(ColumnFamilyStore store, Object... pk)
- {
- if (pk.length == 1)
- return store.getPartitioner().decorateKey(ByteBufferUtil.objectToBytes(pk[0]));
-
- ByteBuffer[] values = new ByteBuffer[pk.length];
- for (int i = 0; i < pk.length; i++)
- values[i] = ByteBufferUtil.objectToBytes(pk[i]);
- return store.getPartitioner().decorateKey(CompositeType.build(ByteBufferAccessor.instance, values));
- }
-}
diff --git a/test/distributed/org/apache/cassandra/distributed/fuzz/SSTableLoadingVisitor.java b/test/distributed/org/apache/cassandra/distributed/fuzz/SSTableLoadingVisitor.java
deleted file mode 100644
index ce6ef57aff..0000000000
--- a/test/distributed/org/apache/cassandra/distributed/fuzz/SSTableLoadingVisitor.java
+++ /dev/null
@@ -1,113 +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.fuzz;
-
-import harry.core.Run;
-import harry.model.OpSelectors;
-import harry.visitors.VisitExecutor;
-import org.apache.cassandra.db.Keyspace;
-
-public class SSTableLoadingVisitor extends VisitExecutor
-{
- private final SSTableGenerator gen;
- private final int forceFlushAfter;
-
- public SSTableLoadingVisitor(Run run, int forceFlushAfter)
- {
- this.forceFlushAfter = forceFlushAfter;
- gen = new SSTableGenerator(run, Keyspace.open(run.schemaSpec.keyspace).getColumnFamilyStore(run.schemaSpec.table));
- gen.mark();
- }
-
- @Override
- protected void beforeLts(long l, long l1)
- {
-
- }
-
- protected void afterLts(long lts, long pd) {
- if (lts > 0 && lts % forceFlushAfter == 0)
- forceFlush();
- }
-
- @Override
- protected void beforeBatch(long l, long l1, long l2)
- {
-
- }
-
- @Override
- protected void afterBatch(long l, long l1, long l2)
- {
-
- }
-
- @Override
- protected void operation(long lts, long pd, long cd, long m, long opId, OpSelectors.OperationKind operationKind)
- {
- switch (operationKind)
- {
- case INSERT:
- gen.write(lts, pd, cd, opId, true).applyUnsafe();
- break;
- case INSERT_WITH_STATICS:
- gen.write(lts, pd, cd, opId, true).applyUnsafe();
- gen.writeStatic(lts, pd, cd, opId, true).applyUnsafe();
- break;
- case UPDATE:
- gen.write(lts, pd, cd, opId, false).applyUnsafe();
- break;
- case UPDATE_WITH_STATICS:
- gen.write(lts, pd, cd, opId, false).applyUnsafe();
- gen.writeStatic(lts, pd, cd, opId, false).applyUnsafe();
- break;
- case DELETE_PARTITION:
- gen.deletePartition(lts, pd).applyUnsafe();
- break;
- case DELETE_ROW:
- gen.deleteRow(lts, pd, cd).applyUnsafe();
- break;
- case DELETE_COLUMN:
- gen.deleteColumn(lts, pd, cd, opId).applyUnsafe();
- break;
- case DELETE_COLUMN_WITH_STATICS:
- gen.deleteColumn(lts, pd, cd, opId).applyUnsafe();
- gen.deleteStatic(lts, pd, opId).applyUnsafe();
- break;
- case DELETE_RANGE:
- gen.deleteRange(lts, pd, opId).applyUnsafe();
- break;
- case DELETE_SLICE:
- gen.deleteSlice(lts, pd, opId).applyUnsafe();
- break;
- }
- }
-
- public void forceFlush()
- {
- gen.flush();
- }
-
-
- @Override
- public void shutdown() throws InterruptedException
- {
-
- }
-}
diff --git a/test/distributed/org/apache/cassandra/distributed/fuzz/test/SSTableGeneratorTest.java b/test/distributed/org/apache/cassandra/distributed/fuzz/test/SSTableGeneratorTest.java
deleted file mode 100644
index 5050bc68ff..0000000000
--- a/test/distributed/org/apache/cassandra/distributed/fuzz/test/SSTableGeneratorTest.java
+++ /dev/null
@@ -1,131 +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.fuzz.test;
-
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.CompletableFuture;
-
-import com.google.common.collect.Iterators;
-import org.junit.Test;
-
-import harry.core.Configuration;
-import harry.core.Run;
-import harry.ddl.ColumnSpec;
-import harry.ddl.SchemaSpec;
-import harry.model.Model;
-import harry.model.QuiescentChecker;
-import harry.model.clock.OffsetClock;
-import harry.model.sut.SystemUnderTest;
-import harry.operations.Query;
-import harry.visitors.GeneratingVisitor;
-import harry.visitors.LtsVisitor;
-import org.apache.cassandra.cql3.CQLTester;
-import org.apache.cassandra.distributed.fuzz.FixedSchemaProviderConfiguration;
-import org.apache.cassandra.distributed.fuzz.HarryHelper;
-import org.apache.cassandra.distributed.fuzz.SSTableLoadingVisitor;
-import org.apache.cassandra.distributed.impl.RowUtil;
-
-public class SSTableGeneratorTest extends CQLTester
-{
- private static final Configuration configuration;
-
- static
- {
- try
- {
- HarryHelper.init();
- configuration = HarryHelper.defaultConfiguration()
- .setClock(() -> new OffsetClock(10000L))
- .build();
- }
- catch (Exception e)
- {
- throw new RuntimeException(e);
- }
- }
-
- private static SchemaSpec schemaSpec = new SchemaSpec(KEYSPACE, "tbl1",
- Arrays.asList(ColumnSpec.pk("pk1", ColumnSpec.asciiType),
- ColumnSpec.pk("pk2", ColumnSpec.int64Type)),
- Arrays.asList(ColumnSpec.ck("ck1", ColumnSpec.asciiType, false),
- ColumnSpec.ck("ck2", ColumnSpec.int64Type, false)),
- Arrays.asList(ColumnSpec.regularColumn("v1", ColumnSpec.int32Type),
- ColumnSpec.regularColumn("v2", ColumnSpec.int64Type),
- ColumnSpec.regularColumn("v3", ColumnSpec.int32Type),
- ColumnSpec.regularColumn("v4", ColumnSpec.asciiType)),
- Arrays.asList(ColumnSpec.staticColumn("s1", ColumnSpec.asciiType),
- ColumnSpec.staticColumn("s2", ColumnSpec.int64Type)));
-
- @Test
- public void testSSTableGenerator()
- {
- createTable(schemaSpec.compile().cql());
- Run run = configuration.unbuild()
- .setSchemaProvider(new FixedSchemaProviderConfiguration(schemaSpec))
- .setSUT(CqlTesterSUT::new)
- .build()
- .createRun();
-
-
- SSTableLoadingVisitor sstableVisitor = new SSTableLoadingVisitor(run, 1000);
- LtsVisitor visitor = new GeneratingVisitor(run, sstableVisitor);
- Set<Long> pds = new HashSet<>();
- run.tracker.onLtsStarted((lts) -> pds.add(run.pdSelector.pd(lts, run.schemaSpec)));
- for (int i = 0; i < 1000; i++)
- visitor.visit();
-
- sstableVisitor.forceFlush();
-
- Model checker = new QuiescentChecker(run);
- for (Long pd : pds)
- checker.validate(Query.selectPartition(run.schemaSpec, pd, false));
- }
-
- public class CqlTesterSUT implements SystemUnderTest
- {
- public boolean isShutdown()
- {
- return false;
- }
-
- public void shutdown()
- {
- }
-
- public CompletableFuture<Object[][]> executeAsync(String s, ConsistencyLevel consistencyLevel, Object... objects)
- {
- throw new RuntimeException("Not implemented");
- }
-
- public Object[][] execute(String s, ConsistencyLevel consistencyLevel, Object... objects)
- {
- try
- {
- return Iterators.toArray(RowUtil.toIter(SSTableGeneratorTest.this.execute(s, objects)),
- Object[].class);
- }
- catch (Throwable throwable)
- {
- throw new RuntimeException();
- }
- }
- }
-}
diff --git a/test/distributed/org/apache/cassandra/distributed/shared/ClusterUtils.java b/test/distributed/org/apache/cassandra/distributed/shared/ClusterUtils.java
index ac8aea6591..afb782f5a5 100644
--- a/test/distributed/org/apache/cassandra/distributed/shared/ClusterUtils.java
+++ b/test/distributed/org/apache/cassandra/distributed/shared/ClusterUtils.java
@@ -42,14 +42,13 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.Futures;
-
import org.junit.Assert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import harry.core.VisibleForTesting;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.distributed.api.ConsistencyLevel;
import org.apache.cassandra.distributed.api.Feature;
@@ -61,10 +60,10 @@ import org.apache.cassandra.distributed.api.IMessageFilters;
import org.apache.cassandra.distributed.api.NodeToolResult;
import org.apache.cassandra.distributed.impl.AbstractCluster;
import org.apache.cassandra.distributed.impl.InstanceConfig;
-import org.apache.cassandra.gms.ApplicationState;
-import org.apache.cassandra.gms.VersionedValue;
import org.apache.cassandra.distributed.impl.TestChangeListener;
import org.apache.cassandra.distributed.test.log.TestProcessor;
+import org.apache.cassandra.gms.ApplicationState;
+import org.apache.cassandra.gms.VersionedValue;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.net.Message;
diff --git a/test/distributed/org/apache/cassandra/distributed/test/log/BounceGossipTest.java b/test/distributed/org/apache/cassandra/distributed/test/log/BounceGossipTest.java
index f75269c540..8c188a2fec 100644
--- a/test/distributed/org/apache/cassandra/distributed/test/log/BounceGossipTest.java
+++ b/test/distributed/org/apache/cassandra/distributed/test/log/BounceGossipTest.java
@@ -27,34 +27,35 @@ import java.util.concurrent.atomic.AtomicInteger;
import com.google.common.util.concurrent.Uninterruptibles;
import org.junit.Test;
-import harry.core.Configuration;
-import harry.ddl.SchemaSpec;
-import harry.visitors.MutatingVisitor;
-import harry.visitors.QueryLogger;
-import harry.visitors.RandomPartitionValidator;
import org.apache.cassandra.distributed.Cluster;
import org.apache.cassandra.distributed.Constants;
-import org.apache.cassandra.distributed.harry.ClusterState;
-import org.apache.cassandra.distributed.harry.ExistingClusterSUT;
-import org.apache.cassandra.distributed.harry.FlaggedRunner;
+import org.apache.cassandra.harry.HarryHelper;
+import org.apache.cassandra.harry.runner.FlaggedRunner;
import org.apache.cassandra.distributed.test.TestBaseImpl;
import org.apache.cassandra.gms.ApplicationState;
import org.apache.cassandra.gms.EndpointState;
import org.apache.cassandra.gms.Gossiper;
+import org.apache.cassandra.harry.core.Configuration;
+import org.apache.cassandra.harry.ddl.SchemaSpec;
+import org.apache.cassandra.harry.sut.injvm.InJvmSut;
+import org.apache.cassandra.harry.sut.injvm.InJvmSutBase;
+import org.apache.cassandra.harry.visitors.MutatingVisitor;
+import org.apache.cassandra.harry.visitors.QueryLogger;
+import org.apache.cassandra.harry.visitors.RandomPartitionValidator;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.utils.concurrent.CountDownLatch;
-import static harry.core.Configuration.VisitorPoolConfiguration.pool;
-import static harry.ddl.ColumnSpec.asciiType;
-import static harry.ddl.ColumnSpec.ck;
-import static harry.ddl.ColumnSpec.int64Type;
-import static harry.ddl.ColumnSpec.pk;
-import static harry.ddl.ColumnSpec.regularColumn;
-import static harry.ddl.ColumnSpec.staticColumn;
import static java.util.Arrays.asList;
import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory;
import static org.apache.cassandra.distributed.api.Feature.GOSSIP;
import static org.apache.cassandra.distributed.api.Feature.NETWORK;
+import static org.apache.cassandra.harry.core.Configuration.VisitorPoolConfiguration.pool;
+import static org.apache.cassandra.harry.ddl.ColumnSpec.asciiType;
+import static org.apache.cassandra.harry.ddl.ColumnSpec.ck;
+import static org.apache.cassandra.harry.ddl.ColumnSpec.int64Type;
+import static org.apache.cassandra.harry.ddl.ColumnSpec.pk;
+import static org.apache.cassandra.harry.ddl.ColumnSpec.regularColumn;
+import static org.apache.cassandra.harry.ddl.ColumnSpec.staticColumn;
import static org.apache.cassandra.utils.Clock.Global.currentTimeMillis;
import static org.junit.Assert.fail;
import static org.psjava.util.AssertStatus.assertTrue;
@@ -76,12 +77,10 @@ public class BounceGossipTest extends TestBaseImpl
asList(regularColumn("regular1", asciiType), regularColumn("regular1", int64Type)),
asList(staticColumn("static1", asciiType), staticColumn("static1", int64Type)));
AtomicInteger down = new AtomicInteger(0);
- ClusterState clusterState = (i) -> down.get() == i;
- Configuration config = Configuration.fromYamlString(createHarryConf())
- .unbuild()
- .setKeyspaceDdl(String.format("CREATE KEYSPACE IF NOT EXISTS %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor': %d};", schema.keyspace, 3))
- .setSUT(new ExistingClusterSUT(cluster, clusterState))
- .build();
+ Configuration config = HarryHelper.defaultConfiguration()
+ .setKeyspaceDdl(String.format("CREATE KEYSPACE IF NOT EXISTS %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor': %d};", schema.keyspace, 3))
+ .setSUT(() -> new InJvmSut(cluster, () -> 1, InJvmSutBase.retryOnTimeout()))
+ .build();
CountDownLatch stopLatch = CountDownLatch.newCountDownLatch(1);
Future<?> f = es.submit(() -> {
diff --git a/test/distributed/org/apache/cassandra/distributed/test/log/CMSTestBase.java b/test/distributed/org/apache/cassandra/distributed/test/log/CMSTestBase.java
index b262b66b00..eb78bc7e38 100644
--- a/test/distributed/org/apache/cassandra/distributed/test/log/CMSTestBase.java
+++ b/test/distributed/org/apache/cassandra/distributed/test/log/CMSTestBase.java
@@ -24,6 +24,7 @@ import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.guardrails.Guardrails;
import org.apache.cassandra.dht.Murmur3Partitioner;
import org.apache.cassandra.distributed.api.IIsolatedExecutor;
+import org.apache.cassandra.harry.sut.TokenPlacementModel;
import org.apache.cassandra.locator.IEndpointSnitch;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.locator.Replica;
@@ -87,9 +88,9 @@ public class CMSTestBase
public final LocalLog log;
public final ClusterMetadataService service;
public final SchemaProvider schemaProvider;
- public final PlacementSimulator.ReplicationFactor rf;
+ public final TokenPlacementModel.ReplicationFactor rf;
- public CMSSut(IIsolatedExecutor.SerializableFunction<LocalLog, Processor> processorFactory, boolean addListeners, PlacementSimulator.ReplicationFactor rf)
+ public CMSSut(IIsolatedExecutor.SerializableFunction<LocalLog, Processor> processorFactory, boolean addListeners, TokenPlacementModel.ReplicationFactor rf)
{
partitioner = Murmur3Partitioner.instance;
this.rf = rf;
diff --git a/test/distributed/org/apache/cassandra/distributed/test/log/CoordinatorPathTest.java b/test/distributed/org/apache/cassandra/distributed/test/log/CoordinatorPathTest.java
index 398fd4ffa7..ce5c5800d4 100644
--- a/test/distributed/org/apache/cassandra/distributed/test/log/CoordinatorPathTest.java
+++ b/test/distributed/org/apache/cassandra/distributed/test/log/CoordinatorPathTest.java
@@ -30,40 +30,36 @@ import java.util.stream.Collectors;
import org.junit.Assert;
import org.junit.Test;
-import harry.core.Configuration;
-import harry.core.Run;
-import harry.model.OpSelectors;
-import harry.operations.CompiledStatement;
-import harry.operations.WriteHelper;
-import harry.util.ByteUtils;
-import harry.util.TokenUtil;
+import org.apache.cassandra.harry.core.Configuration;
+import org.apache.cassandra.harry.core.Run;
+import org.apache.cassandra.harry.model.OpSelectors;
+import org.apache.cassandra.harry.operations.CompiledStatement;
+import org.apache.cassandra.harry.operations.WriteHelper;
+import org.apache.cassandra.harry.sut.injvm.InJvmSut;
+import org.apache.cassandra.harry.util.ByteUtils;
+import org.apache.cassandra.harry.util.TokenUtil;
import org.apache.cassandra.db.Mutation;
import org.apache.cassandra.db.ReadCommand;
import org.apache.cassandra.db.ReadResponse;
import org.apache.cassandra.distributed.api.ConsistencyLevel;
-import org.apache.cassandra.distributed.fuzz.HarryHelper;
-import org.apache.cassandra.distributed.fuzz.InJvmSut;
+import org.apache.cassandra.harry.HarryHelper;
+import org.apache.cassandra.harry.sut.TokenPlacementModel;
+import org.apache.cassandra.harry.sut.TokenPlacementModel.Node;
import org.apache.cassandra.net.Message;
import org.apache.cassandra.net.NoPayload;
import org.apache.cassandra.net.Verb;
import org.apache.cassandra.utils.concurrent.Future;
-import static org.apache.cassandra.distributed.test.log.PlacementSimulator.Node;
-
-
public class CoordinatorPathTest extends CoordinatorPathTestBase
{
- private static final PlacementSimulator.SimpleReplicationFactor RF = new PlacementSimulator.SimpleReplicationFactor(3);
+ private static final TokenPlacementModel.SimpleReplicationFactor RF = new TokenPlacementModel.SimpleReplicationFactor(3);
@Test
public void writeConsistencyTest() throws Throwable
{
- Configuration.ConfigurationBuilder configBuilder = HarryHelper.defaultConfiguration()
- .setPartitionDescriptorSelector(new Configuration.DefaultPDSelectorConfiguration(1, 1))
- .setClusteringDescriptorSelector(HarryHelper.singleRowPerModification().build());
-
coordinatorPathTest(RF, (cluster, simulatedCluster) -> {
- configBuilder.setSUT(() -> new InJvmSut(cluster));
+ Configuration.ConfigurationBuilder configBuilder = HarryHelper.defaultConfiguration()
+ .setSUT(() -> new InJvmSut(cluster));
Run run = configBuilder.build().createRun();
for (int ignored : new int[]{ 2, 3, 4, 5 })
diff --git a/test/distributed/org/apache/cassandra/distributed/test/log/CoordinatorPathTestBase.java b/test/distributed/org/apache/cassandra/distributed/test/log/CoordinatorPathTestBase.java
index 7f4fc82b32..8c41d17ddc 100644
--- a/test/distributed/org/apache/cassandra/distributed/test/log/CoordinatorPathTestBase.java
+++ b/test/distributed/org/apache/cassandra/distributed/test/log/CoordinatorPathTestBase.java
@@ -42,8 +42,6 @@ import org.junit.Assert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import harry.util.ByteUtils;
-import harry.util.TestRunner;
import org.apache.cassandra.ServerTestUtils;
import org.apache.cassandra.concurrent.ExecutorFactory;
import org.apache.cassandra.concurrent.ExecutorPlus;
@@ -66,6 +64,11 @@ import org.apache.cassandra.distributed.shared.NetworkTopology;
import org.apache.cassandra.distributed.test.log.PlacementSimulator.Transformations;
import org.apache.cassandra.gms.GossipDigestAck;
import org.apache.cassandra.gms.GossipDigestSyn;
+import org.apache.cassandra.harry.sut.TokenPlacementModel;
+import org.apache.cassandra.harry.sut.TokenPlacementModel.Node;
+import org.apache.cassandra.harry.sut.TokenPlacementModel.NodeFactory;
+import org.apache.cassandra.harry.util.ByteUtils;
+import org.apache.cassandra.harry.util.TestRunner;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.locator.SimpleSeedProvider;
import org.apache.cassandra.tcm.*;
@@ -91,7 +94,6 @@ import org.apache.cassandra.utils.concurrent.AsyncPromise;
import org.apache.cassandra.utils.concurrent.CountDownLatch;
import org.apache.cassandra.utils.concurrent.Future;
-import static org.apache.cassandra.distributed.test.log.PlacementSimulator.Node;
import static org.apache.cassandra.distributed.test.log.PlacementSimulator.RefSimulatedPlacementHolder;
import static org.apache.cassandra.distributed.test.log.PlacementSimulator.SimulatedPlacementHolder;
import static org.apache.cassandra.distributed.test.log.PlacementSimulator.SimulatedPlacements;
@@ -102,7 +104,7 @@ public abstract class CoordinatorPathTestBase extends FuzzTestBase
{
private static final Logger logger = LoggerFactory.getLogger(CoordinatorPathTestBase.class);
- public void coordinatorPathTest(PlacementSimulator.ReplicationFactor rf, TestRunner.ThrowingBiConsumer<Cluster, SimulatedCluster> test) throws Throwable
+ public void coordinatorPathTest(TokenPlacementModel.ReplicationFactor rf, TestRunner.ThrowingBiConsumer<Cluster, SimulatedCluster> test) throws Throwable
{
coordinatorPathTest(rf, test, true);
}
@@ -112,7 +114,7 @@ public abstract class CoordinatorPathTestBase extends FuzzTestBase
*
* CMS node is 127.0.0.10 and is simulated.
*/
- public void coordinatorPathTest(PlacementSimulator.ReplicationFactor rf, TestRunner.ThrowingBiConsumer<Cluster, SimulatedCluster> test, boolean startCluster) throws Throwable
+ public void coordinatorPathTest(TokenPlacementModel.ReplicationFactor rf, TestRunner.ThrowingBiConsumer<Cluster, SimulatedCluster> test, boolean startCluster) throws Throwable
{
ServerTestUtils.daemonInitialization();
DatabaseDescriptor.setPartitionerUnsafe(Murmur3Partitioner.instance);
@@ -120,8 +122,8 @@ public abstract class CoordinatorPathTestBase extends FuzzTestBase
String nodeUnderTest = "127.0.0.1";
InetAddressAndPort nodeUnderTestAddr = InetAddressAndPort.getByName(nodeUnderTest + ":7012");
- PlacementSimulator.NodeFactory factory = PlacementSimulator.nodeFactory();
- Node fakeCmsNode = factory.make(10,1,1);
+ NodeFactory factory = TokenPlacementModel.nodeFactory();
+ Node fakeCmsNode = factory.make(10, 1, 1);
FBUtilities.setBroadcastInetAddressAndPort(fakeCmsNode.addr());
try (Cluster cluster = builder().withNodes(1)
@@ -153,7 +155,7 @@ public abstract class CoordinatorPathTestBase extends FuzzTestBase
.withTokenSupplier(tokenSupplier)
.withNodeIdTopology(NetworkTopology.singleDcNetworkTopology(10, "dc0", "rack0"))
.createWithoutStarting();
- SimulatedCluster simulatedCluster = new SimulatedCluster(new PlacementSimulator.SimpleReplicationFactor(3),
+ SimulatedCluster simulatedCluster = new SimulatedCluster(new TokenPlacementModel.SimpleReplicationFactor(3),
cluster, tokenSupplier))
{
// Note that in case of a CMS node teset we first start a cluster, and then initialize a simulated cluster.
@@ -607,9 +609,9 @@ public abstract class CoordinatorPathTestBase extends FuzzTestBase
protected final TokenSupplier tokenSupplier;
protected final IPartitioner partitioner = Murmur3Partitioner.instance;
protected ExecutorPlus executor;
- public final PlacementSimulator.NodeFactory nodeFactory = PlacementSimulator.nodeFactory();
+ public final NodeFactory nodeFactory = TokenPlacementModel.nodeFactory();
- public SimulatedCluster(PlacementSimulator.ReplicationFactor rf, ICluster<IInvokableInstance> realCluster, TokenSupplier tokenSupplier)
+ public SimulatedCluster(TokenPlacementModel.ReplicationFactor rf, ICluster<IInvokableInstance> realCluster, TokenSupplier tokenSupplier)
{
this.tokenSupplier = tokenSupplier;
@@ -845,9 +847,9 @@ public abstract class CoordinatorPathTestBase extends FuzzTestBase
{
protected final SimulatedPlacementHolder state;
protected final List<VirtualSimulatedNode> nodes;
- protected final PlacementSimulator.NodeFactory factory;
+ protected final NodeFactory factory;
- public VirtualSimulatedCluster(SimulatedPlacementHolder state, PlacementSimulator.NodeFactory factory)
+ public VirtualSimulatedCluster(SimulatedPlacementHolder state, NodeFactory factory)
{
this.state = state;
this.factory = factory;
diff --git a/test/distributed/org/apache/cassandra/distributed/test/log/FailedLeaveTest.java b/test/distributed/org/apache/cassandra/distributed/test/log/FailedLeaveTest.java
index 52aec24c0b..5dadb0ed01 100644
--- a/test/distributed/org/apache/cassandra/distributed/test/log/FailedLeaveTest.java
+++ b/test/distributed/org/apache/cassandra/distributed/test/log/FailedLeaveTest.java
@@ -28,12 +28,16 @@ import java.util.function.BiFunction;
import org.junit.Assert;
import org.junit.Test;
-import harry.core.Configuration;
-import harry.core.Run;
-import harry.model.sut.SystemUnderTest;
-import harry.visitors.GeneratingVisitor;
-import harry.visitors.MutatingRowVisitor;
-import harry.visitors.Visitor;
+import org.apache.cassandra.harry.core.Configuration;
+import org.apache.cassandra.harry.core.Run;
+import org.apache.cassandra.harry.sut.SystemUnderTest;
+import org.apache.cassandra.harry.sut.TokenPlacementModel;
+import org.apache.cassandra.harry.sut.injvm.InJVMTokenAwareVisitExecutor;
+import org.apache.cassandra.harry.sut.injvm.InJvmSut;
+import org.apache.cassandra.harry.sut.injvm.QuiescentLocalStateChecker;
+import org.apache.cassandra.harry.visitors.GeneratingVisitor;
+import org.apache.cassandra.harry.visitors.MutatingRowVisitor;
+import org.apache.cassandra.harry.visitors.Visitor;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.implementation.MethodDelegation;
@@ -42,12 +46,9 @@ import org.apache.cassandra.distributed.Cluster;
import org.apache.cassandra.distributed.api.ConsistencyLevel;
import org.apache.cassandra.distributed.api.Feature;
import org.apache.cassandra.distributed.api.IInvokableInstance;
-import org.apache.cassandra.distributed.fuzz.HarryHelper;
-import org.apache.cassandra.distributed.fuzz.InJVMTokenAwareVisitorExecutor;
-import org.apache.cassandra.distributed.fuzz.InJvmSut;
+import org.apache.cassandra.harry.HarryHelper;
import org.apache.cassandra.distributed.shared.ClusterUtils;
import org.apache.cassandra.distributed.shared.ClusterUtils.SerializableBiPredicate;
-import org.apache.cassandra.locator.ReplicationFactor;
import org.apache.cassandra.tcm.Commit;
import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.tcm.Transformation;
@@ -89,11 +90,6 @@ public class FailedLeaveTest extends FuzzTestBase
throws Exception
{
ExecutorService es = Executors.newSingleThreadExecutor();
- Configuration.ConfigurationBuilder configBuilder = HarryHelper.defaultConfiguration()
- .setPartitionDescriptorSelector(new Configuration.DefaultPDSelectorConfiguration(1, 1))
- .setClusteringDescriptorSelector(HarryHelper.defaultClusteringDescriptorSelectorConfiguration().setMaxPartitionSize(100).build());
-
-
try (Cluster cluster = builder().withNodes(3)
.withInstanceInitializer(BB::install)
.appendConfig(c -> c.with(Feature.NETWORK))
@@ -101,7 +97,9 @@ public class FailedLeaveTest extends FuzzTestBase
{
IInvokableInstance cmsInstance = cluster.get(1);
IInvokableInstance leavingInstance = cluster.get(2);
- configBuilder.setSUT(() -> new InJvmSut(cluster));
+
+ Configuration.ConfigurationBuilder configBuilder = HarryHelper.defaultConfiguration()
+ .setSUT(() -> new InJvmSut(cluster));
Run run = configBuilder.build().createRun();
cluster.coordinator(1).execute("CREATE KEYSPACE " + run.schemaSpec.keyspace +
@@ -110,8 +108,12 @@ public class FailedLeaveTest extends FuzzTestBase
cluster.coordinator(1).execute(run.schemaSpec.compile().cql(), ConsistencyLevel.ALL);
ClusterUtils.waitForCMSToQuiesce(cluster, cmsInstance);
- QuiescentLocalStateChecker model = new QuiescentLocalStateChecker(run, ReplicationFactor.fullOnly(2));
- Visitor visitor = new GeneratingVisitor(run, new InJVMTokenAwareVisitorExecutor(run, MutatingRowVisitor::new, SystemUnderTest.ConsistencyLevel.ALL));
+ TokenPlacementModel.ReplicationFactor rf = new TokenPlacementModel.SimpleReplicationFactor(2);
+ QuiescentLocalStateChecker model = new QuiescentLocalStateChecker(run, rf);
+ Visitor visitor = new GeneratingVisitor(run, new InJVMTokenAwareVisitExecutor(run,
+ MutatingRowVisitor::new,
+ SystemUnderTest.ConsistencyLevel.ALL,
+ rf));
for (int i = 0; i < WRITES; i++)
visitor.visit();
diff --git a/test/distributed/org/apache/cassandra/distributed/test/log/FuzzTestBase.java b/test/distributed/org/apache/cassandra/distributed/test/log/FuzzTestBase.java
index 94df950c91..86e5f681e1 100644
--- a/test/distributed/org/apache/cassandra/distributed/test/log/FuzzTestBase.java
+++ b/test/distributed/org/apache/cassandra/distributed/test/log/FuzzTestBase.java
@@ -18,30 +18,13 @@
package org.apache.cassandra.distributed.test.log;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
import java.util.concurrent.TimeUnit;
import org.junit.BeforeClass;
-import harry.core.Run;
-import harry.data.ResultSetRow;
-import harry.model.OpSelectors;
-import harry.model.QuiescentChecker;
-import harry.model.sut.SystemUnderTest;
-import harry.operations.CompiledStatement;
-import harry.operations.Query;
import org.apache.cassandra.distributed.Cluster;
-import org.apache.cassandra.distributed.api.IIsolatedExecutor;
-import org.apache.cassandra.distributed.fuzz.HarryHelper;
-import org.apache.cassandra.distributed.fuzz.InJvmSut;
-import org.apache.cassandra.distributed.test.ExecUtil;
import org.apache.cassandra.distributed.test.TestBaseImpl;
-import org.apache.cassandra.locator.ReplicationFactor;
-
-import static harry.model.SelectHelper.resultSetToRow;
+import org.apache.cassandra.harry.HarryHelper;
public class FuzzTestBase extends TestBaseImpl
{
@@ -61,81 +44,4 @@ public class FuzzTestBase extends TestBaseImpl
// unpaused before event expiration.
.set("cms_await_timeout", String.format("%dms", TimeUnit.MINUTES.toMillis(10))));
}
-
- public static IIsolatedExecutor.SerializableRunnable toRunnable(ExecUtil.ThrowingSerializableRunnable runnable)
- {
- return () -> {
- try
- {
- runnable.run();
- }
- catch (Throwable t)
- {
- System.out.println(t.getMessage());
- t.printStackTrace();
- }
- };
- }
-
- public static class QuiescentLocalStateChecker extends QuiescentChecker
- {
- public final InJvmSut inJvmSut;
- public final ReplicationFactor rf;
- private final OpSelectors.PdSelector pdSelector;
-
- public QuiescentLocalStateChecker(Run run)
- {
- this(run, null);
- }
-
- public QuiescentLocalStateChecker(Run run, ReplicationFactor rf)
- {
- super(run);
- assert run.sut instanceof InJvmSut;
-
- this.inJvmSut = (InJvmSut) run.sut;
- this.rf = rf;
- this.pdSelector = run.pdSelector;
- }
-
- public void validateAll()
- {
- for (int lts = 0; lts < clock.peek(); lts++)
- validate(Query.selectPartition(schema, pdSelector.pd(lts, schema), false));
- }
-
- @Override
- public void validate(Query query)
- {
- CompiledStatement compiled = query.toSelectStatement();
- int[] replicas = inJvmSut.getReadReplicasFor(schema.inflatePartitionKey(query.pd), schema.keyspace, schema.table);
- if (rf != null && replicas.length != rf.allReplicas)
- throw new IllegalStateException(String.format("Total number of replicas %d does not match expectation %d",
- replicas.length, rf.allReplicas));
-
- for (int node : replicas)
- {
- try
- {
- validate(() -> {
- Object[][] objects = inJvmSut.execute(compiled.cql(),
- SystemUnderTest.ConsistencyLevel.NODE_LOCAL,
- node,
- compiled.bindings());
- List<ResultSetRow> result = new ArrayList<>();
- for (Object[] obj : objects)
- result.add(resultSetToRow(query.schemaSpec, clock, obj));
-
- return result;
- }, query);
- }
- catch (ValidationException e)
- {
- throw new AssertionError(String.format("Caught error while validating replica %d of replica set %s",
- node, Arrays.toString(replicas)),
- e);
- }
- }
- }
- }
}
diff --git a/test/distributed/org/apache/cassandra/distributed/test/log/MetadataChangeSimulationTest.java b/test/distributed/org/apache/cassandra/distributed/test/log/MetadataChangeSimulationTest.java
index 857db9d8ba..036e5fff71 100644
--- a/test/distributed/org/apache/cassandra/distributed/test/log/MetadataChangeSimulationTest.java
+++ b/test/distributed/org/apache/cassandra/distributed/test/log/MetadataChangeSimulationTest.java
@@ -44,7 +44,7 @@ import org.apache.cassandra.dht.Murmur3Partitioner;
import org.apache.cassandra.dht.Murmur3Partitioner.LongToken;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token;
-import org.apache.cassandra.distributed.test.log.PlacementSimulator.SimulatedPlacements;
+import org.apache.cassandra.harry.sut.TokenPlacementModel;
import org.apache.cassandra.locator.EndpointsForRange;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.locator.CMSPlacementStrategy;
@@ -65,8 +65,8 @@ import org.apache.cassandra.tcm.ownership.VersionedEndpoints;
import org.apache.cassandra.tcm.transformations.Register;
import org.apache.cassandra.tcm.transformations.SealPeriod;
-import static org.apache.cassandra.distributed.test.log.PlacementSimulator.Node;
-import static org.apache.cassandra.distributed.test.log.PlacementSimulator.nodeFactoryHumanReadable;
+import static org.apache.cassandra.distributed.test.log.PlacementSimulator.*;
+import static org.apache.cassandra.harry.sut.TokenPlacementModel.*;
public class MetadataChangeSimulationTest extends CMSTestBase
{
@@ -88,7 +88,7 @@ public class MetadataChangeSimulationTest extends CMSTestBase
{
for (int rf : new int[]{ 2, 3, 5 })
{
- simulate(50, new PlacementSimulator.NtsReplicationFactor(3, rf), concurrency);
+ simulate(50, new NtsReplicationFactor(3, rf), concurrency);
}
}
}
@@ -100,7 +100,7 @@ public class MetadataChangeSimulationTest extends CMSTestBase
{
for (int rf : new int[]{ 2, 3, 5 })
{
- simulate(50, new PlacementSimulator.SimpleReplicationFactor(rf), concurrency);
+ simulate(50, new SimpleReplicationFactor(rf), concurrency);
}
}
}
@@ -110,49 +110,49 @@ public class MetadataChangeSimulationTest extends CMSTestBase
{
for (int i = 0; i < 4; i++)
{
- testMoveReal(new PlacementSimulator.NtsReplicationFactor(1, 3), i, 150, 4);
- testMoveReal(new PlacementSimulator.NtsReplicationFactor(1, 3), i, 350, 4);
- testMoveReal(new PlacementSimulator.NtsReplicationFactor(1, 3), i, 550, 4);
+ testMoveReal(new NtsReplicationFactor(1, 3), i, 150, 4);
+ testMoveReal(new NtsReplicationFactor(1, 3), i, 350, 4);
+ testMoveReal(new NtsReplicationFactor(1, 3), i, 550, 4);
}
for (int i = 0; i < 5; i++)
{
- testMoveReal(new PlacementSimulator.NtsReplicationFactor(1, 3), i, 150, 5);
- testMoveReal(new PlacementSimulator.NtsReplicationFactor(1, 3), i, 350, 5);
- testMoveReal(new PlacementSimulator.NtsReplicationFactor(1, 3), i, 650, 5);
+ testMoveReal(new NtsReplicationFactor(1, 3), i, 150, 5);
+ testMoveReal(new NtsReplicationFactor(1, 3), i, 350, 5);
+ testMoveReal(new NtsReplicationFactor(1, 3), i, 650, 5);
}
for (int i = 0; i < 10; i++)
{
- testMoveReal(new PlacementSimulator.NtsReplicationFactor(1, 3), i, 150, 10);
- testMoveReal(new PlacementSimulator.NtsReplicationFactor(1, 3), i, 550, 10);
- testMoveReal(new PlacementSimulator.NtsReplicationFactor(1, 3), i, 1050, 10);
+ testMoveReal(new NtsReplicationFactor(1, 3), i, 150, 10);
+ testMoveReal(new NtsReplicationFactor(1, 3), i, 550, 10);
+ testMoveReal(new NtsReplicationFactor(1, 3), i, 1050, 10);
}
for (int i = 0; i < 9; i++)
{
- testMoveReal(new PlacementSimulator.NtsReplicationFactor(3, 3), i, 350, 9);
- testMoveReal(new PlacementSimulator.NtsReplicationFactor(3, 3), i, 550, 9);
- testMoveReal(new PlacementSimulator.NtsReplicationFactor(3, 3), i, 1050, 9);
+ testMoveReal(new NtsReplicationFactor(3, 3), i, 350, 9);
+ testMoveReal(new NtsReplicationFactor(3, 3), i, 550, 9);
+ testMoveReal(new NtsReplicationFactor(3, 3), i, 1050, 9);
}
for (int i = 0; i < 10; i++)
{
- testMoveReal(new PlacementSimulator.NtsReplicationFactor(3, 3), i, 350, 10);
- testMoveReal(new PlacementSimulator.NtsReplicationFactor(3, 3), i, 550, 10);
- testMoveReal(new PlacementSimulator.NtsReplicationFactor(3, 3), i, 1050, 10);
+ testMoveReal(new NtsReplicationFactor(3, 3), i, 350, 10);
+ testMoveReal(new NtsReplicationFactor(3, 3), i, 550, 10);
+ testMoveReal(new NtsReplicationFactor(3, 3), i, 1050, 10);
}
for (int i = 0; i < 18; i++)
{
- testMoveReal(new PlacementSimulator.NtsReplicationFactor(3, 3), i, 350, 18);
- testMoveReal(new PlacementSimulator.NtsReplicationFactor(3, 3), i, 1050, 18);
- testMoveReal(new PlacementSimulator.NtsReplicationFactor(3, 3), i, 2050, 18);
+ testMoveReal(new NtsReplicationFactor(3, 3), i, 350, 18);
+ testMoveReal(new NtsReplicationFactor(3, 3), i, 1050, 18);
+ testMoveReal(new NtsReplicationFactor(3, 3), i, 2050, 18);
}
}
- public void testMoveReal(PlacementSimulator.ReplicationFactor rf, int moveNodeId, long moveToken, int numberOfNodes) throws Throwable
+ public void testMoveReal(ReplicationFactor rf, int moveNodeId, long moveToken, int numberOfNodes) throws Throwable
{
try (CMSTestBase.CMSSut sut = new CMSTestBase.CMSSut(AtomicLongBackedProcessor::new, false, rf))
{
@@ -181,14 +181,14 @@ public class MetadataChangeSimulationTest extends CMSTestBase
@Test
public void testLeaveReal() throws Throwable
{
- testLeaveReal(new PlacementSimulator.NtsReplicationFactor(1, 3), 1);
- testLeaveReal(new PlacementSimulator.NtsReplicationFactor(1, 3), 5);
- testLeaveReal(new PlacementSimulator.NtsReplicationFactor(3, 3), 1);
- testLeaveReal(new PlacementSimulator.NtsReplicationFactor(3, 3), 5);
+ testLeaveReal(new NtsReplicationFactor(1, 3), 1);
+ testLeaveReal(new NtsReplicationFactor(1, 3), 5);
+ testLeaveReal(new NtsReplicationFactor(3, 3), 1);
+ testLeaveReal(new NtsReplicationFactor(3, 3), 5);
}
- public void testLeaveReal(PlacementSimulator.ReplicationFactor rf, int decomNodeId) throws Throwable
+ public void testLeaveReal(ReplicationFactor rf, int decomNodeId) throws Throwable
{
try (CMSTestBase.CMSSut sut = new CMSTestBase.CMSSut(AtomicLongBackedProcessor::new, false, rf))
{
@@ -217,11 +217,11 @@ public class MetadataChangeSimulationTest extends CMSTestBase
@Test
public void testJoinReal() throws Throwable
{
- testJoinReal(new PlacementSimulator.NtsReplicationFactor(3, 3), 1);
- testJoinReal(new PlacementSimulator.NtsReplicationFactor(3, 3), 5);
+ testJoinReal(new NtsReplicationFactor(3, 3), 1);
+ testJoinReal(new NtsReplicationFactor(3, 3), 5);
}
- public void testJoinReal(PlacementSimulator.ReplicationFactor rf, int joinNodeId) throws Throwable
+ public void testJoinReal(ReplicationFactor rf, int joinNodeId) throws Throwable
{
try (CMSTestBase.CMSSut sut = new CMSTestBase.CMSSut(AtomicLongBackedProcessor::new, false, rf))
{
@@ -250,14 +250,14 @@ public class MetadataChangeSimulationTest extends CMSTestBase
}
}
- public void simulate(int toBootstrap, PlacementSimulator.ReplicationFactor rf, int concurrency) throws Throwable
+ public void simulate(int toBootstrap, ReplicationFactor rf, int concurrency) throws Throwable
{
System.out.printf("RUNNING SIMULATION. TO BOOTSTRAP: %s, RF: %s, CONCURRENCY: %s%n",
toBootstrap, rf, concurrency);
long startTime = System.currentTimeMillis();
ModelChecker<ModelState, CMSSut> modelChecker = new ModelChecker<>();
ClusterMetadataService.unsetInstance();
- modelChecker.init(ModelState.empty(PlacementSimulator.nodeFactory(), toBootstrap, concurrency),
+ modelChecker.init(ModelState.empty(nodeFactory(), toBootstrap, concurrency),
new CMSSut(AtomicLongBackedProcessor::new, false, rf))
// Sequentially bootstrap rf nodes first
.step((state, sut) -> state.currentNodes.isEmpty(),
@@ -380,7 +380,7 @@ public class MetadataChangeSimulationTest extends CMSTestBase
Random random = new Random(1L);
for (int i = 0; i < 10; i++)
{
- PlacementSimulator.ReplicationFactor ntsRf = new PlacementSimulator.NtsReplicationFactor(3, 3);
+ ReplicationFactor ntsRf = new NtsReplicationFactor(3, 3);
Map<String, Integer> cmsRf = new HashMap<>();
for (String s : ntsRf.asMap().keySet())
cmsRf.put(s, 3);
@@ -389,12 +389,12 @@ public class MetadataChangeSimulationTest extends CMSTestBase
}
}
- public void simulateBounces(PlacementSimulator.ReplicationFactor rf, CMSPlacementStrategy CMSConfigurationStrategy, Random random) throws Throwable
+ public void simulateBounces(ReplicationFactor rf, CMSPlacementStrategy CMSConfigurationStrategy, Random random) throws Throwable
{
try(CMSSut sut = new CMSSut(AtomicLongBackedProcessor::new, false, rf))
{
- ModelState state = ModelState.empty(PlacementSimulator.nodeFactory(), 300, 1);
+ ModelState state = ModelState.empty(nodeFactory(), 300, 1);
for (Map.Entry<String, Integer> e : rf.asMap().entrySet())
{
@@ -553,16 +553,16 @@ public class MetadataChangeSimulationTest extends CMSTestBase
return sb.toString();
}
- public static void match(PlacementForRange actual, Map<PlacementSimulator.Range, List<Node>> predicted) throws Throwable
+ public static void match(PlacementForRange actual, Map<TokenPlacementModel.Range, List<Node>> predicted) throws Throwable
{
Map<Range<Token>, VersionedEndpoints.ForRange> actualGroups = actual.replicaGroups();
assert predicted.size() == actualGroups.size() :
String.format("\nPredicted:\n%s(%d)" +
"\nActual:\n%s(%d)", toString(predicted), predicted.size(), toString(actual.replicaGroups()), actualGroups.size());
- for (Map.Entry<PlacementSimulator.Range, List<Node>> entry : predicted.entrySet())
+ for (Map.Entry<TokenPlacementModel.Range, List<Node>> entry : predicted.entrySet())
{
- PlacementSimulator.Range range = entry.getKey();
+ TokenPlacementModel.Range range = entry.getKey();
List<Node> predictedNodes = entry.getValue();
Range<Token> predictedRange = new Range<Token>(new Murmur3Partitioner.LongToken(range.start),
new Murmur3Partitioner.LongToken(range.end));
@@ -653,7 +653,7 @@ public class MetadataChangeSimulationTest extends CMSTestBase
}
public static void validatePlacements(IPartitioner partitioner,
- PlacementSimulator.ReplicationFactor rf,
+ ReplicationFactor rf,
ModelState modelState,
DataPlacements placements)
{
@@ -678,7 +678,7 @@ public class MetadataChangeSimulationTest extends CMSTestBase
Assert.assertEquals(new TreeSet<>(l), new TreeSet<>(r));
}
- public static void validatePlacementsInternal(PlacementSimulator.ReplicationFactor rf, List<SimulatedOperation> opStates, List<Range<Token>> expectedRanges, PlacementForRange placements, boolean allowPending)
+ public static void validatePlacementsInternal(ReplicationFactor rf, List<SimulatedOperation> opStates, List<Range<Token>> expectedRanges, PlacementForRange placements, boolean allowPending)
{
int overreplicated = 0;
for (Range<Token> range : expectedRanges)
@@ -719,7 +719,7 @@ public class MetadataChangeSimulationTest extends CMSTestBase
}
}
- if (allowPending && rf instanceof PlacementSimulator.SimpleReplicationFactor)
+ if (allowPending && rf instanceof SimpleReplicationFactor)
{
int bootstrappingNodes = 0;
int movingNodes = 0;
diff --git a/test/distributed/org/apache/cassandra/distributed/test/log/ModelState.java b/test/distributed/org/apache/cassandra/distributed/test/log/ModelState.java
index b1e0a6d7d5..7a0a61604d 100644
--- a/test/distributed/org/apache/cassandra/distributed/test/log/ModelState.java
+++ b/test/distributed/org/apache/cassandra/distributed/test/log/ModelState.java
@@ -28,6 +28,8 @@ import java.util.Random;
import java.util.Set;
import java.util.TreeMap;
+import org.apache.cassandra.harry.sut.TokenPlacementModel;
+
public class ModelState
{
public final int maxClusterSize;
@@ -37,17 +39,17 @@ public class ModelState
public final int[] cancelled;
public final int[] finished;
public final int bootstrappingCount;
- public final List<PlacementSimulator.Node> currentNodes;
- public final Map<String, List<PlacementSimulator.Node>> nodesByDc;
- public final List<PlacementSimulator.Node> registeredNodes;
- public final List<PlacementSimulator.Node> leavingNodes;
- public final List<PlacementSimulator.Node> movingNodes;
+ public final List<TokenPlacementModel.Node> currentNodes;
+ public final Map<String, List<TokenPlacementModel.Node>> nodesByDc;
+ public final List<TokenPlacementModel.Node> registeredNodes;
+ public final List<TokenPlacementModel.Node> leavingNodes;
+ public final List<TokenPlacementModel.Node> movingNodes;
public final List<SimulatedOperation> inFlightOperations;
public final PlacementSimulator.SimulatedPlacements simulatedPlacements;
- public final PlacementSimulator.NodeFactory nodeFactory;
+ public final TokenPlacementModel.NodeFactory nodeFactory;
- public static ModelState empty(PlacementSimulator.NodeFactory nodeFactory, int maxClusterSize, int maxConcurrency)
+ public static ModelState empty(TokenPlacementModel.NodeFactory nodeFactory, int maxClusterSize, int maxConcurrency)
{
return new ModelState(maxClusterSize, maxConcurrency,
0, 0,
@@ -58,11 +60,11 @@ public class ModelState
nodeFactory);
}
- public static Map<String, List<PlacementSimulator.Node>> groupByDc(List<PlacementSimulator.Node> nodes)
+ public static Map<String, List<TokenPlacementModel.Node>> groupByDc(List<TokenPlacementModel.Node> nodes)
{
// using treemap here since it is much easier to read/debug when it comes to that
- Map<String, List<PlacementSimulator.Node>> grouped = new TreeMap<>();
- for (PlacementSimulator.Node node : nodes)
+ Map<String, List<TokenPlacementModel.Node>> grouped = new TreeMap<>();
+ for (TokenPlacementModel.Node node : nodes)
{
grouped.computeIfAbsent(node.dc(), (k) -> new ArrayList<>())
.add(node);
@@ -76,13 +78,13 @@ public class ModelState
int rejected,
int[] cancelled,
int[] finished,
- List<PlacementSimulator.Node> currentNodes,
- List<PlacementSimulator.Node> registeredNodes,
- List<PlacementSimulator.Node> leavingNodes,
- List<PlacementSimulator.Node> movingNodes,
+ List<TokenPlacementModel.Node> currentNodes,
+ List<TokenPlacementModel.Node> registeredNodes,
+ List<TokenPlacementModel.Node> leavingNodes,
+ List<TokenPlacementModel.Node> movingNodes,
List<SimulatedOperation> operationStates,
PlacementSimulator.SimulatedPlacements simulatedPlacements,
- PlacementSimulator.NodeFactory nodeFactory)
+ TokenPlacementModel.NodeFactory nodeFactory)
{
this.maxClusterSize = maxClusterSize;
this.maxConcurrency = maxConcurrency;
@@ -118,30 +120,30 @@ public class ModelState
return withinConcurrencyLimit() && bootstrappingCount + currentNodes.size() < maxClusterSize;
}
- public boolean shouldLeave(PlacementSimulator.ReplicationFactor rf, Random rng)
+ public boolean shouldLeave(TokenPlacementModel.ReplicationFactor rf, Random rng)
{
return canRemove(rf) && rng.nextDouble() > 0.7;
}
- public boolean shouldMove(PlacementSimulator.ReplicationFactor rf, Random rng)
+ public boolean shouldMove(TokenPlacementModel.ReplicationFactor rf, Random rng)
{
return canRemove(rf) && rng.nextDouble() > 0.7;
}
- public boolean shouldReplace(PlacementSimulator.ReplicationFactor rf, Random rng)
+ public boolean shouldReplace(TokenPlacementModel.ReplicationFactor rf, Random rng)
{
return canRemove(rf) && rng.nextDouble() > 0.8;
}
- private boolean canRemove(PlacementSimulator.ReplicationFactor rfs)
+ private boolean canRemove(TokenPlacementModel.ReplicationFactor rfs)
{
if (!withinConcurrencyLimit()) return false;
for (Map.Entry<String, Integer> e : rfs.asMap().entrySet())
{
String dc = e.getKey();
int rf = e.getValue();
- List<PlacementSimulator.Node> nodes = nodesByDc.get(dc);
- Set<PlacementSimulator.Node> nodesInDc = nodes == null ? new HashSet<>() : new HashSet<>(nodes);
+ List<TokenPlacementModel.Node> nodes = nodesByDc.get(dc);
+ Set<TokenPlacementModel.Node> nodesInDc = nodes == null ? new HashSet<>() : new HashSet<>(nodes);
for (SimulatedOperation op : inFlightOperations)
nodesInDc.removeAll(Arrays.asList(op.nodes));
if (nodesInDc.size() > rf)
@@ -179,13 +181,13 @@ public class ModelState
// join/replace/leave/move
private int[] cancelled;
private int[] finished;
- private List<PlacementSimulator.Node> currentNodes;
- private List<PlacementSimulator.Node> registeredNodes;
- private List<PlacementSimulator.Node> leavingNodes;
- private List<PlacementSimulator.Node> movingNodes;
+ private List<TokenPlacementModel.Node> currentNodes;
+ private List<TokenPlacementModel.Node> registeredNodes;
+ private List<TokenPlacementModel.Node> leavingNodes;
+ private List<TokenPlacementModel.Node> movingNodes;
private List<SimulatedOperation> operationStates;
private PlacementSimulator.SimulatedPlacements simulatedPlacements;
- private PlacementSimulator.NodeFactory nodeFactory;
+ private TokenPlacementModel.NodeFactory nodeFactory;
private Transformer(ModelState source)
{
@@ -254,26 +256,26 @@ public class ModelState
return this;
}
- public Transformer withJoined(PlacementSimulator.Node node)
+ public Transformer withJoined(TokenPlacementModel.Node node)
{
addToCluster(node);
finished[0]++;
return this;
}
- public Transformer recycleRejected(PlacementSimulator.Node node)
+ public Transformer recycleRejected(TokenPlacementModel.Node node)
{
registeredNodes = new ArrayList<>(registeredNodes);
registeredNodes.add(node);
return this;
}
- public Transformer withMoved(PlacementSimulator.Node movingNode, PlacementSimulator.Node movedTo)
+ public Transformer withMoved(TokenPlacementModel.Node movingNode, TokenPlacementModel.Node movedTo)
{
assert currentNodes.contains(movingNode) : movingNode;
- List<PlacementSimulator.Node> tmp = currentNodes;
+ List<TokenPlacementModel.Node> tmp = currentNodes;
currentNodes = new ArrayList<>();
- for (PlacementSimulator.Node n : tmp)
+ for (TokenPlacementModel.Node n : tmp)
{
if (n.idx() == movingNode.idx())
currentNodes.add(movedTo);
@@ -288,14 +290,14 @@ public class ModelState
return this;
}
- private void addToCluster(PlacementSimulator.Node node)
+ private void addToCluster(TokenPlacementModel.Node node)
{
// called during both join and replacement
currentNodes = new ArrayList<>(currentNodes);
currentNodes.add(node);
}
- public Transformer markMoving(PlacementSimulator.Node moving)
+ public Transformer markMoving(TokenPlacementModel.Node moving)
{
assert currentNodes.contains(moving);
movingNodes = new ArrayList<>(movingNodes);
@@ -303,7 +305,7 @@ public class ModelState
return this;
}
- public Transformer markLeaving(PlacementSimulator.Node leaving)
+ public Transformer markLeaving(TokenPlacementModel.Node leaving)
{
assert currentNodes.contains(leaving);
leavingNodes = new ArrayList<>(leavingNodes);
@@ -311,7 +313,7 @@ public class ModelState
return this;
}
- public Transformer withLeft(PlacementSimulator.Node node)
+ public Transformer withLeft(TokenPlacementModel.Node node)
{
assert currentNodes.contains(node);
// for now... assassinate may change this assertion
@@ -321,7 +323,7 @@ public class ModelState
return this;
}
- private void removeFromCluster(PlacementSimulator.Node node)
+ private void removeFromCluster(TokenPlacementModel.Node node)
{
// called during both decommission and replacement
currentNodes = new ArrayList<>(currentNodes);
@@ -330,7 +332,7 @@ public class ModelState
leavingNodes.remove(node);
}
- public Transformer withReplaced(PlacementSimulator.Node oldNode, PlacementSimulator.Node newNode)
+ public Transformer withReplaced(TokenPlacementModel.Node oldNode, TokenPlacementModel.Node newNode)
{
addToCluster(newNode);
removeFromCluster(oldNode);
diff --git a/test/distributed/org/apache/cassandra/distributed/test/log/OperationalEquivalenceTest.java b/test/distributed/org/apache/cassandra/distributed/test/log/OperationalEquivalenceTest.java
index 23cb98a283..58732e3641 100644
--- a/test/distributed/org/apache/cassandra/distributed/test/log/OperationalEquivalenceTest.java
+++ b/test/distributed/org/apache/cassandra/distributed/test/log/OperationalEquivalenceTest.java
@@ -31,6 +31,7 @@ import org.junit.Assert;
import org.junit.Test;
import org.apache.cassandra.config.DatabaseDescriptor;
+import org.apache.cassandra.harry.sut.TokenPlacementModel;
import org.apache.cassandra.locator.EndpointsForRange;
import org.apache.cassandra.locator.Replica;
import org.apache.cassandra.schema.KeyspaceParams;
@@ -60,9 +61,9 @@ public class OperationalEquivalenceTest extends CMSTestBase
}
// Private to this class for now as this is only a limited implementation
- private static class TransientReplicationFactor extends PlacementSimulator.ReplicationFactor
+ private static class TransientReplicationFactor extends TokenPlacementModel.ReplicationFactor
{
- private final PlacementSimulator.Lookup lookup = new PlacementSimulator.DefaultLookup();
+ private final TokenPlacementModel.Lookup lookup = new TokenPlacementModel.DefaultLookup();
private final int dcs;
private final int full;
private final int trans;
@@ -96,7 +97,7 @@ public class OperationalEquivalenceTest extends CMSTestBase
throw new IllegalStateException("Does not work with transient replication");
}
- public PlacementSimulator.ReplicatedRanges replicate(PlacementSimulator.Range[] ranges, List<PlacementSimulator.Node> nodes)
+ public TokenPlacementModel.ReplicatedRanges replicate(TokenPlacementModel.Range[] ranges, List<TokenPlacementModel.Node> nodes)
{
throw new IllegalStateException("Does not work with transient replication (yet)");
}
@@ -105,28 +106,28 @@ public class OperationalEquivalenceTest extends CMSTestBase
@Test
public void testMove() throws Exception
{
- testMove(new PlacementSimulator.SimpleReplicationFactor(2));
- testMove(new PlacementSimulator.SimpleReplicationFactor(3));
- testMove(new PlacementSimulator.SimpleReplicationFactor(5));
+ testMove(new TokenPlacementModel.SimpleReplicationFactor(2));
+ testMove(new TokenPlacementModel.SimpleReplicationFactor(3));
+ testMove(new TokenPlacementModel.SimpleReplicationFactor(5));
- testMove(new PlacementSimulator.NtsReplicationFactor(1, 2));
- testMove(new PlacementSimulator.NtsReplicationFactor(1, 3));
- testMove(new PlacementSimulator.NtsReplicationFactor(1, 5));
+ testMove(new TokenPlacementModel.NtsReplicationFactor(1, 2));
+ testMove(new TokenPlacementModel.NtsReplicationFactor(1, 3));
+ testMove(new TokenPlacementModel.NtsReplicationFactor(1, 5));
- testMove(new PlacementSimulator.NtsReplicationFactor(3, 2));
- testMove(new PlacementSimulator.NtsReplicationFactor(3, 3));
- testMove(new PlacementSimulator.NtsReplicationFactor(3, 5));
+ testMove(new TokenPlacementModel.NtsReplicationFactor(3, 2));
+ testMove(new TokenPlacementModel.NtsReplicationFactor(3, 3));
+ testMove(new TokenPlacementModel.NtsReplicationFactor(3, 5));
testMove(new TransientReplicationFactor(3, 3, 1));
testMove(new TransientReplicationFactor(3, 3, 2));
}
- public void testMove(PlacementSimulator.ReplicationFactor rf) throws Exception
+ public void testMove(TokenPlacementModel.ReplicationFactor rf) throws Exception
{
- PlacementSimulator.NodeFactory nodeFactory = PlacementSimulator.nodeFactory();
+ TokenPlacementModel.NodeFactory nodeFactory = TokenPlacementModel.nodeFactory();
ClusterMetadata withMove = null;
- List<PlacementSimulator.Node> equivalentNodes = new ArrayList<>();
+ List<TokenPlacementModel.Node> equivalentNodes = new ArrayList<>();
int nodes = 30;
try (CMSSut sut = new CMSSut(AtomicLongBackedProcessor::new, false, rf))
{
@@ -134,14 +135,14 @@ public class OperationalEquivalenceTest extends CMSTestBase
for (int i = 0; i < nodes; i++)
{
int dc = toDc(i, rf);
- PlacementSimulator.Node node = nodeFactory.make(counter.incrementAndGet(), dc, 1);
+ TokenPlacementModel.Node node = nodeFactory.make(counter.incrementAndGet(), dc, 1);
sut.service.commit(new Register(new NodeAddresses(node.addr()), new Location(node.dc(), node.rack()), NodeVersion.CURRENT));
sut.service.commit(new UnsafeJoin(node.nodeId(), Collections.singleton(node.longToken()), sut.service.placementProvider()));
equivalentNodes.add(node);
}
- PlacementSimulator.Node toMove = equivalentNodes.get(rng.nextInt(equivalentNodes.size()));
- PlacementSimulator.Node moved = toMove.withNewToken();
+ TokenPlacementModel.Node toMove = equivalentNodes.get(rng.nextInt(equivalentNodes.size()));
+ TokenPlacementModel.Node moved = toMove.withNewToken();
equivalentNodes.set(equivalentNodes.indexOf(toMove), moved);
Move plan = SimulatedOperation.prepareMove(sut, toMove, moved.longToken()).get();
@@ -156,12 +157,12 @@ public class OperationalEquivalenceTest extends CMSTestBase
withMove.placements);
}
- private static ClusterMetadata simulateAndCompare(PlacementSimulator.ReplicationFactor rf, List<PlacementSimulator.Node> nodes) throws Exception
+ private static ClusterMetadata simulateAndCompare(TokenPlacementModel.ReplicationFactor rf, List<TokenPlacementModel.Node> nodes) throws Exception
{
Collections.shuffle(nodes, rng);
try (CMSSut sut = new CMSSut(AtomicLongBackedProcessor::new, false, rf))
{
- for (PlacementSimulator.Node node : nodes)
+ for (TokenPlacementModel.Node node : nodes)
{
sut.service.commit(new Register(new NodeAddresses(node.addr()), new Location(node.dc(), node.rack()), NodeVersion.CURRENT));
sut.service.commit(new UnsafeJoin(node.nodeId(), Collections.singleton(node.longToken()), sut.service.placementProvider()));
@@ -196,7 +197,7 @@ public class OperationalEquivalenceTest extends CMSTestBase
{
return ep.stream().sorted(Replica::compareTo).collect(Collectors.toList());
}
- private static int toDc(int i, PlacementSimulator.ReplicationFactor rf)
+ private static int toDc(int i, TokenPlacementModel.ReplicationFactor rf)
{
return (i % rf.dcs()) + 1;
}
diff --git a/test/distributed/org/apache/cassandra/distributed/test/log/PlacementSimulator.java b/test/distributed/org/apache/cassandra/distributed/test/log/PlacementSimulator.java
index 4d44c3b775..bd9e5c577f 100644
--- a/test/distributed/org/apache/cassandra/distributed/test/log/PlacementSimulator.java
+++ b/test/distributed/org/apache/cassandra/distributed/test/log/PlacementSimulator.java
@@ -24,23 +24,25 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
-import java.net.UnknownHostException;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.Random;
+import java.util.Set;
+import java.util.TreeMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.junit.Assert;
-import harry.generators.PCGFastPure;
-import org.apache.cassandra.dht.Murmur3Partitioner;
-import org.apache.cassandra.distributed.api.TokenSupplier;
-import org.apache.cassandra.locator.InetAddressAndPort;
-;
-import org.apache.cassandra.schema.KeyspaceParams;
-import org.apache.cassandra.tcm.ClusterMetadata;
-import org.apache.cassandra.tcm.membership.Location;
-import org.apache.cassandra.tcm.membership.NodeId;
+import static org.apache.cassandra.harry.sut.TokenPlacementModel.Node;
+import static org.apache.cassandra.harry.sut.TokenPlacementModel.Range;
+import static org.apache.cassandra.harry.sut.TokenPlacementModel.ReplicationFactor;
/**
* A small class that helps to avoid doing mental arithmetics on ranges.
@@ -398,7 +400,7 @@ public class PlacementSimulator
List<Node> finalNodes = new ArrayList<>();
for (int i = 0; i < origNodes.size(); i++)
{
- if (origNodes.get(i).nodeIdx == movingNode.nodeIdx)
+ if (origNodes.get(i).idx() == movingNode.idx())
continue;
finalNodes.add(origNodes.get(i));
}
@@ -674,8 +676,8 @@ public class PlacementSimulator
// bootstrap_diffBased implementation and the real code doesn't do this, only the endpoint matters for
// correctness, so we limit this comparison to endpoints only.
Assert.assertEquals(String.format("For key: %s\n", k),
- expected.get(k).stream().map(n -> n.nodeIdx).sorted().collect(Collectors.toList()),
- actual.get(k).stream().map(n -> n.nodeIdx).sorted().collect(Collectors.toList()));
+ expected.get(k).stream().map(n -> n.idx()).sorted().collect(Collectors.toList()),
+ actual.get(k).stream().map(n -> n.idx()).sorted().collect(Collectors.toList()));
});
}
@@ -935,199 +937,6 @@ public class PlacementSimulator
return Collections.unmodifiableList(newNodes);
}
- /**
- * Replicate ranges to rf nodes.
- */
- private static ReplicatedRanges combine(NavigableMap<Range, Map<String, List<Node>>> orig)
- {
-
- Range[] ranges = new Range[orig.size()];
- int idx = 0;
- NavigableMap<Range, List<Node>> flattened = new TreeMap<>();
- for (Map.Entry<Range, Map<String, List<Node>>> e : orig.entrySet())
- {
- List<Node> placementsForRange = new ArrayList<>();
- for (List<Node> v : e.getValue().values())
- placementsForRange.addAll(v);
- ranges[idx++] = e.getKey();
- flattened.put(e.getKey(), placementsForRange);
- }
- return new ReplicatedRanges(ranges, flattened);
- }
-
- public static class ReplicatedRanges
- {
- private final Range[] ranges;
- private final NavigableMap<Range, List<Node>> placementsForRange;
-
- public ReplicatedRanges(Range[] ranges, NavigableMap<Range, List<Node>> placementsForRange)
- {
- this.ranges = ranges;
- this.placementsForRange = placementsForRange;
- }
-
- public List<Node> replicasFor(long token)
- {
- int idx = indexedBinarySearch(ranges, range -> {
- // exclusive start, so token at the start belongs to a lower range
- if (token <= range.start)
- return 1;
- // ie token > start && token <= end
- if (token <= range.end ||range.end == Long.MIN_VALUE)
- return 0;
-
- return -1;
- });
- assert idx >= 0 : String.format("Somehow ranges %s do not contain token %d", Arrays.toString(ranges), token);
- return placementsForRange.get(ranges[idx]);
- }
-
- public NavigableMap<Range, List<Node>> asMap()
- {
- return placementsForRange;
- }
-
- private static <T> int indexedBinarySearch(T[] arr, CompareTo<T> comparator)
- {
- int low = 0;
- int high = arr.length - 1;
-
- while (low <= high)
- {
- int mid = (low + high) >>> 1;
- T midEl = arr[mid];
- int cmp = comparator.compareTo(midEl);
-
- if (cmp < 0)
- low = mid + 1;
- else if (cmp > 0)
- high = mid - 1;
- else
- return mid;
- }
- return -(low + 1); // key not found
- }
- }
-
- public interface CompareTo<V>
- {
- int compareTo(V v);
- }
-
- private static <K extends Comparable<K>, T1, T2> Map<K, T2> mapValues(Map<K, T1> allDCs, Function<T1, T2> map)
- {
- NavigableMap<K, T2> res = new TreeMap<>();
- for (Map.Entry<K, T1> e : allDCs.entrySet())
- {
- res.put(e.getKey(), map.apply(e.getValue()));
- }
- return res;
- }
-
- public static Map<String, List<Node>> nodesByDC(List<Node> nodes)
- {
- Map<String, List<Node>> nodesByDC = new HashMap<>();
- for (Node node : nodes)
- nodesByDC.computeIfAbsent(node.dc(), (k) -> new ArrayList<>()).add(node);
-
- return nodesByDC;
- }
-
- public static Map<String, Set<String>> racksByDC(List<Node> nodes)
- {
- Map<String, Set<String>> racksByDC = new HashMap<>();
- for (Node node : nodes)
- racksByDC.computeIfAbsent(node.dc(), (k) -> new HashSet<>()).add(node.rack());
-
- return racksByDC;
- }
-
- private static final class DatacenterNodes
- {
- private final List<Node> nodes = new ArrayList<>();
- private final Set<Location> racks = new HashSet<>();
-
- /** Number of replicas left to fill from this DC. */
- int rfLeft;
- int acceptableRackRepeats;
-
- public DatacenterNodes copy()
- {
- return new DatacenterNodes(rfLeft, acceptableRackRepeats);
- }
-
- DatacenterNodes(int rf,
- int rackCount,
- int nodeCount)
- {
- this.rfLeft = Math.min(rf, nodeCount);
- acceptableRackRepeats = rf - rackCount;
- }
-
- // for copying
- DatacenterNodes(int rfLeft, int acceptableRackRepeats)
- {
- this.rfLeft = rfLeft;
- this.acceptableRackRepeats = acceptableRackRepeats;
- }
-
- boolean addAndCheckIfDone(Node node, Location location)
- {
- if (done())
- return false;
-
- if (nodes.contains(node))
- // Cannot repeat a node.
- return false;
-
- if (racks.add(location))
- {
- // New rack.
- --rfLeft;
- nodes.add(node);
- return done();
- }
- if (acceptableRackRepeats <= 0)
- // There must be rfLeft distinct racks left, do not add any more rack repeats.
- return false;
-
- nodes.add(node);
-
- // Added a node that is from an already met rack to match RF when there aren't enough racks.
- --acceptableRackRepeats;
- --rfLeft;
- return done();
- }
-
- boolean done()
- {
- assert rfLeft >= 0;
- return rfLeft == 0;
- }
- }
-
-
- public static void addIfUnique(List<Node> nodes, Set<Integer> names, Node node)
- {
- if (names.contains(node.idx()))
- return;
- nodes.add(node);
- names.add(node.idx());
- }
-
- /**
- * Finds a primary replica
- */
- public static int primaryReplica(List<Node> nodes, Range range)
- {
- for (int i = 0; i < nodes.size(); i++)
- {
- if (range.end != Long.MIN_VALUE && nodes.get(i).token() >= range.end)
- return i;
- }
- return -1;
- }
-
/**
* Generates token ranges from the list of nodes
*/
@@ -1190,297 +999,6 @@ public class PlacementSimulator
}
}
-
- /**
- * A Range is responsible for the tokens between (start, end].
- */
- public static class Range implements Comparable<Range>
- {
- public final long start;
- public final long end;
-
- public Range(long start, long end)
- {
- assert end > start || end == Long.MIN_VALUE : String.format("Start (%d) should be smaller than end (%d)", start, end);
- this.start = start;
- this.end = end;
- }
-
- public boolean contains(long min, long max)
- {
- assert max > min;
- return min > start && (max <= end || end == Long.MIN_VALUE);
- }
-
- public boolean contains(long token)
- {
- return token > start && (token <= end || end == Long.MIN_VALUE);
- }
-
- public int compareTo(Range o)
- {
- int res = Long.compare(start, o.start);
- if (res == 0)
- return Long.compare(end, o.end);
- return res;
- }
-
- public boolean equals(Object o)
- {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- Range range = (Range) o;
- return start == range.start && end == range.end;
- }
-
- public int hashCode()
- {
- return Objects.hash(start, end);
- }
-
- public String toString()
- {
- return "(" +
- "" + (start == Long.MIN_VALUE ? "MIN" : start) +
- ", " + (end == Long.MIN_VALUE ? "MIN" : end) +
- ']';
- }
- }
-
- public interface Lookup
- {
- String id(int nodeIdx);
- String dc(int dcIdx);
- String rack(int rackIdx);
- NodeId nodeId(int nodeIdx);
- long token(int tokenIdx);
- Lookup forceToken(int tokenIdx, long token);
- InetAddressAndPort addr(int idx);
- void reset();
- }
-
- public static class DefaultLookup implements Lookup
- {
- protected final Map<Integer, Long> overrides = new HashMap<>(2);
-
- public String id(int nodeIdx)
- {
- return String.format("127.0.%d.%d", nodeIdx / 256, nodeIdx % 256);
- }
-
- public NodeId nodeId(int nodeIdx)
- {
- return ClusterMetadata.current().directory.peerId(addr(nodeIdx));
- }
-
- public long token(int tokenIdx)
- {
- Long override = overrides.get(tokenIdx);
- if (override != null)
- return override;
- return PCGFastPure.next(tokenIdx, 1L);
- }
-
- public Lookup forceToken(int tokenIdx, long token)
- {
- DefaultLookup newLookup = new DefaultLookup();
- newLookup.overrides.putAll(overrides);
- newLookup.overrides.put(tokenIdx, token);
- return newLookup;
- }
-
- public InetAddressAndPort addr(int idx)
- {
- try
- {
- return InetAddressAndPort.getByName(id(idx));
- }
- catch (UnknownHostException e)
- {
- throw new RuntimeException(e);
- }
- }
-
- public void reset()
- {
- overrides.clear();
- }
-
- public String dc(int dcIdx)
- {
- return String.format("datacenter%d", dcIdx);
- }
-
- public String rack(int rackIdx)
- {
- return String.format("rack%d", rackIdx);
- }
- }
-
- public static class HumanReadableTokensLookup extends DefaultLookup {
- @Override
- public long token(int tokenIdx)
- {
- Long override = overrides.get(tokenIdx);
- if (override != null)
- return override;
- return tokenIdx * 100L;
- }
-
- public Lookup forceToken(int tokenIdx, long token)
- {
- DefaultLookup lookup = new HumanReadableTokensLookup();
- lookup.overrides.putAll(overrides);
- lookup.overrides.put(tokenIdx, token);
- return lookup;
- }
- }
- public static NodeFactory nodeFactory()
- {
- return new NodeFactory(new DefaultLookup());
- }
-
- public static NodeFactory nodeFactoryHumanReadable()
- {
- return new NodeFactory(new HumanReadableTokensLookup());
- }
-
- public static class NodeFactory implements TokenSupplier
- {
- private final Lookup lookup;
-
- public NodeFactory(Lookup lookup)
- {
- this.lookup = lookup;
- }
-
- public Node make(int idx, int dc, int rack)
- {
- return new Node(idx, idx, dc, rack, lookup);
- }
-
- public Lookup lookup()
- {
- return lookup;
- }
-
- public Collection<String> tokens(int i)
- {
- return Collections.singletonList(Long.toString(lookup.token(i)));
- }
- }
-
- public static class Node implements Comparable<Node>
- {
- private final int tokenIdx;
- private final int nodeIdx;
- private final int dcIdx;
- private final int rackIdx;
- private final Lookup lookup;
-
- private Node(int tokenIdx, int idx, int dcIdx, int rackIdx, Lookup lookup)
- {
- this.tokenIdx = tokenIdx;
- this.nodeIdx = idx;
- this.dcIdx = dcIdx;
- this.rackIdx = rackIdx;
- this.lookup = lookup;
- }
-
- public InetAddressAndPort addr()
- {
- return lookup.addr(nodeIdx);
- }
-
- public NodeId nodeId()
- {
- return lookup.nodeId(nodeIdx);
- }
-
- public String id()
- {
- return lookup.id(nodeIdx);
- }
-
- public int idx()
- {
- return nodeIdx;
- }
-
- public int dcIdx()
- {
- return dcIdx;
- }
-
- public int rackIdx()
- {
- return rackIdx;
- }
-
- public String dc()
- {
- return lookup.dc(dcIdx);
- }
-
- public String rack()
- {
- return lookup.rack(rackIdx);
- }
-
- public long token()
- {
- return lookup.token(tokenIdx);
- }
-
- public int tokenIdx()
- {
- return tokenIdx;
- }
-
- public Murmur3Partitioner.LongToken longToken()
- {
- return new Murmur3Partitioner.LongToken(token());
- }
-
- public Node withNewToken()
- {
- return new Node(tokenIdx + 100_000, nodeIdx, dcIdx, rackIdx, lookup);
- }
-
- public Node withToken(int tokenIdx)
- {
- return new Node(tokenIdx, nodeIdx, dcIdx, rackIdx, lookup);
- }
-
- public Node overrideToken(long override)
- {
- return new Node(tokenIdx, nodeIdx, dcIdx, rackIdx, lookup.forceToken(tokenIdx, override));
- }
-
- public boolean equals(Object o)
- {
- if (this == o) return true;
- if (o == null || !Node.class.isAssignableFrom(o.getClass())) return false;
- Node node = (Node) o;
- return Objects.equals(nodeIdx, node.nodeIdx);
- }
-
- public int hashCode()
- {
- return Objects.hash(nodeIdx);
- }
-
- public int compareTo(Node o)
- {
- return Long.compare(token(), o.token());
- }
-
- public String toString()
- {
- return String.format("%s-%s@%d", dc(), id(), token());
- }
- }
-
public static String diffsToString(Map<Range, Diff<Node>> placements)
{
StringBuilder builder = new StringBuilder();
@@ -1535,255 +1053,4 @@ public class PlacementSimulator
}
}
}
-
- public abstract static class ReplicationFactor
- {
- private final int nodesTotal;
-
- public ReplicationFactor(int total)
- {
- this.nodesTotal = total;
- }
-
- public int total()
- {
- return nodesTotal;
- }
-
- public abstract int dcs();
-
- public abstract KeyspaceParams asKeyspaceParams();
-
- public abstract Map<String, Integer> asMap();
-
- public ReplicatedRanges replicate(List<Node> nodes)
- {
- return replicate(toRanges(nodes), nodes);
- }
- public abstract ReplicatedRanges replicate(Range[] ranges, List<Node> nodes);
- }
-
- public static class NtsReplicationFactor extends ReplicationFactor
- {
- private final Lookup lookup = new DefaultLookup();
- public final int[] nodesPerDc;
- private KeyspaceParams keyspaceParams;
- private Map<String, Integer> map;
-
- public NtsReplicationFactor(int... nodesPerDc)
- {
- super(total(nodesPerDc));
- this.nodesPerDc = nodesPerDc;
- }
-
- public NtsReplicationFactor(int dcs, int nodesPerDc)
- {
- super(dcs * nodesPerDc);
- this.nodesPerDc = new int[dcs];
- Arrays.fill(this.nodesPerDc, nodesPerDc);
- }
-
- private static int total(int... num)
- {
- int tmp = 0;
- for (int i : num)
- tmp += i;
- return tmp;
- }
-
- public int dcs()
- {
- return nodesPerDc.length;
- }
-
- public KeyspaceParams asKeyspaceParams()
- {
- if (this.keyspaceParams == null)
- this.keyspaceParams = toKeyspaceParams(lookup);
- return this.keyspaceParams;
- }
-
-
- public Map<String, Integer> asMap()
- {
- if (this.map == null)
- this.map = toMap(lookup);
- return this.map;
- }
-
- public ReplicatedRanges replicate(Range[] ranges, List<Node> nodes)
- {
- return replicate(ranges, nodes, asMap());
- }
-
- private static <T extends Comparable<T>> void assertStrictlySorted(Collection<T> coll)
- {
- if (coll.size() <= 1) return;
-
- Iterator<T> iter = coll.iterator();
- T prev = iter.next();
- while (iter.hasNext())
- {
- T next = iter.next();
- assert next.compareTo(prev) > 0 : String.format("Collection does not seem to be sorted. %s and %s are in wrong order", prev, next);
- prev = next;
- }
- }
- public static ReplicatedRanges replicate(Range[] ranges, List<Node> nodes, Map<String, Integer> rfs)
- {
- assertStrictlySorted(nodes);
- Map<String, DatacenterNodes> template = new HashMap<>();
-
- Map<String, List<Node>> nodesByDC = nodesByDC(nodes);
- Map<String, Set<String>> racksByDC = racksByDC(nodes);
-
- for (Map.Entry<String, Integer> entry : rfs.entrySet())
- {
- String dc = entry.getKey();
- int rf = entry.getValue();
- List<Node> nodesInThisDC = nodesByDC.get(dc);
- Set<String> racksInThisDC = racksByDC.get(dc);
- int nodeCount = nodesInThisDC == null ? 0 : nodesInThisDC.size();
- int rackCount = racksInThisDC == null ? 0 : racksInThisDC.size();
- if (rf <= 0 || nodeCount == 0)
- continue;
-
- template.put(dc, new DatacenterNodes(rf, rackCount, nodeCount));
- }
-
- NavigableMap<Range, Map<String, List<Node>>> replication = new TreeMap<>();
-
- for (Range range : ranges)
- {
- final int idx = primaryReplica(nodes, range);
- int cnt = 0;
- if (idx >= 0)
- {
- int dcsToFill = template.size();
-
- Map<String, DatacenterNodes> nodesInDCs = new HashMap<>();
- for (Map.Entry<String, DatacenterNodes> e : template.entrySet())
- nodesInDCs.put(e.getKey(), e.getValue().copy());
-
- while (dcsToFill > 0 && cnt < nodes.size())
- {
- Node node = nodes.get((idx + cnt) % nodes.size());
- DatacenterNodes dcNodes = nodesInDCs.get(node.dc());
- if (dcNodes != null && dcNodes.addAndCheckIfDone(node, new Location(node.dc(), node.rack())))
- dcsToFill--;
-
- cnt++;
- }
-
- replication.put(range, mapValues(nodesInDCs, v -> v.nodes));
- }
- else
- {
- // if the range end is larger than the highest assigned token, then treat it
- // as part of the wraparound and replicate it to the same nodes as the first
- // range. This is most likely caused by a decommission removing the node with
- // the largest token.
- replication.put(range, replication.get(ranges[0]));
- }
- }
-
- return combine(replication);
- }
-
- private KeyspaceParams toKeyspaceParams(Lookup lookup)
- {
- Object[] args = new Object[nodesPerDc.length * 2];
- for (int i = 0; i < nodesPerDc.length; i++)
- {
- args[i * 2] = lookup.dc(i + 1);
- args[i * 2 + 1] = nodesPerDc[i];
- }
- return KeyspaceParams.nts(args);
- }
-
- private Map<String, Integer> toMap(Lookup lookup)
- {
- Map<String, Integer> map = new TreeMap<>();
- for (int i = 0; i < nodesPerDc.length; i++)
- {
- map.put(lookup.dc(i + 1), nodesPerDc[i]);
- }
- return map;
- }
-
- public String toString()
- {
- return "NtsReplicationFactor{" +
- "map=" + asMap() +
- '}';
- }
- }
-
- public static class SimpleReplicationFactor extends ReplicationFactor
- {
- private final Lookup lookup = new DefaultLookup();
- public SimpleReplicationFactor(int total)
- {
- super(total);
- }
-
- public int dcs()
- {
- return 1;
- }
-
- public KeyspaceParams asKeyspaceParams()
- {
- return KeyspaceParams.simple(total());
- }
-
- public Map<String, Integer> asMap()
- {
- return Collections.singletonMap(lookup.dc(1), total());
- }
-
- public ReplicatedRanges replicate(Range[] ranges, List<Node> nodes)
- {
- return replicate(ranges, nodes, total());
- }
-
- public static ReplicatedRanges replicate(Range[] ranges, List<Node> nodes, int rf)
- {
- NavigableMap<Range, List<Node>> replication = new TreeMap<>();
- for (Range range : ranges)
- {
- Set<Integer> names = new HashSet<>();
- List<Node> replicas = new ArrayList<>();
- int idx = primaryReplica(nodes, range);
- if (idx >= 0)
- {
- for (int i = idx; i < nodes.size() && replicas.size() < rf; i++)
- addIfUnique(replicas, names, nodes.get(i));
-
- for (int i = 0; replicas.size() < rf && i < idx; i++)
- addIfUnique(replicas, names, nodes.get(i));
- if (range.start == Long.MIN_VALUE)
- replication.put(ranges[ranges.length - 1], replicas);
- replication.put(range, replicas);
- }
- else
- {
- // if the range end is larger than the highest assigned token, then treat it
- // as part of the wraparound and replicate it to the same nodes as the first
- // range. This is most likely caused by a decommission removing the node with
- // the largest token.
- replication.put(range, replication.get(ranges[0]));
- }
- }
-
- return new ReplicatedRanges(ranges, Collections.unmodifiableNavigableMap(replication));
- }
-
- public String toString()
- {
- return "SimpleReplicationFactor{" +
- "rf=" + total() +
- '}';
- }
- }
}
diff --git a/test/distributed/org/apache/cassandra/distributed/test/log/PlacementSimulatorTest.java b/test/distributed/org/apache/cassandra/distributed/test/log/PlacementSimulatorTest.java
index babe38a9c1..6b4b496532 100644
--- a/test/distributed/org/apache/cassandra/distributed/test/log/PlacementSimulatorTest.java
+++ b/test/distributed/org/apache/cassandra/distributed/test/log/PlacementSimulatorTest.java
@@ -30,7 +30,24 @@ import java.util.function.Supplier;
import org.junit.Test;
-import static org.apache.cassandra.distributed.test.log.PlacementSimulator.*;
+import org.apache.cassandra.harry.sut.TokenPlacementModel;
+
+import static org.apache.cassandra.distributed.test.log.PlacementSimulator.SimulatedPlacements;
+import static org.apache.cassandra.distributed.test.log.PlacementSimulator.Transformations;
+import static org.apache.cassandra.distributed.test.log.PlacementSimulator.assertPlacements;
+import static org.apache.cassandra.distributed.test.log.PlacementSimulator.assertRanges;
+import static org.apache.cassandra.distributed.test.log.PlacementSimulator.filter;
+import static org.apache.cassandra.distributed.test.log.PlacementSimulator.join;
+import static org.apache.cassandra.distributed.test.log.PlacementSimulator.leave;
+import static org.apache.cassandra.distributed.test.log.PlacementSimulator.move;
+import static org.apache.cassandra.distributed.test.log.PlacementSimulator.replace;
+import static org.apache.cassandra.distributed.test.log.PlacementSimulator.split;
+import static org.apache.cassandra.distributed.test.log.PlacementSimulator.superset;
+import static org.apache.cassandra.harry.sut.TokenPlacementModel.Node;
+import static org.apache.cassandra.harry.sut.TokenPlacementModel.NodeFactory;
+import static org.apache.cassandra.harry.sut.TokenPlacementModel.Range;
+import static org.apache.cassandra.harry.sut.TokenPlacementModel.ReplicationFactor;
+import static org.apache.cassandra.harry.sut.TokenPlacementModel.SimpleReplicationFactor;
import static org.junit.Assert.assertTrue;
public class PlacementSimulatorTest
@@ -50,7 +67,7 @@ public class PlacementSimulatorTest
public void testMove(long t1, long t2, long t3, long t4, long newToken, ReplicationFactor rf)
{
- NodeFactory factory = PlacementSimulator.nodeFactory();
+ NodeFactory factory = TokenPlacementModel.nodeFactory();
Node movingNode = factory.make(1, 1, 1).overrideToken(t1);
List<Node> orig = Arrays.asList(movingNode,
factory.make(2, 1, 1).overrideToken(t2),
@@ -103,7 +120,7 @@ public class PlacementSimulatorTest
public void testBootstrap(long t1, long t2, long t3, long t4, long newToken, ReplicationFactor rf)
{
- NodeFactory factory = PlacementSimulator.nodeFactory();
+ NodeFactory factory = TokenPlacementModel.nodeFactory();
List<Node> orig = Arrays.asList(factory.make(1, 1, 1).overrideToken(t1),
factory.make(2, 1, 1).overrideToken(t2),
factory.make(3, 1, 1).overrideToken(t3),
@@ -157,7 +174,7 @@ public class PlacementSimulatorTest
public void testDecommission(long t1, long t2, long t3, long t4, long t5, ReplicationFactor rf)
{
- NodeFactory factory = PlacementSimulator.nodeFactory();
+ NodeFactory factory = TokenPlacementModel.nodeFactory();
Node leavingNode = factory.make(1, 1, 1).overrideToken(t1);
List<Node> orig = Arrays.asList(leavingNode,
factory.make(2, 1, 1).overrideToken(t2),
@@ -225,7 +242,7 @@ public class PlacementSimulatorTest
public void simulate(ReplicationFactor rf) throws Throwable
{
- NodeFactory factory = PlacementSimulator.nodeFactory();
+ NodeFactory factory = TokenPlacementModel.nodeFactory();
List<Node> orig = Collections.singletonList(factory.make(1, 1, 1));
ModelChecker<SimulatedPlacements, SUTState> modelChecker = new ModelChecker<>();
@@ -287,7 +304,7 @@ public class PlacementSimulatorTest
for (int n : new int[]{ 2, 3, 5 })
{
ReplicationFactor rf = new SimpleReplicationFactor(n);
- NodeFactory factory = PlacementSimulator.nodeFactoryHumanReadable();
+ NodeFactory factory = TokenPlacementModel.nodeFactoryHumanReadable();
List<Node> nodes = new ArrayList<>(10);
for (int i = 1; i <= 10; i++)
nodes.add(factory.make(i, 1, 1));
@@ -304,7 +321,7 @@ public class PlacementSimulatorTest
for (int n : new int[]{ 2, 3, 5 })
{
ReplicationFactor rf = new SimpleReplicationFactor(n);
- NodeFactory factory = PlacementSimulator.nodeFactoryHumanReadable();
+ NodeFactory factory = TokenPlacementModel.nodeFactoryHumanReadable();
List<Node> nodes = new ArrayList<>(10);
for (int i = 1; i <= 10; i++)
nodes.add(factory.make(i, 1, 1));
@@ -321,7 +338,7 @@ public class PlacementSimulatorTest
for (int n : new int[]{ 2, 3, 5 })
{
ReplicationFactor rf = new SimpleReplicationFactor(n);
- NodeFactory factory = PlacementSimulator.nodeFactoryHumanReadable();
+ NodeFactory factory = TokenPlacementModel.nodeFactoryHumanReadable();
List<Node> nodes = new ArrayList<>(10);
for (int i = 1; i <= 10; i++)
nodes.add(factory.make(i, 1, 1));
diff --git a/test/distributed/org/apache/cassandra/distributed/test/log/ResumableStartupTest.java b/test/distributed/org/apache/cassandra/distributed/test/log/ResumableStartupTest.java
index d73be10136..c1440a1342 100644
--- a/test/distributed/org/apache/cassandra/distributed/test/log/ResumableStartupTest.java
+++ b/test/distributed/org/apache/cassandra/distributed/test/log/ResumableStartupTest.java
@@ -24,19 +24,21 @@ import java.util.concurrent.Callable;
import org.junit.Assert;
import org.junit.Test;
-import harry.core.Configuration;
-import harry.core.Run;
-import harry.model.sut.SystemUnderTest;
-import harry.visitors.GeneratingVisitor;
-import harry.visitors.MutatingRowVisitor;
-import harry.visitors.Visitor;
+import org.apache.cassandra.harry.core.Configuration;
+import org.apache.cassandra.harry.core.Run;
+import org.apache.cassandra.harry.sut.SystemUnderTest;
+import org.apache.cassandra.harry.sut.TokenPlacementModel;
+import org.apache.cassandra.harry.sut.injvm.InJVMTokenAwareVisitExecutor;
+import org.apache.cassandra.harry.sut.injvm.InJvmSut;
+import org.apache.cassandra.harry.sut.injvm.QuiescentLocalStateChecker;
+import org.apache.cassandra.harry.visitors.GeneratingVisitor;
+import org.apache.cassandra.harry.visitors.MutatingRowVisitor;
+import org.apache.cassandra.harry.visitors.Visitor;
import org.apache.cassandra.config.CassandraRelevantProperties;
import org.apache.cassandra.distributed.Cluster;
import org.apache.cassandra.distributed.Constants;
import org.apache.cassandra.distributed.api.*;
-import org.apache.cassandra.distributed.fuzz.HarryHelper;
-import org.apache.cassandra.distributed.fuzz.InJVMTokenAwareVisitorExecutor;
-import org.apache.cassandra.distributed.fuzz.InJvmSut;
+import org.apache.cassandra.harry.HarryHelper;
import org.apache.cassandra.distributed.shared.ClusterUtils;
import org.apache.cassandra.distributed.shared.NetworkTopology;
import org.apache.cassandra.locator.InetAddressAndPort;
@@ -57,11 +59,6 @@ public class ResumableStartupTest extends FuzzTestBase
@Test
public void bootstrapWithDeferredJoinTest() throws Throwable
{
- Configuration.ConfigurationBuilder configBuilder = HarryHelper.defaultConfiguration()
- .setPartitionDescriptorSelector(new Configuration.DefaultPDSelectorConfiguration(1, 1))
- .setClusteringDescriptorSelector(HarryHelper.defaultClusteringDescriptorSelectorConfiguration().setMaxPartitionSize(100).build());
-
-
try (Cluster cluster = builder().withNodes(1)
.withTokenSupplier(TokenSupplier.evenlyDistributedTokens(4))
.withNodeIdTopology(NetworkTopology.singleDcNetworkTopology(4, "dc0", "rack0"))
@@ -69,7 +66,8 @@ public class ResumableStartupTest extends FuzzTestBase
.createWithoutStarting())
{
IInvokableInstance cmsInstance = cluster.get(1);
- configBuilder.setSUT(() -> new InJvmSut(cluster));
+ Configuration.ConfigurationBuilder configBuilder = HarryHelper.defaultConfiguration()
+ .setSUT(() -> new InJvmSut(cluster));
Run run = configBuilder.build().createRun();
cmsInstance.config().set("auto_bootstrap", true);
@@ -81,7 +79,10 @@ public class ResumableStartupTest extends FuzzTestBase
cluster.coordinator(1).execute(run.schemaSpec.compile().cql(), ConsistencyLevel.ALL);
ClusterUtils.waitForCMSToQuiesce(cluster, cluster.get(1));
- Visitor visitor = new GeneratingVisitor(run, new InJVMTokenAwareVisitorExecutor(run, MutatingRowVisitor::new, SystemUnderTest.ConsistencyLevel.NODE_LOCAL));
+ TokenPlacementModel.ReplicationFactor rf = new TokenPlacementModel.SimpleReplicationFactor(2);
+ Visitor visitor = new GeneratingVisitor(run, new InJVMTokenAwareVisitExecutor(run, MutatingRowVisitor::new,
+ SystemUnderTest.ConsistencyLevel.NODE_LOCAL,
+ rf));
for (int i = 0; i < WRITES; i++)
visitor.visit();
@@ -92,16 +93,18 @@ public class ResumableStartupTest extends FuzzTestBase
withProperty(CassandraRelevantProperties.TEST_WRITE_SURVEY, true, newInstance::startup);
- // Write with ONE, replicate via pending range mechanism
- visitor = new GeneratingVisitor(run, new InJVMTokenAwareVisitorExecutor(run, MutatingRowVisitor::new, SystemUnderTest.ConsistencyLevel.ONE));
+ // Write with ALL, replicate via pending range mechanism
+ visitor = new GeneratingVisitor(run, new InJVMTokenAwareVisitExecutor(run,
+ MutatingRowVisitor::new,
+ SystemUnderTest.ConsistencyLevel.ONE,
+ rf));
+
for (int i = 0; i < WRITES; i++)
visitor.visit();
Epoch currentEpoch = getClusterMetadataVersion(cmsInstance);
// Quick check that schema changes are possible with nodes in write survey mode (i.e. with ranges locked)
- cluster.coordinator(1).execute("ALTER TABLE " + run.schemaSpec.keyspace + "." + run.schemaSpec.table +
- " WITH comment = 'Schema alterations which do not affect placements should" +
- " not be restricted by in flight operations';",
+ cluster.coordinator(1).execute(String.format("ALTER TABLE %s.%s WITH comment = 'Schema alterations which do not affect placements should not be restricted by in flight operations';", run.schemaSpec.keyspace, run.schemaSpec.table),
ConsistencyLevel.ALL);
final String newAddress = ClusterUtils.getBroadcastAddressHostWithPortString(newInstance);
@@ -144,7 +147,7 @@ public class ResumableStartupTest extends FuzzTestBase
for (int i = 0; i < WRITES; i++)
visitor.visit();
- QuiescentLocalStateChecker model = new QuiescentLocalStateChecker(run);
+ QuiescentLocalStateChecker model = new QuiescentLocalStateChecker(run, new TokenPlacementModel.SimpleReplicationFactor(3));
model.validateAll();
}
}
diff --git a/test/distributed/org/apache/cassandra/distributed/test/log/SimulatedOperation.java b/test/distributed/org/apache/cassandra/distributed/test/log/SimulatedOperation.java
index 57ca794882..932b7c3bba 100644
--- a/test/distributed/org/apache/cassandra/distributed/test/log/SimulatedOperation.java
+++ b/test/distributed/org/apache/cassandra/distributed/test/log/SimulatedOperation.java
@@ -33,6 +33,8 @@ import org.junit.Assert;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token;
+import org.apache.cassandra.distributed.test.log.PlacementSimulator.SimulatedPlacements;
+import org.apache.cassandra.distributed.test.log.PlacementSimulator.Transformations;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.ClusterMetadataService;
import org.apache.cassandra.tcm.MultiStepOperation;
@@ -51,9 +53,7 @@ import org.apache.cassandra.tcm.transformations.UnsafeJoin;
import static org.apache.cassandra.dht.Murmur3Partitioner.LongToken;
import static org.apache.cassandra.distributed.test.log.CMSTestBase.CMSSut;
-import static org.apache.cassandra.distributed.test.log.PlacementSimulator.Node;
-import static org.apache.cassandra.distributed.test.log.PlacementSimulator.SimulatedPlacements;
-import static org.apache.cassandra.distributed.test.log.PlacementSimulator.Transformations;
+import static org.apache.cassandra.harry.sut.TokenPlacementModel.*;
public abstract class SimulatedOperation
{
diff --git a/test/distributed/org/apache/cassandra/distributed/test/log/SystemKeyspaceStorageTest.java b/test/distributed/org/apache/cassandra/distributed/test/log/SystemKeyspaceStorageTest.java
index 7cfa62ec34..9682091bf0 100644
--- a/test/distributed/org/apache/cassandra/distributed/test/log/SystemKeyspaceStorageTest.java
+++ b/test/distributed/org/apache/cassandra/distributed/test/log/SystemKeyspaceStorageTest.java
@@ -30,6 +30,7 @@ import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.QueryProcessor;
import org.apache.cassandra.cql3.UntypedResultSet;
import org.apache.cassandra.distributed.test.ExecUtil;
+import org.apache.cassandra.harry.sut.TokenPlacementModel;
import org.apache.cassandra.tcm.ClusterMetadataService;
import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.tcm.Sealed;
@@ -155,7 +156,7 @@ public class SystemKeyspaceStorageTest extends CoordinatorPathTestBase
@Test
public void bounceNodeBootrappedFromSnapshot() throws Throwable
{
- coordinatorPathTest(new PlacementSimulator.SimpleReplicationFactor(3), (cluster, simulatedCluster) -> {
+ coordinatorPathTest(new TokenPlacementModel.SimpleReplicationFactor(3), (cluster, simulatedCluster) -> {
ClusterMetadataService.instance().sealPeriod();
ClusterMetadataService.instance().log().waitForHighestConsecutive();
ClusterMetadataService.instance().snapshotManager().storeSnapshot(ClusterMetadata.current());
diff --git a/test/distributed/org/apache/cassandra/fuzz/harry/examples/RangeTombstoneBurnTest.java b/test/distributed/org/apache/cassandra/fuzz/harry/examples/RangeTombstoneBurnTest.java
new file mode 100644
index 0000000000..b67813b0a5
--- /dev/null
+++ b/test/distributed/org/apache/cassandra/fuzz/harry/examples/RangeTombstoneBurnTest.java
@@ -0,0 +1,144 @@
+/*
+ * 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.fuzz.harry.examples;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Supplier;
+
+import org.junit.Test;
+
+import org.apache.cassandra.harry.checker.ModelChecker;
+import org.apache.cassandra.harry.ddl.SchemaGenerators;
+import org.apache.cassandra.harry.ddl.SchemaSpec;
+import org.apache.cassandra.harry.dsl.ReplayingHistoryBuilder;
+import org.apache.cassandra.harry.gen.EntropySource;
+import org.apache.cassandra.harry.gen.rng.JdkRandomEntropySource;
+import org.apache.cassandra.harry.model.AgainstSutChecker;
+import org.apache.cassandra.fuzz.harry.integration.model.IntegrationTestBase;
+import org.apache.cassandra.harry.sut.QueryModifyingSut;
+import org.apache.cassandra.harry.sut.SystemUnderTest;
+import org.apache.cassandra.harry.sut.TokenPlacementModel;
+import org.apache.cassandra.harry.tracker.DataTracker;
+import org.apache.cassandra.harry.tracker.DefaultDataTracker;
+
+public class RangeTombstoneBurnTest extends IntegrationTestBase
+{
+ private final long seed = 1;
+ private final int ITERATIONS = 5;
+ private final int STEPS_PER_ITERATION = 100;
+
+ @Test
+ public void rangeTombstoneBurnTest() throws Throwable
+ {
+ Supplier<SchemaSpec> supplier = SchemaGenerators.progression(SchemaGenerators.DEFAULT_SWITCH_AFTER);
+
+ for (int i = 0; i < SchemaGenerators.DEFAULT_RUNS; i++)
+ {
+ SchemaSpec schema = supplier.get();
+ beforeEach();
+ SchemaSpec doubleWriteSchema = schema.cloneWithName(schema.keyspace, schema.keyspace + "_debug");
+
+ sut.schemaChange(schema.compile().cql());
+ sut.schemaChange(doubleWriteSchema.compile().cql());
+
+ QueryModifyingSut sut = new QueryModifyingSut(this.sut,
+ schema.table,
+ doubleWriteSchema.table);
+
+ cluster.get(1).nodetool("disableautocompaction");
+
+ for (int iteration = 0; iteration < ITERATIONS; iteration++)
+ {
+ ModelChecker<ReplayingHistoryBuilder> modelChecker = new ModelChecker<>();
+ EntropySource entropySource = new JdkRandomEntropySource(iteration);
+
+ int maxPartitionSize = entropySource.nextInt(1, 1 << entropySource.nextInt(5, 11));
+
+ int[] partitions = new int[10];
+ for (int j = 0; j < partitions.length; j++)
+ partitions[j] = iteration * partitions.length + j;
+
+ float deleteRowChance = entropySource.nextFloat(0.99f, 1.0f);
+ float deletePartitionChance = entropySource.nextFloat(0.999f, 1.0f);
+ float deleteRangeChance = entropySource.nextFloat(0.95f, 1.0f);
+ float flushChance = entropySource.nextFloat(0.999f, 1.0f);
+ AtomicInteger flushes = new AtomicInteger();
+
+ TokenPlacementModel.ReplicationFactor rf = new TokenPlacementModel.SimpleReplicationFactor(1);
+
+ DataTracker tracker = new DefaultDataTracker();
+ modelChecker.init(new ReplayingHistoryBuilder(seed, maxPartitionSize, STEPS_PER_ITERATION, new DefaultDataTracker(), sut, schema, rf, SystemUnderTest.ConsistencyLevel.ALL))
+ .step((history) -> {
+ int rowIdx = entropySource.nextInt(maxPartitionSize);
+ int partitionIdx = partitions[entropySource.nextInt(partitions.length)];
+ history.visitPartition(partitionIdx).insert(rowIdx);
+ return history;
+ })
+ .step((history) -> entropySource.nextFloat() > deleteRowChance,
+ (history) -> {
+ int partitionIdx = partitions[entropySource.nextInt(partitions.length)];
+ history.visitPartition(partitionIdx).deleteRow();
+ return history;
+ })
+ .step((history) -> entropySource.nextFloat() > deleteRowChance,
+ (history) -> {
+ int partitionIdx = partitions[entropySource.nextInt(partitions.length)];
+ history.visitPartition(partitionIdx).deleteColumns();
+ return history;
+ })
+ .step((history) -> entropySource.nextFloat() > deletePartitionChance,
+ (history) -> {
+ int partitionIdx = partitions[entropySource.nextInt(partitions.length)];
+ history.visitPartition(partitionIdx).deletePartition();
+ return history;
+ })
+ .step((history) -> entropySource.nextFloat() > flushChance,
+ (history) -> {
+ cluster.get(1).nodetool("flush", schema.keyspace, schema.table);
+ flushes.incrementAndGet();
+ return history;
+ })
+ .step((history) -> entropySource.nextFloat() > deleteRangeChance,
+ (history) -> {
+ int partitionIdx = partitions[entropySource.nextInt(partitions.length)];
+ history.visitPartition(partitionIdx).deleteRowSlice();
+ return history;
+ })
+ .step((history) -> entropySource.nextFloat() > deleteRangeChance,
+ (history) -> {
+ int row1 = entropySource.nextInt(maxPartitionSize);
+ int row2 = entropySource.nextInt(maxPartitionSize);
+ int partitionIdx = partitions[entropySource.nextInt(partitions.length)];
+ history.visitPartition(partitionIdx).deleteRowRange(Math.min(row1, row2),
+ Math.max(row1, row2),
+ entropySource.nextBoolean(),
+ entropySource.nextBoolean());
+ return history;
+ })
+ .afterAll((history) -> {
+ // Sanity check
+ history.validate(new AgainstSutChecker(tracker, history.clock(), sut, schema, doubleWriteSchema),
+ partitions);
+ history.validate(partitions);
+ })
+ .run(STEPS_PER_ITERATION, seed);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/distributed/org/apache/cassandra/fuzz/harry/gen/DataGeneratorsTest.java b/test/distributed/org/apache/cassandra/fuzz/harry/gen/DataGeneratorsTest.java
new file mode 100644
index 0000000000..37aa5a5274
--- /dev/null
+++ b/test/distributed/org/apache/cassandra/fuzz/harry/gen/DataGeneratorsTest.java
@@ -0,0 +1,504 @@
+/*
+ * 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.fuzz.harry.gen;
+
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import org.apache.cassandra.harry.ddl.ColumnSpec;
+import org.apache.cassandra.harry.gen.Bijections;
+import org.apache.cassandra.harry.gen.Bytes;
+import org.apache.cassandra.harry.gen.DataGenerators;
+import org.apache.cassandra.harry.gen.EntropySource;
+import org.apache.cassandra.harry.gen.Generator;
+import org.apache.cassandra.harry.gen.StringBijection;
+
+public class DataGeneratorsTest
+{
+ private static final int RUNS = 100;
+ private static final EntropySource rand = EntropySource.forTests(1);
+
+ @Test
+ public void testSingleTypeRoundTrip()
+ {
+ for (ColumnSpec.DataType dt : new ColumnSpec.DataType[]{ColumnSpec.int8Type,
+ ColumnSpec.int16Type,
+ ColumnSpec.int32Type,
+ ColumnSpec.int64Type,
+ ColumnSpec.asciiType,
+ ColumnSpec.floatType,
+ ColumnSpec.doubleType})
+ {
+ for (int i = 0; i < RUNS; i++)
+ {
+ DataGenerators.SinglePartKeyGenerator gen = new DataGenerators.SinglePartKeyGenerator(Collections.singletonList(ColumnSpec.ck("ck0", dt, false)));
+ long descriptor = rand.next();
+ descriptor = gen.adjustEntropyDomain(descriptor);
+ Assert.assertEquals(descriptor,
+ gen.deflate(gen.inflate(descriptor)));
+ }
+ }
+ }
+
+ @Test
+ public void testRequiredBytes()
+ {
+ testRequiredBytes(sizes(4, 1),
+ ColumnSpec.int32Type, ColumnSpec.int8Type);
+ testRequiredBytes(sizes(7, 1),
+ ColumnSpec.int64Type, ColumnSpec.int8Type);
+ testRequiredBytes(sizes(4, 4),
+ ColumnSpec.int32Type, ColumnSpec.int64Type);
+ testRequiredBytes(sizes(4, 4),
+ ColumnSpec.int32Type, ColumnSpec.int32Type);
+ testRequiredBytes(sizes(4, 3),
+ ColumnSpec.int32Type, ColumnSpec.floatType);
+ testRequiredBytes(sizes(4, 4),
+ ColumnSpec.int64Type, ColumnSpec.int64Type);
+ testRequiredBytes(sizes(4, 2, 2),
+ ColumnSpec.int64Type, ColumnSpec.int16Type, ColumnSpec.int32Type);
+ testRequiredBytes(sizes(6, 1, 1),
+ ColumnSpec.int64Type, ColumnSpec.int8Type, ColumnSpec.int8Type);
+ testRequiredBytes(sizes(4, 2, 2),
+ ColumnSpec.int32Type, ColumnSpec.int32Type, ColumnSpec.int64Type);
+ testRequiredBytes(sizes(4, 2, 2),
+ ColumnSpec.int32Type, ColumnSpec.int32Type, ColumnSpec.int64Type);
+ testRequiredBytes(sizes(1, 5, 2),
+ ColumnSpec.int8Type, ColumnSpec.asciiType, ColumnSpec.int64Type);
+ testRequiredBytes(sizes(1, 1, 6),
+ ColumnSpec.int8Type, ColumnSpec.int8Type, ColumnSpec.int64Type);
+ testRequiredBytes(sizes(2, 2, 2, 2),
+ ColumnSpec.int64Type, ColumnSpec.int32Type, ColumnSpec.int32Type, ColumnSpec.int32Type);
+ testRequiredBytes(sizes(1, 3, 2, 2),
+ ColumnSpec.int8Type, ColumnSpec.int32Type, ColumnSpec.int32Type, ColumnSpec.int32Type);
+ testRequiredBytes(sizes(1, 3, 2, 2),
+ ColumnSpec.int8Type, ColumnSpec.int32Type, ColumnSpec.int32Type, ColumnSpec.int32Type);
+ testRequiredBytes(sizes(1, 3, 2, 2),
+ ColumnSpec.int8Type, ColumnSpec.int32Type, ColumnSpec.int32Type, ColumnSpec.int32Type, ColumnSpec.int64Type);
+ }
+
+ private static void testRequiredBytes(int[] sizes,
+ ColumnSpec.DataType<?>... types)
+ {
+ int sum = 0;
+ for (int size : sizes)
+ sum += size;
+ Assert.assertTrue(sum > 0);
+ Assert.assertTrue(sum <= 8);
+
+ List<ColumnSpec<?>> columns = new ArrayList<>(types.length);
+ for (int i = 0; i < types.length; i++)
+ columns.add(ColumnSpec.ck("r" + i, types[i], false));
+ Assert.assertArrayEquals(columns.toString(),
+ sizes,
+ DataGenerators.requiredBytes(columns));
+ }
+
+ @Test
+ public void testSliceStitch()
+ {
+ for (int i = 2; i < 5; i++)
+ {
+ Iterator<ColumnSpec.DataType[]> iter = permutations(i,
+ ColumnSpec.DataType.class,
+ ColumnSpec.int8Type,
+ ColumnSpec.asciiType,
+ ColumnSpec.int16Type,
+ ColumnSpec.int32Type,
+ ColumnSpec.int32Type,
+ ColumnSpec.floatType,
+ ColumnSpec.doubleType);
+ while (iter.hasNext())
+ {
+ testSliceStitch(iter.next());
+ }
+ }
+ }
+
+ private static void testSliceStitch(ColumnSpec.DataType... types)
+ {
+ List<ColumnSpec<?>> spec = new ArrayList<>(types.length);
+ for (int i = 0; i < types.length; i++)
+ spec.add(ColumnSpec.ck("r" + i, types[i], false));
+ DataGenerators.MultiPartKeyGenerator gen = new DataGenerators.MultiPartKeyGenerator(spec);
+
+ for (int i = 0; i < RUNS; i++)
+ {
+ long orig = gen.adjustEntropyDomain(rand.next());
+ long[] sliced = gen.slice(orig);
+ long stitched = gen.stitch(sliced);
+ Assert.assertEquals(String.format("Orig: %s. Stitched: %s",
+ Long.toHexString(orig),
+ Long.toHexString(stitched)),
+ orig, stitched);
+ }
+ }
+
+ @Test
+ public void testKeyGenerators()
+ {
+ for (int i = 1; i < 5; i++)
+ {
+ for (boolean asReversed : new boolean[]{ false, true })
+ {
+ Iterator<ColumnSpec.DataType[]> iter = permutations(i,
+ ColumnSpec.DataType.class,
+ ColumnSpec.int8Type,
+ ColumnSpec.asciiType,
+ ColumnSpec.int16Type,
+ ColumnSpec.int32Type,
+ ColumnSpec.int64Type,
+ ColumnSpec.floatType,
+ ColumnSpec.doubleType
+ );
+
+ while (iter.hasNext())
+ {
+ ColumnSpec.DataType[] types = iter.next();
+ try
+ {
+ testKeyGenerators(asReversed, types);
+ }
+ catch (Throwable t)
+ {
+ throw new AssertionError("Caught error for the type combination " + Arrays.toString(types), t);
+ }
+ }
+ }
+ }
+ }
+
+ @Ignore
+ @Test // this one is mostly useful when the above test fails and you need a quicker turnaround
+ public void testSomeKeyGenerators()
+ {
+ Iterator<ColumnSpec.DataType[]> iter = Collections.singletonList(new ColumnSpec.DataType[]{ ColumnSpec.int32Type, ColumnSpec.int32Type }).iterator();
+
+ while (iter.hasNext())
+ {
+ ColumnSpec.DataType[] types = iter.next();
+ try
+ {
+ testKeyGenerators(true, types);
+ testKeyGenerators(false, types);
+ }
+ catch (Throwable t)
+ {
+ throw new AssertionError("Caught error for the type combination " + Arrays.toString(types), t);
+ }
+ }
+ }
+
+ static void testKeyGenerators(boolean reversed, ColumnSpec.DataType<?>... types)
+ {
+ List<ColumnSpec<?>> spec = new ArrayList<>(types.length);
+ for (int i = 0; i < types.length; i++)
+ spec.add(ColumnSpec.ck("r" + i, types[i], reversed));
+
+ DataGenerators.KeyGenerator keyGenerator = DataGenerators.createKeyGenerator(spec);
+
+ for (int i = 0; i < RUNS; i++)
+ {
+ testKeyGenerators(rand.next(), rand.next(), keyGenerator);
+ // test some edge cases
+ testKeyGenerators(0, 0, keyGenerator);
+ testKeyGenerators(0xffffffffffffffffL, 0xffffffffffffffffL, keyGenerator);
+ testKeyGenerators(keyGenerator.minValue(), keyGenerator.maxValue(), keyGenerator);
+ testKeyGenerators(0, keyGenerator.minValue(), keyGenerator);
+ testKeyGenerators(0, keyGenerator.maxValue(), keyGenerator);
+ long descriptor = rand.next();
+ testKeyGenerators(descriptor, 0, keyGenerator);
+ testKeyGenerators(descriptor, keyGenerator.minValue(), keyGenerator);
+ testKeyGenerators(descriptor, keyGenerator.maxValue(), keyGenerator);
+ testKeyGenerators(descriptor, 0xffffffffffffffffL, keyGenerator);
+ testKeyGenerators(descriptor, descriptor + 1, keyGenerator);
+ testKeyGenerators(descriptor, descriptor - 1, keyGenerator);
+ testKeyGenerators(descriptor, descriptor, keyGenerator);
+ }
+
+ // Fixed prefix, tests sign inversion of subsequent values
+ if (types.length > 1)
+ {
+
+ long pattern = Bytes.bytePatternFor(Long.BYTES - DataGenerators.requiredBytes(spec)[0]);
+ for (int i = 0; i < RUNS; i++)
+ {
+ long descriptor = rand.next();
+ long descriptor2 = (descriptor & ~pattern) | (rand.next() & pattern);
+ testKeyGenerators(descriptor, descriptor2, keyGenerator);
+ }
+
+ }
+ }
+
+ static void testKeyGenerators(long descriptor1, long descriptor2, DataGenerators.KeyGenerator keyGenerator)
+ {
+ descriptor1 = keyGenerator.adjustEntropyDomain(descriptor1);
+ descriptor2 = keyGenerator.adjustEntropyDomain(descriptor2);
+ Object[] value1 = keyGenerator.inflate(descriptor1);
+ Object[] value2 = keyGenerator.inflate(descriptor2);
+
+ assertDescriptorsEqual(descriptor1,
+ keyGenerator.deflate(value1));
+ assertDescriptorsEqual(descriptor2,
+ keyGenerator.deflate(value2));
+
+ Assert.assertEquals(String.format("%s %s %s and %s %s %s have different order. ",
+ Arrays.toString(value1),
+ toSignString(compare(value1, value2, keyGenerator.columns)),
+ Arrays.toString(value2),
+ Long.toHexString(descriptor1),
+ toSignString(Long.compare(descriptor1, descriptor2)),
+ Long.toHexString(descriptor2)),
+ normalize(Long.compare(descriptor1, descriptor2)),
+ compare(value1, value2, keyGenerator.columns));
+ }
+
+ private static void assertDescriptorsEqual(long l, long r)
+ {
+ Assert.assertEquals(String.format("Expected %d (0x%s), but got %d (0x%s)",
+ l, Long.toHexString(l), r, Long.toHexString(r)),
+ l, r);
+ }
+
+ @Test
+ public void int8GeneratorTest()
+ {
+ testInverse(Bijections.INT8_GENERATOR);
+ }
+
+ @Test
+ public void int16GeneratorTest()
+ {
+ testInverse(Bijections.INT16_GENERATOR);
+ }
+
+ @Test
+ public void int32GeneratorTest()
+ {
+ testInverse(Bijections.INT32_GENERATOR);
+ }
+
+ @Test
+ public void int64GeneratorTest()
+ {
+ testInverse(Bijections.INT64_GENERATOR);
+ testOrderPreserving(Bijections.INT64_GENERATOR);
+ testInverse(new Bijections.ReverseBijection(Bijections.INT64_GENERATOR));
+ testOrderPreserving(new Bijections.ReverseBijection(Bijections.INT64_GENERATOR), true);
+ }
+
+ @Test
+ public void booleanGenTest()
+ {
+ testInverse(Bijections.BOOLEAN_GENERATOR);
+ testOrderPreserving(Bijections.BOOLEAN_GENERATOR);
+ }
+
+ @Test
+ public void floatGeneratorTest()
+ {
+ testInverse(Bijections.FLOAT_GENERATOR);
+ testOrderPreserving(Bijections.FLOAT_GENERATOR, Float::compareTo);
+ }
+
+ @Test
+ public void doubleGeneratorTest()
+ {
+ testInverse(Bijections.DOUBLE_GENERATOR);
+ testOrderPreserving(Bijections.DOUBLE_GENERATOR);
+ }
+
+
+ @Test
+ public void stringGenTest()
+ {
+ testInverse(new StringBijection());
+ testOrderPreserving(new StringBijection());
+ }
+
+ public static <T> void testInverse(Bijections.Bijection<T> gen)
+ {
+ test(gen,
+ (v) -> Assert.assertEquals(gen.adjustEntropyDomain(v.descriptor), gen.deflate(v.value)));
+ }
+
+ public static <T extends Comparable> void testOrderPreserving(Bijections.Bijection<T> gen)
+ {
+ testOrderPreserving(gen, false);
+ }
+
+ public static <T extends Comparable> void testOrderPreserving(Bijections.Bijection<T> gen, boolean reverse)
+ {
+ test(gen, gen,
+ (v1, v2) -> {
+ long v1Descriptor = gen.adjustEntropyDomain(v1.descriptor);
+ long v2Descriptor = gen.adjustEntropyDomain(v2.descriptor);
+ Assert.assertEquals(String.format("%s (%s) and %s (%s) sort wrong",
+ v1.value,
+ Long.toHexString(v1Descriptor),
+ v2.value,
+ Long.toHexString(v2Descriptor)),
+ normalize(Long.compare(v1Descriptor, v2Descriptor)) * (reverse ? -1 : 1),
+ normalize(v1.value.compareTo(v2.value)));
+ });
+ }
+
+ public static <T extends Comparable> void testOrderPreserving(Bijections.Bijection<T> gen, Comparator<T> comparator)
+ {
+ test(gen, gen,
+ (v1, v2) -> Assert.assertEquals(normalize(Long.compare(gen.adjustEntropyDomain(v1.descriptor),
+ gen.adjustEntropyDomain(v2.descriptor))),
+ normalize(comparator.compare(v1.value, v2.value))));
+ }
+
+ public static <T1> void test(Bijections.Bijection<T1> gen1,
+ Consumer<Generator.Value<T1>> validate)
+ {
+
+ for (int i = 0; i < RUNS; i++)
+ {
+ long descriptor1 = rand.next();
+ validate.accept(new Generator.Value<T1>(descriptor1, gen1.inflate(gen1.adjustEntropyDomain(descriptor1))));
+ }
+ }
+
+ public static <T1, T2> void test(Bijections.Bijection<T1> gen1,
+ Bijections.Bijection<T2> gen2,
+ BiConsumer<Generator.Value<T1>, Generator.Value<T2>> validate)
+ {
+
+ for (int i = 0; i < RUNS; i++)
+ {
+ long descriptor1 = rand.next();
+ long descriptor2 = rand.next();
+ validate.accept(new Generator.Value<T1>(descriptor1, gen1.inflate(gen1.adjustEntropyDomain(descriptor1))),
+ new Generator.Value<T2>(descriptor2, gen2.inflate(gen1.adjustEntropyDomain(descriptor2))));
+ }
+ }
+
+ public static int[] sizes(int... ints)
+ {
+ return ints;
+ }
+
+
+ public static String toSignString(int l)
+ {
+ if (l == 0)
+ return "=";
+ else if (l > 0)
+ return ">";
+ return "<";
+ }
+ public static int normalize(int l)
+ {
+ if (l == 0)
+ return 0;
+ if (l > 0)
+ return 1;
+ else
+ return -1;
+ }
+
+ static int compare(Object[] a, Object[] b, List<ColumnSpec<?>> spec)
+ {
+ assert a.length == b.length;
+ for (int i = 0; i < a.length; i++)
+ {
+ Comparable comparableA = (Comparable) a[i];
+ Comparable comparableB = (Comparable) b[i];
+
+ int cmp = comparableA.compareTo(comparableB);
+ if (cmp != 0)
+ {
+ if (spec.get(i).isReversed())
+ cmp *= -1;
+
+ return cmp < 0 ? -1 : 1;
+ }
+ }
+ return 0;
+ }
+
+ public static <T> Iterator<T[]> permutations(int size, Class<T> klass, T... values)
+ {
+ int[] cursors = new int[size];
+ return new Iterator<T[]>()
+ {
+ int left = 0;
+ T[] next = fromCursors();
+
+ public boolean hasNext()
+ {
+ for (int i = cursors.length - 1; i >= 0; i--)
+ {
+ if (cursors[i] < values.length - 1)
+ return true;
+ }
+ return false;
+ }
+
+ public T[] computeNext()
+ {
+ cursors[left]++;
+
+ for (int i = 0; i < cursors.length; i++)
+ {
+ if (cursors[i] == values.length)
+ {
+ cursors[i] = 0;
+ cursors[i + 1]++;
+ }
+ }
+
+ return fromCursors();
+ }
+
+ public T[] next()
+ {
+ if (next == null)
+ next = computeNext();
+
+ T[] ret = next;
+ next = null;
+ return ret;
+ }
+
+ public T[] fromCursors()
+ {
+ T[] res = (T[]) Array.newInstance(klass, cursors.length);
+ for (int i = 0; i < cursors.length; i++)
+ res[i] = values[cursors[i]];
+ return res;
+ }
+ };
+ }
+}
\ No newline at end of file
diff --git a/test/distributed/org/apache/cassandra/fuzz/harry/gen/EntropySourceTest.java b/test/distributed/org/apache/cassandra/fuzz/harry/gen/EntropySourceTest.java
new file mode 100644
index 0000000000..a2b53306d0
--- /dev/null
+++ b/test/distributed/org/apache/cassandra/fuzz/harry/gen/EntropySourceTest.java
@@ -0,0 +1,158 @@
+/*
+ * 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.fuzz.harry.gen;
+
+import java.util.Random;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.cassandra.harry.gen.EntropySource;
+import org.apache.cassandra.harry.gen.distribution.Distribution;
+import org.apache.cassandra.harry.gen.rng.PCGFastPure;
+import org.apache.cassandra.harry.gen.rng.PcgRSUFast;
+import org.apache.cassandra.harry.model.OpSelectors;
+
+public class EntropySourceTest
+{
+ private static int RUNS = 100000;
+
+ @Test
+ public void testScale()
+ {
+ Random rand = new Random();
+ for (int cycle = 0; cycle < RUNS; cycle++)
+ {
+ int a = rand.nextInt(100);
+ int b = rand.nextInt(100);
+ while (a == b)
+ b = rand.nextInt(100);
+
+ int min = Math.min(a, b);
+ int max = Math.max(a, b);
+ long[] cardinality = new long[max - min];
+ for (int i = 0; i < 100000; i++)
+ {
+ long rnd = rand.nextLong();
+ long scaled = Distribution.ScaledDistribution.scale(rnd, min, max);
+ cardinality[(int) scaled - min]++;
+ }
+
+ for (long c : cardinality)
+ Assert.assertTrue(c > 0);
+ }
+ }
+
+ @Test
+ public void testShuffleUnshuffle()
+ {
+ Random rnd = new Random();
+
+ for (int i = 1; i < RUNS; i++)
+ {
+ long l = rnd.nextLong();
+ Assert.assertEquals(l, PCGFastPure.unshuffle(PCGFastPure.shuffle(l)));
+ }
+ }
+
+ @Test
+ public void testImmutableRng()
+ {
+ int size = 5;
+ OpSelectors.PureRng rng = new OpSelectors.PCGFast(1);
+ for (int stream = 1; stream < RUNS; stream++)
+ {
+ long[] generated = new long[size];
+ for (int i = 0; i < size; i++)
+ generated[i] = rng.randomNumber(i, stream);
+
+ Assert.assertEquals(0, rng.sequenceNumber(generated[0], stream));
+ Assert.assertEquals(generated[1], rng.next(generated[0], stream));
+
+ for (int i = 1; i < size; i++)
+ {
+ Assert.assertEquals(generated[i], rng.next(generated[i - 1], stream));
+ Assert.assertEquals(generated[i - 1], rng.prev(generated[i], stream));
+ Assert.assertEquals(i, rng.sequenceNumber(generated[i], stream));
+ }
+ }
+ }
+
+ @Test
+ public void testSequenceNumber()
+ {
+ int size = 5;
+ OpSelectors.PureRng rng = new OpSelectors.PCGFast(1);
+ for (int stream = 1; stream < RUNS; stream++)
+ {
+ for (int i = 0; i < size; i++)
+ Assert.assertEquals(i, rng.sequenceNumber(rng.randomNumber(i, stream), stream));
+ }
+ }
+
+ @Test
+ public void seekTest()
+ {
+ PcgRSUFast rand = new PcgRSUFast(1, 1);
+ long first = rand.next();
+ long last = 0;
+ for (int i = 0; i < 10; i++)
+ last = rand.next();
+
+ rand.advance(-11);
+ Assert.assertEquals(first, rand.next());
+
+ rand.advance(9);
+ Assert.assertEquals(last, rand.next());
+ Assert.assertEquals(first, rand.nextAt(0));
+ Assert.assertEquals(last, rand.nextAt(10));
+ Assert.assertEquals(-10, rand.distance(first));
+ }
+
+ @Test
+ public void shuffleUnshuffleTest()
+ {
+ Random rnd = new Random();
+ for (int i = 0; i < RUNS; i++)
+ {
+ long a = rnd.nextLong();
+ Assert.assertEquals(a, PCGFastPure.unshuffle(PCGFastPure.shuffle(a)));
+ }
+ }
+
+ @Test
+ public void testIntBetween()
+ {
+ EntropySource rng = new PcgRSUFast(System.currentTimeMillis(), 0);
+
+ int a = 0;
+ int b = 50;
+ int[] cardinality = new int[b - a];
+ for (int i = 0; i < RUNS; i++)
+ {
+ int min = Math.min(a, b);
+ int max = Math.max(a, b);
+ cardinality[rng.nextInt(min, max - 1) - min]++;
+ }
+
+ // Extremely improbable yet possible that some of the values won't be generated
+ for (int i = 0; i < cardinality.length; i++)
+ Assert.assertTrue(cardinality[i] > 0);
+ }
+}
diff --git a/test/distributed/org/apache/cassandra/distributed/fuzz/FixedSchemaProviderConfiguration.java b/test/distributed/org/apache/cassandra/fuzz/harry/gen/ExtensionsTest.java
similarity index 55%
rename from test/distributed/org/apache/cassandra/distributed/fuzz/FixedSchemaProviderConfiguration.java
rename to test/distributed/org/apache/cassandra/fuzz/harry/gen/ExtensionsTest.java
index 2e03903542..d6d331eed5 100644
--- a/test/distributed/org/apache/cassandra/distributed/fuzz/FixedSchemaProviderConfiguration.java
+++ b/test/distributed/org/apache/cassandra/fuzz/harry/gen/ExtensionsTest.java
@@ -16,28 +16,29 @@
* limitations under the License.
*/
-package org.apache.cassandra.distributed.fuzz;
+package org.apache.cassandra.fuzz.harry.gen;
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonTypeName;
-import harry.core.Configuration;
-import harry.ddl.SchemaSpec;
-import harry.model.sut.SystemUnderTest;
+import java.util.function.Supplier;
-@JsonTypeName("fixed")
-public class FixedSchemaProviderConfiguration implements Configuration.SchemaProviderConfiguration
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.cassandra.harry.gen.EntropySource;
+import org.apache.cassandra.harry.gen.Generators;
+
+public class ExtensionsTest
{
- private final SchemaSpec schemaSpec;
- @JsonCreator
- public FixedSchemaProviderConfiguration(SchemaSpec schemaSpec)
+ @Test
+ public void testPick()
{
- this.schemaSpec = schemaSpec;
- }
+ Supplier<Integer> gen = Generators.pick(101, 102, 103, 104, 105).bind(EntropySource.forTests());
- @Override
- public SchemaSpec make(long l, SystemUnderTest systemUnderTest)
- {
- return this.schemaSpec;
+ int[] counts = new int[5];
+ for (int i = 0; i < 1000; i++)
+ counts[gen.get() - 101] += 1;
+
+ // It is possible, however very improbable we won't hit each one at least once
+ for (int count: counts) Assert.assertTrue(count > 0);
}
}
diff --git a/test/distributed/org/apache/cassandra/fuzz/harry/gen/SurjectionsTest.java b/test/distributed/org/apache/cassandra/fuzz/harry/gen/SurjectionsTest.java
new file mode 100644
index 0000000000..b749c4140a
--- /dev/null
+++ b/test/distributed/org/apache/cassandra/fuzz/harry/gen/SurjectionsTest.java
@@ -0,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.fuzz.harry.gen;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.cassandra.harry.gen.EntropySource;
+import org.apache.cassandra.harry.gen.Surjections;
+import org.apache.cassandra.harry.gen.rng.PcgRSUFast;
+
+public class SurjectionsTest
+{
+ private static int RUNS = 1000000;
+
+ @Test
+ public void weightedTest()
+ {
+ int[] weights = new int[] {50, 40, 10};
+
+ Surjections.Surjection<String> gen = Surjections.weighted(Surjections.weights(weights),
+ "a", "b", "c");
+
+ Map<String, Integer> frequencies = new HashMap<>();
+ EntropySource rng = new PcgRSUFast(System.currentTimeMillis(), 0);
+
+ for (int i = 0; i < RUNS; i++)
+ {
+ String s = gen.inflate(rng.next());
+ frequencies.compute(s, (s1, i1) -> {
+ if (i1 == null)
+ return 1;
+ else
+ return i1 + 1;
+ });
+ }
+
+ Assert.assertEquals(frequencies.get("a") / 10000, weights[0], 1);
+ Assert.assertEquals(frequencies.get("b") / 10000, weights[1], 1);
+ Assert.assertEquals(frequencies.get("c") / 10000, weights[2], 1);
+ }
+
+
+}
diff --git a/test/distributed/org/apache/cassandra/fuzz/harry/integration/QuickTheoriesAdapter.java b/test/distributed/org/apache/cassandra/fuzz/harry/integration/QuickTheoriesAdapter.java
new file mode 100644
index 0000000000..ecb3513278
--- /dev/null
+++ b/test/distributed/org/apache/cassandra/fuzz/harry/integration/QuickTheoriesAdapter.java
@@ -0,0 +1,100 @@
+/*
+ * 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.fuzz.harry.integration;
+
+import org.apache.cassandra.harry.gen.Generator;
+import org.apache.cassandra.harry.gen.EntropySource;
+import org.apache.cassandra.harry.gen.rng.RngUtils;
+import org.quicktheories.core.Gen;
+import org.quicktheories.core.RandomnessSource;
+import org.quicktheories.impl.Constraint;
+
+public class QuickTheoriesAdapter
+{
+ public static <T> Gen<T> convert(Generator<T> generator)
+ {
+ return new Gen<T>()
+ {
+ private final RandomnessSourceAdapter<T> adapter = new RandomnessSourceAdapter<>();
+
+ public T generate(RandomnessSource randomnessSource)
+ {
+ return adapter.generate(randomnessSource, generator);
+ }
+ };
+ }
+
+ public static class RandomnessSourceAdapter<T> implements EntropySource
+ {
+ private RandomnessSource rnd;
+
+ public long next()
+ {
+ return rnd.next(Constraint.none());
+ }
+
+ public void seed(long seed)
+ {
+ throw new RuntimeException("Seed is not settable");
+ }
+
+ public EntropySource derive()
+ {
+ return new RandomnessSourceAdapter<>();
+ }
+
+ public int nextInt()
+ {
+ return RngUtils.asInt(next());
+ }
+
+ public int nextInt(int max)
+ {
+ return RngUtils.asInt(next(), max);
+ }
+
+ public int nextInt(int min, int max)
+ {
+ return RngUtils.asInt(next(), min, max);
+ }
+
+ public long nextLong(long min, long max)
+ {
+ return RngUtils.trim(next(), min, max);
+ }
+
+ public float nextFloat()
+ {
+ return RngUtils.asFloat(next());
+ }
+
+ public boolean nextBoolean()
+ {
+ return RngUtils.asBoolean(next());
+ }
+
+ public T generate(RandomnessSource rnd, Generator<T> generate)
+ {
+ this.rnd = rnd;
+ T value = generate.generate(this);
+ this.rnd = null;
+ return value;
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/distributed/org/apache/cassandra/fuzz/harry/integration/ddl/SchemaGenTest.java b/test/distributed/org/apache/cassandra/fuzz/harry/integration/ddl/SchemaGenTest.java
new file mode 100644
index 0000000000..8334ae4911
--- /dev/null
+++ b/test/distributed/org/apache/cassandra/fuzz/harry/integration/ddl/SchemaGenTest.java
@@ -0,0 +1,216 @@
+/*
+ * 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.fuzz.harry.integration.ddl;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.cassandra.fuzz.harry.integration.QuickTheoriesAdapter;
+import org.apache.cassandra.harry.ddl.ColumnSpec;
+import org.apache.cassandra.harry.ddl.SchemaGenerators;
+import org.apache.cassandra.harry.ddl.SchemaSpec;
+import org.apache.cassandra.harry.gen.Generator;
+import org.apache.cassandra.harry.operations.CompiledStatement;
+
+import org.apache.cassandra.harry.operations.Query;
+import org.apache.cassandra.harry.util.TestRunner;
+import org.apache.cassandra.cql3.CQLTester;
+import org.apache.cassandra.db.Keyspace;
+import org.apache.cassandra.schema.ColumnMetadata;
+import org.apache.cassandra.schema.TableMetadata;
+import org.apache.cassandra.utils.Pair;
+import org.quicktheories.QuickTheory;
+import org.quicktheories.core.Gen;
+
+import static org.quicktheories.generators.SourceDSL.integers;
+
+public class SchemaGenTest extends CQLTester
+{
+ private static final int CYCLES = 10;
+
+ // TODO: compact storage tests
+ @Test
+ public void testSelectForwardAndReverseIteration() throws Throwable
+ {
+ Generator<SchemaSpec> gen = new SchemaGenerators.Builder(KEYSPACE).partitionKeyColumnCount(1, 4)
+ .clusteringColumnCount(1, 10)
+ .regularColumnCount(0, 10)
+ .staticColumnCount(0, 10)
+ .generator();
+
+
+ TestRunner.test(gen,
+ schemaDefinition -> {
+ String tableDef = schemaDefinition.compile().cql();
+ createTable(tableDef);
+
+ try
+ {
+ CompiledStatement statement = Query.selectPartition(schemaDefinition, 1, false).toSelectStatement();
+ execute(statement.cql(), statement.bindings());
+ statement = Query.selectPartition(schemaDefinition, 1, true).toSelectStatement();
+ execute(statement.cql(), statement.bindings());
+ }
+ catch (Throwable t)
+ {
+ throw new AssertionError("Exception caught", t);
+ }
+ });
+ }
+
+ @Test
+ public void createTableRoundTrip() throws Throwable
+ {
+ Generator<SchemaSpec> gen = new SchemaGenerators.Builder(KEYSPACE).partitionKeyColumnCount(1, 10)
+ .clusteringColumnCount(1, 10)
+ .regularColumnCount(0, 10)
+ .staticColumnCount(0, 10)
+ .generator();
+
+ TestRunner.test(gen,
+ schemaDefinition -> {
+ String tableDef = schemaDefinition.compile().cql();
+ createTable(KEYSPACE, tableDef);
+ TableMetadata tableMetadata = Keyspace.open(KEYSPACE).getColumnFamilyStore(schemaDefinition.table).metadata.get();
+ compareColumns(schemaDefinition.partitionKeys, tableMetadata.partitionKeyColumns());
+ compareColumns(schemaDefinition.clusteringKeys, tableMetadata.clusteringColumns());
+ compareColumns(schemaDefinition.regularColumns, tableMetadata.regularColumns());
+ compareColumns(schemaDefinition.staticColumns, tableMetadata.staticColumns());
+ });
+ }
+
+ @Test
+ public void testReverseComparator()
+ {
+ SchemaSpec spec = new SchemaSpec(KEYSPACE, "tbl1",
+ Arrays.asList(ColumnSpec.pk("pk1", ColumnSpec.asciiType),
+ ColumnSpec.pk("pk2", ColumnSpec.int64Type)),
+ Arrays.asList(ColumnSpec.ck("ck1", ColumnSpec.asciiType, true),
+ ColumnSpec.ck("ck2", ColumnSpec.int64Type, false)),
+ Arrays.asList(ColumnSpec.regularColumn("v1", ColumnSpec.asciiType),
+ ColumnSpec.regularColumn("v2", ColumnSpec.asciiType),
+ ColumnSpec.regularColumn("v3", ColumnSpec.int64Type),
+ ColumnSpec.regularColumn("v4", ColumnSpec.int64Type)),
+ Arrays.asList(ColumnSpec.staticColumn("static1", ColumnSpec.asciiType),
+ ColumnSpec.staticColumn("static2", ColumnSpec.int64Type)));
+
+
+ String tableDef = spec.compile().cql();
+ createTable(KEYSPACE, tableDef);
+ TableMetadata tableMetadata = Keyspace.open(KEYSPACE).getColumnFamilyStore(spec.table).metadata.get();
+ compareColumns(spec.partitionKeys, tableMetadata.partitionKeyColumns());
+ compareColumns(spec.clusteringKeys, tableMetadata.clusteringColumns());
+ compareColumns(spec.regularColumns, tableMetadata.regularColumns());
+ compareColumns(spec.staticColumns, tableMetadata.staticColumns());
+ }
+
+
+ @Test
+ public void testSchemaGeneration()
+ {
+ Gen<Pair<Integer, Integer>> ckCounts = integers().between(0, 4).zip(integers().between(0, 6), Pair::create);
+ Gen<Pair<Integer, Integer>> regCounts = integers().between(0, 4).zip(integers().between(0, 6), Pair::create);
+// Gen<Pair<Integer, Integer>> staticCounts = integers().between(0, 4).zip(integers().between(0, 6), Pair::create);
+ Gen<Pair<Integer, Integer>> pkCounts = integers().between(1, 4).zip(integers().between(0, 6), Pair::create);
+
+ Gen<SchemaGenerationInputs> inputs = pkCounts.zip(ckCounts, regCounts,
+ (pks, cks, regs) ->
+ new SchemaGenerationInputs(pks.left, pks.left + pks.right,
+ cks.left, cks.left + cks.right,
+ regs.left, regs.left + regs.right));
+
+ Gen<Pair<SchemaGenerationInputs, SchemaSpec>> schemaAndInputs = inputs.flatMap(input -> {
+ Generator<SchemaSpec> gen = new SchemaGenerators.Builder("test")
+ .partitionKeyColumnCount(input.minPk, input.maxPk)
+ .clusteringColumnCount(input.minCks, input.maxCks)
+ .regularColumnCount(input.minRegs, input.maxRegs)
+ .generator();
+
+ return QuickTheoriesAdapter.convert(gen).map(schema -> Pair.create(input, schema));
+ });
+
+ qt().forAll(schemaAndInputs)
+ .check(schemaAndInput -> {
+ SchemaGenerationInputs input = schemaAndInput.left;
+ SchemaSpec schema = schemaAndInput.right;
+
+ return schema.partitionKeys.size() <= input.maxPk && schema.partitionKeys.size() >= input.minPk &&
+ schema.clusteringKeys.size() <= input.maxCks && schema.clusteringKeys.size() >= input.minCks &&
+ schema.regularColumns.size() <= input.maxRegs && schema.regularColumns.size() >= input.minRegs;
+ });
+ }
+
+ private static class SchemaGenerationInputs {
+ private final int minPk;
+ private final int maxPk;
+ private final int minCks;
+ private final int maxCks;
+ private final int minRegs;
+ private final int maxRegs;
+
+ public SchemaGenerationInputs(int minPk, int maxPk, int minCks, int maxCks, int minRegs, int maxRegs)
+ {
+ this.minPk = minPk;
+ this.maxPk = maxPk;
+ this.minCks = minCks;
+ this.maxCks = maxCks;
+ this.minRegs = minRegs;
+ this.maxRegs = maxRegs;
+ }
+ }
+
+ private static boolean compareColumns(Collection<ColumnSpec<?>> expectedColl, Collection<ColumnMetadata> actualColl)
+ {
+ List<ColumnSpec<?>> expectedSorted = new ArrayList<>(expectedColl);
+ expectedSorted.sort(Comparator.comparing(Object::toString));
+ List<ColumnMetadata> actualSorted = new ArrayList<>(actualColl);
+ actualSorted.sort(Comparator.comparing(Object::toString));
+
+ Iterator<ColumnSpec<?>> expectedIter = expectedSorted.iterator();
+ Iterator<ColumnMetadata> actualIter = actualSorted.iterator();
+
+ while (expectedIter.hasNext() && actualIter.hasNext())
+ {
+ ColumnSpec expected = expectedIter.next();
+ ColumnMetadata actual = actualIter.next();
+
+ Assert.assertEquals(expected.kind.toString(), actual.kind.toString());
+ Assert.assertEquals(expected.name, actual.name.toString());
+ Assert.assertEquals(expected.type.toString(), actual.type.asCQL3Type().toString());
+ }
+
+ Assert.assertEquals(String.format("Collections %s and %s have different sizes", expectedColl, actualColl),
+ expectedIter.hasNext(), actualIter.hasNext());
+ return true;
+ }
+
+ public static QuickTheory qt()
+ {
+ return QuickTheory.qt()
+ .withExamples(CYCLES)
+ .withShrinkCycles(0);
+ }
+}
\ No newline at end of file
diff --git a/test/distributed/org/apache/cassandra/fuzz/harry/integration/dsl/HistoryBuilderIntegrationTest.java b/test/distributed/org/apache/cassandra/fuzz/harry/integration/dsl/HistoryBuilderIntegrationTest.java
new file mode 100644
index 0000000000..bf5cfde121
--- /dev/null
+++ b/test/distributed/org/apache/cassandra/fuzz/harry/integration/dsl/HistoryBuilderIntegrationTest.java
@@ -0,0 +1,148 @@
+/*
+ * 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.fuzz.harry.integration.dsl;
+
+import java.util.Random;
+import java.util.function.LongSupplier;
+import java.util.function.Supplier;
+
+import org.junit.Test;
+
+import org.apache.cassandra.fuzz.harry.integration.model.ModelTestBase;
+import org.apache.cassandra.harry.checker.ModelChecker;
+import org.apache.cassandra.harry.core.Configuration;
+import org.apache.cassandra.harry.core.Run;
+import org.apache.cassandra.harry.ddl.SchemaGenerators;
+import org.apache.cassandra.harry.ddl.SchemaSpec;
+import org.apache.cassandra.harry.dsl.BatchVisitBuilder;
+import org.apache.cassandra.harry.dsl.HistoryBuilder;
+import org.apache.cassandra.harry.dsl.SingleOperationBuilder;
+import org.apache.cassandra.harry.dsl.ValueDescriptorIndexGenerator;
+import org.apache.cassandra.harry.gen.rng.JdkRandomEntropySource;
+import org.apache.cassandra.harry.model.Model;
+import org.apache.cassandra.harry.operations.Query;
+import org.apache.cassandra.harry.sut.SystemUnderTest;
+import org.apache.cassandra.harry.sut.TokenPlacementModel;
+import org.apache.cassandra.harry.visitors.ReplayingVisitor;
+
+public class HistoryBuilderIntegrationTest extends ModelTestBase
+{
+ private final long seed = 1L;
+ private final int STEPS_PER_ITERATION = 1_000;
+ private final int MAX_PARTITIONS = 100;
+
+ @Override
+ protected Configuration.ModelConfiguration modelConfiguration()
+ {
+ return new Configuration.QuiescentCheckerConfig();
+ }
+
+ public Configuration.ConfigurationBuilder configuration(long seed, SchemaSpec schema)
+ {
+ return super.configuration(seed, schema)
+ .setPartitionDescriptorSelector(new Configuration.DefaultPDSelectorConfiguration(2, 2))
+ .setClusteringDescriptorSelector((builder) -> builder.setOperationsPerLtsDistribution(new Configuration.ConstantDistributionConfig(100_000)));
+ }
+
+ @Test
+ public void simpleDSLTest() throws Throwable
+ {
+ Supplier<SchemaSpec> supplier = SchemaGenerators.progression(SchemaGenerators.DEFAULT_SWITCH_AFTER);
+ for (int i = 0; i < SchemaGenerators.DEFAULT_RUNS; i++)
+ {
+ SchemaSpec schema = supplier.get();
+ Configuration config = configuration(i, schema).build();
+
+ Run run = config.createRun();
+ beforeEach();
+ run.sut.schemaChange(schema.compile().cql());
+
+ ModelChecker<SingleOperationBuilder> modelChecker = new ModelChecker<>();
+ JdkRandomEntropySource entropySource = new JdkRandomEntropySource(new Random(seed));
+
+ LongSupplier[] valueGenerators = new LongSupplier[run.schemaSpec.regularColumns.size()];
+ for (int j = 0; j < valueGenerators.length; j++)
+ {
+ valueGenerators[j] = new ValueDescriptorIndexGenerator(run.schemaSpec.regularColumns.get(j),
+ run.rng)
+ .toSupplier(entropySource.derive(), 20, 0.2f);
+ }
+
+ TokenPlacementModel.ReplicationFactor rf = new TokenPlacementModel.SimpleReplicationFactor(1);
+
+ int maxPartitionSize = 100;
+ modelChecker.init(new HistoryBuilder(seed, maxPartitionSize, 10, schema, rf))
+ .step((history) -> {
+ return history.insert();
+ })
+ .step((history) -> {
+ return history.insert(entropySource.nextInt(maxPartitionSize));
+ })
+ .step((history) -> {
+ int row = entropySource.nextInt(maxPartitionSize);
+ long[] vds = new long[valueGenerators.length];
+ for (int j = 0; j < valueGenerators.length; j++)
+ vds[j] = valueGenerators[j].getAsLong();
+
+ return history.insert(row, vds);
+ })
+ .step((history) -> {
+ return history.deleteRow();
+ })
+ .step((history) -> {
+ return history.deleteRow(entropySource.nextInt(maxPartitionSize));
+ })
+ .step(SingleOperationBuilder::deletePartition)
+ .step(SingleOperationBuilder::deleteColumns)
+ .step(SingleOperationBuilder::deleteRowSlice)
+ .step((history) -> {
+ return history.deleteRowRange();
+ })
+ .step((history) -> {
+ return history.deleteRowRange(entropySource.nextInt(maxPartitionSize),
+ entropySource.nextInt(maxPartitionSize),
+ entropySource.nextBoolean(),
+ entropySource.nextBoolean());
+ })
+ .step((history) -> history instanceof HistoryBuilder,
+ (history) -> ((HistoryBuilder) history).beginBatch())
+ .step((history) -> (history instanceof BatchVisitBuilder) && ((BatchVisitBuilder) history).size() > 1,
+ (history) -> ((BatchVisitBuilder) history).endBatch())
+ .exitCondition((history) -> {
+ if (!(history instanceof HistoryBuilder))
+ return false;
+
+ HistoryBuilder historyBuilder = (HistoryBuilder) history;
+ ReplayingVisitor visitor = historyBuilder.visitor(run.tracker, run.sut, SystemUnderTest.ConsistencyLevel.ALL);
+ visitor.replayAll();
+
+ if (historyBuilder.presetSelector.pds().size() < MAX_PARTITIONS)
+ return false;
+
+ Model model = historyBuilder.quiescentChecker(run.tracker, sut);
+
+ for (Long pd : historyBuilder.presetSelector.pds())
+ model.validate(Query.selectPartition(run.schemaSpec, pd,false));
+
+ return true;
+ })
+ .run(STEPS_PER_ITERATION, seed);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/distributed/org/apache/cassandra/fuzz/harry/integration/generators/DataGeneratorsIntegrationTest.java b/test/distributed/org/apache/cassandra/fuzz/harry/integration/generators/DataGeneratorsIntegrationTest.java
new file mode 100644
index 0000000000..1ce9994c09
--- /dev/null
+++ b/test/distributed/org/apache/cassandra/fuzz/harry/integration/generators/DataGeneratorsIntegrationTest.java
@@ -0,0 +1,179 @@
+/*
+ * 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.fuzz.harry.integration.generators;
+
+import java.util.Random;
+import java.util.concurrent.CompletableFuture;
+
+import com.google.common.collect.Iterators;
+import org.junit.Test;
+
+import org.apache.cassandra.harry.HarryHelper;
+import org.apache.cassandra.harry.core.Configuration;
+import org.apache.cassandra.harry.core.Run;
+import org.apache.cassandra.harry.ddl.ColumnSpec;
+import org.apache.cassandra.harry.ddl.SchemaGenerators;
+import org.apache.cassandra.harry.ddl.SchemaSpec;
+import org.apache.cassandra.harry.gen.Generator;
+import org.apache.cassandra.harry.gen.Surjections;
+import org.apache.cassandra.harry.gen.distribution.Distribution;
+import org.apache.cassandra.harry.model.NoOpChecker;
+import org.apache.cassandra.harry.model.OpSelectors;
+import org.apache.cassandra.harry.sut.SystemUnderTest;
+import org.apache.cassandra.harry.visitors.MutatingVisitor;
+import org.apache.cassandra.harry.visitors.MutatingRowVisitor;
+import org.apache.cassandra.harry.visitors.SingleValidator;
+import org.apache.cassandra.harry.util.TestRunner;
+import org.apache.cassandra.harry.visitors.Visitor;
+import org.apache.cassandra.cql3.CQLTester;
+import org.apache.cassandra.cql3.UntypedResultSet;
+import org.apache.cassandra.distributed.impl.RowUtil;
+
+public class DataGeneratorsIntegrationTest extends CQLTester
+{
+ @Test
+ public void testTimestampTieResolution()
+ {
+ Random rng = new Random(1);
+ String ks = "test_timestamp_tie_resolution";
+ createKeyspace(String.format("CREATE KEYSPACE %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}", ks));
+ int counter = 0;
+ for (ColumnSpec.DataType<?> dataType : new ColumnSpec.DataType[]{ ColumnSpec.int8Type,
+ ColumnSpec.int16Type,
+ ColumnSpec.int32Type,
+ ColumnSpec.int64Type,
+ ColumnSpec.asciiType,
+ ColumnSpec.floatType,
+ ColumnSpec.doubleType })
+ {
+
+
+ String tbl = "table_" + (counter++);
+ createTable(String.format("CREATE TABLE %s.%s (pk int PRIMARY KEY, v %s)",
+ ks, tbl,
+ dataType));
+ for (int i = 0; i < 10_000; i++)
+ {
+ long d1 = dataType.generator().adjustEntropyDomain(rng.nextLong());
+ long d2 = dataType.generator().adjustEntropyDomain(rng.nextLong());
+ for (long d : new long[]{ d1, d2 })
+ {
+ execute(String.format("INSERT INTO %s.%s (pk, v) VALUES (?,?) USING TIMESTAMP 1", ks, tbl),
+ i, dataType.generator().inflate(d));
+ }
+
+ if (dataType.compareLexicographically(d1, d2) > 0)
+ assertRows(execute(String.format("SELECT v FROM %s.%s WHERE pk=?", ks, tbl), i),
+ row(dataType.generator().inflate(d1)));
+ else
+ assertRows(execute(String.format("SELECT v FROM %s.%s WHERE pk=?", ks, tbl), i),
+ row(dataType.generator().inflate(d2)));
+ }
+ }
+ }
+
+ @Test
+ public void queryParseabilityTest() throws Throwable
+ {
+ Generator<SchemaSpec> gen = new SchemaGenerators.Builder(KEYSPACE).partitionKeyColumnCount(2, 4)
+ .clusteringColumnCount(1, 4)
+ .regularColumnCount(1, 4)
+ .staticColumnCount(1, 4)
+ .generator();
+
+ TestRunner.test(gen,
+ (schema) -> {
+ try
+ {
+ schema.validate();
+ }
+ catch (AssertionError e)
+ {
+ return;
+ }
+ createTable(schema.compile().cql());
+
+
+ Configuration.ConfigurationBuilder builder = HarryHelper.defaultConfiguration()
+ .setDataTracker(new Configuration.NoOpDataTrackerConfiguration())
+ .setSchemaProvider(new Configuration.FixedSchemaProviderConfiguration(schema))
+ .setSUT(CqlTesterSut::new);
+
+ for (OpSelectors.OperationKind kind : OpSelectors.OperationKind.values())
+ {
+ Run run = builder
+ .setClusteringDescriptorSelector((rng, schema_) -> {
+ return new OpSelectors.DefaultDescriptorSelector(rng,
+ OpSelectors.columnSelectorBuilder().forAll(schema_).build(),
+ OpSelectors.OperationSelector.weighted(Surjections.weights(100), kind),
+ new Distribution.ConstantDistribution(2),
+ 100);
+ })
+ .build()
+ .createRun();
+
+ Visitor visitor = new MutatingVisitor(run, MutatingRowVisitor::new);
+ for (int lts = 0; lts < 100; lts++)
+ visitor.visit();
+
+ SingleValidator validator = new SingleValidator(100, run, NoOpChecker::new);
+ for (int lts = 0; lts < 10; lts++)
+ validator.visit(lts);
+ }
+ });
+ }
+
+ public class CqlTesterSut implements SystemUnderTest
+ {
+ public boolean isShutdown()
+ {
+ return false;
+ }
+
+ public void shutdown()
+ {
+ }
+
+ public void schemaChange(String statement)
+ {
+ createTable(statement);
+ }
+
+ public Object[][] execute(String statement, ConsistencyLevel cl, Object... bindings)
+ {
+ try
+ {
+ UntypedResultSet res = DataGeneratorsIntegrationTest.this.execute(statement, bindings);
+ if (res == null)
+ return new Object[][]{};
+
+ return Iterators.toArray(RowUtil.toIter(res), Object[].class);
+ }
+ catch (Throwable throwable)
+ {
+ throw new RuntimeException(throwable);
+ }
+ }
+
+ public CompletableFuture<Object[][]> executeAsync(String statement, ConsistencyLevel cl, Object... bindings)
+ {
+ return CompletableFuture.completedFuture(execute(statement, cl, bindings));
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/distributed/org/apache/cassandra/fuzz/harry/integration/model/ConcurrentQuiescentCheckerIntegrationTest.java b/test/distributed/org/apache/cassandra/fuzz/harry/integration/model/ConcurrentQuiescentCheckerIntegrationTest.java
new file mode 100644
index 0000000000..192629b026
--- /dev/null
+++ b/test/distributed/org/apache/cassandra/fuzz/harry/integration/model/ConcurrentQuiescentCheckerIntegrationTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.fuzz.harry.integration.model;
+
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+
+import org.junit.Test;
+
+import org.apache.cassandra.harry.core.Configuration;
+import org.apache.cassandra.harry.ddl.SchemaGenerators;
+import org.apache.cassandra.harry.ddl.SchemaSpec;
+import org.apache.cassandra.harry.tracker.LockingDataTracker;
+import org.apache.cassandra.harry.runner.Runner;
+import org.apache.cassandra.harry.visitors.MutatingVisitor;
+import org.apache.cassandra.harry.visitors.QueryLogger;
+import org.apache.cassandra.harry.visitors.RandomPartitionValidator;
+
+import static org.apache.cassandra.harry.core.Configuration.VisitorPoolConfiguration.pool;
+import static java.util.Arrays.asList;
+
+public class ConcurrentQuiescentCheckerIntegrationTest extends ModelTestBase
+{
+ @Test
+ public void testConcurrentReadWriteWorkload() throws Throwable
+ {
+ Supplier<SchemaSpec> supplier = SchemaGenerators.progression(1);
+ int writeThreads = 2;
+ int readThreads = 2;
+
+ for (int i = 0; i < SchemaGenerators.GENERATORS_COUNT; i++)
+ {
+ SchemaSpec schema = supplier.get();
+
+ Configuration config = configuration(i, schema)
+ .setKeyspaceDdl("CREATE KEYSPACE IF NOT EXISTS " + schema.keyspace + " WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1};")
+ .setCreateSchema(true)
+ .setDropSchema(true)
+ .setDataTracker(LockingDataTracker::new)
+ .build();
+
+ Runner.concurrent(config,
+ asList(pool("Writer", writeThreads, MutatingVisitor::new),
+ pool("Reader", readThreads, (run) -> new RandomPartitionValidator(run, modelConfiguration(), QueryLogger.NO_OP))),
+ 2, TimeUnit.MINUTES)
+ .run();
+ }
+ }
+
+ @Override
+ protected Configuration.ModelConfiguration modelConfiguration()
+ {
+ return new Configuration.QuiescentCheckerConfig();
+ }
+}
\ No newline at end of file
diff --git a/test/distributed/org/apache/cassandra/fuzz/harry/integration/model/InJVMTokenAwareExecutorTest.java b/test/distributed/org/apache/cassandra/fuzz/harry/integration/model/InJVMTokenAwareExecutorTest.java
new file mode 100644
index 0000000000..bd7927a13e
--- /dev/null
+++ b/test/distributed/org/apache/cassandra/fuzz/harry/integration/model/InJVMTokenAwareExecutorTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.fuzz.harry.integration.model;
+
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.cassandra.harry.core.Configuration;
+import org.apache.cassandra.harry.core.Run;
+import org.apache.cassandra.harry.ddl.SchemaGenerators;
+import org.apache.cassandra.harry.ddl.SchemaSpec;
+import org.apache.cassandra.harry.model.QuiescentChecker;
+import org.apache.cassandra.harry.model.RepairingLocalStateValidator;
+import org.apache.cassandra.harry.sut.SystemUnderTest;
+import org.apache.cassandra.harry.sut.TokenPlacementModel;
+import org.apache.cassandra.harry.sut.injvm.InJVMTokenAwareVisitExecutor;
+import org.apache.cassandra.harry.sut.injvm.InJvmSut;
+import org.apache.cassandra.harry.sut.injvm.InJvmSutBase;
+import org.apache.cassandra.harry.runner.Runner;
+import org.apache.cassandra.harry.runner.UpToLtsRunner;
+import org.apache.cassandra.harry.visitors.MutatingVisitor;
+import org.apache.cassandra.distributed.Cluster;
+import org.apache.cassandra.distributed.api.Feature;
+
+public class InJVMTokenAwareExecutorTest extends IntegrationTestBase
+{
+ private static final Logger logger = LoggerFactory.getLogger(InJVMTokenAwareExecutorTest.class);
+
+ @BeforeClass
+ public static void before() throws Throwable
+ {
+ cluster = Cluster.build()
+ .withNodes(5)
+ .withConfig((cfg) -> InJvmSutBase.defaultConfig().accept(cfg.with(Feature.GOSSIP, Feature.NETWORK)))
+ .createWithoutStarting();
+ cluster.setUncaughtExceptionsFilter(t -> {
+ logger.error("Caught exception, reporting during shutdown. Ignoring.", t);
+ return true;
+ });
+ cluster.startup();
+ cluster = init(cluster);
+ sut = new InJvmSut(cluster);
+ }
+
+ @Override
+ @Before
+ public void beforeEach()
+ {
+ cluster.schemaChange("DROP KEYSPACE IF EXISTS harry");
+ cluster.schemaChange("CREATE KEYSPACE harry WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 3};");
+ }
+
+ @Test
+ public void testRepair() throws Throwable
+ {
+ Supplier<SchemaSpec> schemaGen = SchemaGenerators.progression(1);
+ for (int cnt = 0; cnt < SchemaGenerators.DEFAULT_RUNS; cnt++)
+ {
+ SchemaSpec schema = schemaGen.get();
+ Configuration.ConfigurationBuilder builder = sharedConfiguration(cnt, schema);
+
+ Configuration configuration = builder.build();
+ Run run = configuration.createRun();
+ run.sut.schemaChange(run.schemaSpec.compile().cql());
+
+ Runner.chain(configuration,
+ UpToLtsRunner.factory(MutatingVisitor.factory(InJVMTokenAwareVisitExecutor.factory(new Configuration.MutatingRowVisitorConfiguration(),
+ SystemUnderTest.ConsistencyLevel.NODE_LOCAL,
+ new TokenPlacementModel.SimpleReplicationFactor(3))),
+ 10_000, 2, TimeUnit.SECONDS),
+ Runner.single(RepairingLocalStateValidator.factoryForTests(5, QuiescentChecker::new)))
+ .run();
+ }
+ }
+}
diff --git a/test/distributed/org/apache/cassandra/fuzz/harry/integration/model/IntegrationTestBase.java b/test/distributed/org/apache/cassandra/fuzz/harry/integration/model/IntegrationTestBase.java
new file mode 100644
index 0000000000..fd410cef73
--- /dev/null
+++ b/test/distributed/org/apache/cassandra/fuzz/harry/integration/model/IntegrationTestBase.java
@@ -0,0 +1,116 @@
+/*
+ * 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.fuzz.harry.integration.model;
+
+import java.util.function.Supplier;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.cassandra.harry.core.Configuration;
+import org.apache.cassandra.harry.ddl.SchemaGenerators;
+import org.apache.cassandra.harry.ddl.SchemaSpec;
+import org.apache.cassandra.harry.clock.OffsetClock;
+import org.apache.cassandra.harry.model.OpSelectors;
+import org.apache.cassandra.harry.sut.injvm.InJvmSut;
+import org.apache.cassandra.harry.sut.injvm.InJvmSutBase;
+import org.apache.cassandra.distributed.Cluster;
+import org.apache.cassandra.distributed.test.TestBaseImpl;
+
+public class IntegrationTestBase extends TestBaseImpl
+{
+ protected static final Logger logger = LoggerFactory.getLogger(IntegrationTestBase.class);
+ protected static Cluster cluster;
+ protected static InJvmSut sut;
+
+ @BeforeClass
+ public static void before() throws Throwable
+ {
+ cluster = Cluster.build()
+ .withNodes(1)
+ .withConfig(InJvmSutBase.defaultConfig())
+ .createWithoutStarting();
+ cluster.setUncaughtExceptionsFilter(t -> {
+ logger.error("Caught exception, reporting during shutdown. Ignoring.", t);
+ return true;
+ });
+ cluster.startup();
+ cluster = init(cluster);
+ sut = new InJvmSut(cluster);
+ }
+
+ @AfterClass
+ public static void afterClass()
+ {
+ sut.shutdown();
+ }
+
+ @Before
+ public void beforeEach()
+ {
+ cluster.schemaChange("DROP KEYSPACE IF EXISTS harry");
+ cluster.schemaChange("CREATE KEYSPACE harry WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1};");
+ }
+
+ private static long seed = 0;
+ public static Supplier<Configuration.ConfigurationBuilder> sharedConfiguration()
+ {
+ Supplier<SchemaSpec> specGenerator = SchemaGenerators.progression(SchemaGenerators.DEFAULT_SWITCH_AFTER);
+ return () -> {
+ SchemaSpec schemaSpec = specGenerator.get();
+ return sharedConfiguration(seed, schemaSpec);
+ };
+ }
+
+ public static Configuration.CDSelectorConfigurationBuilder sharedCDSelectorConfiguration()
+ {
+ return new Configuration.CDSelectorConfigurationBuilder()
+ .setOperationsPerLtsDistribution(new Configuration.ConstantDistributionConfig(2))
+ .setMaxPartitionSize(100)
+ .setOperationKindWeights(new Configuration.OperationKindSelectorBuilder()
+ .addWeight(OpSelectors.OperationKind.DELETE_ROW, 1)
+ .addWeight(OpSelectors.OperationKind.DELETE_COLUMN, 1)
+ .addWeight(OpSelectors.OperationKind.DELETE_RANGE, 1)
+ .addWeight(OpSelectors.OperationKind.DELETE_SLICE, 1)
+ .addWeight(OpSelectors.OperationKind.DELETE_PARTITION, 1)
+ .addWeight(OpSelectors.OperationKind.DELETE_COLUMN_WITH_STATICS, 5)
+ .addWeight(OpSelectors.OperationKind.INSERT_WITH_STATICS, 20)
+ .addWeight(OpSelectors.OperationKind.INSERT, 20)
+ .addWeight(OpSelectors.OperationKind.UPDATE_WITH_STATICS, 25)
+ .addWeight(OpSelectors.OperationKind.UPDATE, 25)
+ .build());
+ }
+
+ public static Configuration.ConfigurationBuilder sharedConfiguration(long seed, SchemaSpec schema)
+ {
+ return new Configuration.ConfigurationBuilder().setSeed(seed)
+ .setClock(() -> new OffsetClock(100000))
+ .setCreateSchema(true)
+ .setTruncateTable(false)
+ .setDropSchema(true)
+ .setSchemaProvider((seed1, sut) -> schema)
+ .setClusteringDescriptorSelector(sharedCDSelectorConfiguration().build())
+ .setPartitionDescriptorSelector(new Configuration.DefaultPDSelectorConfiguration(2, 200))
+ .setSUT(() -> sut);
+ }
+}
\ No newline at end of file
diff --git a/test/distributed/org/apache/cassandra/fuzz/harry/integration/model/MockSchema.java b/test/distributed/org/apache/cassandra/fuzz/harry/integration/model/MockSchema.java
new file mode 100644
index 0000000000..8d7475be18
--- /dev/null
+++ b/test/distributed/org/apache/cassandra/fuzz/harry/integration/model/MockSchema.java
@@ -0,0 +1,117 @@
+/*
+ * 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.fuzz.harry.integration.model;
+
+import java.util.Arrays;
+
+import org.apache.cassandra.harry.ddl.ColumnSpec;
+import org.apache.cassandra.harry.ddl.SchemaGenerators;
+import org.apache.cassandra.harry.ddl.SchemaSpec;
+import org.apache.cassandra.harry.gen.Surjections;
+import org.apache.cassandra.harry.util.BitSet;
+
+public class MockSchema
+{
+ public static final String KEYSPACE = "harry";
+ public static final Surjections.Surjection<BitSet> columnMaskSelector1;
+ public static final Surjections.Surjection<BitSet> columnMaskSelector2;
+ public static final Surjections.Surjection<BitSet> compactColumnMaskSelector;
+
+ public static final SchemaSpec tbl1;
+ public static final SchemaSpec tbl2;
+ public static final SchemaSpec compact_schema;
+
+ static
+ {
+ columnMaskSelector1 = Surjections.pick(BitSet.create(0b0000111, 7),
+ BitSet.create(0b1110000, 7),
+ BitSet.create(0b1111111, 7));
+
+ columnMaskSelector2 = Surjections.pick(BitSet.create(0b11, 2),
+ BitSet.create(0b01, 2),
+ BitSet.create(0b10, 2));
+
+ compactColumnMaskSelector = Surjections.pick(BitSet.create(1, 1));
+
+ tbl1 = new SchemaSpec(KEYSPACE, "tbl1",
+ Arrays.asList(ColumnSpec.pk("pk1", ColumnSpec.asciiType),
+ ColumnSpec.pk("pk2", ColumnSpec.int64Type)),
+ Arrays.asList(ColumnSpec.ck("ck1", ColumnSpec.asciiType, false),
+ ColumnSpec.ck("ck2", ColumnSpec.int64Type, false)),
+ Arrays.asList(ColumnSpec.regularColumn("v1", ColumnSpec.int32Type),
+ ColumnSpec.regularColumn("v2", ColumnSpec.int64Type),
+ // TODO: test boolean values; before that - submit a PR to Cassandra where we add boolean to ByteBufferUtil#objectToBytes
+ ColumnSpec.regularColumn("v3", ColumnSpec.int32Type),
+ ColumnSpec.regularColumn("v4", ColumnSpec.asciiType),
+ ColumnSpec.regularColumn("v5", ColumnSpec.int64Type),
+ ColumnSpec.regularColumn("v6", ColumnSpec.floatType),
+ ColumnSpec.regularColumn("v7", ColumnSpec.doubleType)),
+ Arrays.asList(ColumnSpec.staticColumn("static1", ColumnSpec.asciiType),
+ ColumnSpec.staticColumn("static2", ColumnSpec.int64Type)));
+
+ tbl2 = new SchemaSpec(KEYSPACE, "tbl2",
+ Arrays.asList(ColumnSpec.pk("pk1", ColumnSpec.asciiType),
+ ColumnSpec.pk("pk2", ColumnSpec.int64Type)),
+ Arrays.asList(ColumnSpec.ck("ck1", ColumnSpec.asciiType, true)),
+ Arrays.asList(ColumnSpec.regularColumn("v1", ColumnSpec.int32Type),
+ ColumnSpec.regularColumn("v2", ColumnSpec.asciiType)),
+ Arrays.asList(ColumnSpec.regularColumn("static1", ColumnSpec.int32Type),
+ ColumnSpec.regularColumn("static2", ColumnSpec.asciiType)));
+
+ compact_schema = new SchemaSpec(KEYSPACE, "tbl3",
+ Arrays.asList(ColumnSpec.pk("pk1", ColumnSpec.asciiType),
+ ColumnSpec.pk("pk2", ColumnSpec.int64Type)),
+ Arrays.asList(ColumnSpec.ck("ck1", ColumnSpec.asciiType, true),
+ ColumnSpec.ck("ck2", ColumnSpec.int64Type, false)),
+ Arrays.asList(ColumnSpec.regularColumn("v1", ColumnSpec.asciiType)),
+ Arrays.asList(ColumnSpec.staticColumn("static1", ColumnSpec.asciiType)))
+ .withCompactStorage();
+ }
+
+ public static SchemaSpec randomSchema(String keyspace, String table, long seed)
+ {
+ return new SchemaGenerators.Builder(keyspace, () -> table)
+ .partitionKeySpec(1, 4,
+// ColumnSpec.int8Type,
+// ColumnSpec.int16Type,
+// ColumnSpec.int32Type,
+ ColumnSpec.int64Type,
+ ColumnSpec.floatType,
+ ColumnSpec.doubleType,
+ ColumnSpec.asciiType(4, 10))
+ .clusteringKeySpec(1, 4,
+// ColumnSpec.int8Type,
+// ColumnSpec.int16Type,
+// ColumnSpec.int32Type,
+ ColumnSpec.int64Type,
+ ColumnSpec.floatType,
+ ColumnSpec.doubleType,
+ ColumnSpec.asciiType(4, 10))
+ .regularColumnSpec(2, 10,
+ ColumnSpec.int8Type,
+ ColumnSpec.int16Type,
+ ColumnSpec.int32Type,
+ ColumnSpec.int64Type,
+ ColumnSpec.floatType,
+ ColumnSpec.doubleType,
+ ColumnSpec.asciiType(5, 10))
+ .surjection()
+ .inflate(seed);
+ }
+}
diff --git a/test/distributed/org/apache/cassandra/fuzz/harry/integration/model/ModelTestBase.java b/test/distributed/org/apache/cassandra/fuzz/harry/integration/model/ModelTestBase.java
new file mode 100644
index 0000000000..2776aea930
--- /dev/null
+++ b/test/distributed/org/apache/cassandra/fuzz/harry/integration/model/ModelTestBase.java
@@ -0,0 +1,138 @@
+/*
+ * 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.fuzz.harry.integration.model;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.concurrent.TimeUnit;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+import org.apache.cassandra.harry.core.Configuration;
+import org.apache.cassandra.harry.core.Run;
+import org.apache.cassandra.harry.ddl.SchemaGenerators;
+import org.apache.cassandra.harry.ddl.SchemaSpec;
+import org.apache.cassandra.harry.runner.Runner;
+import org.apache.cassandra.harry.runner.UpToLtsRunner;
+import org.apache.cassandra.harry.visitors.MutatingRowVisitor;
+import org.apache.cassandra.harry.visitors.MutatingVisitor;
+import org.apache.cassandra.harry.visitors.SingleValidator;
+
+public abstract class ModelTestBase extends IntegrationTestBase
+{
+ private final int ITERATIONS = 20_000;
+
+ void negativeTest(Function<Run, Boolean> corrupt, BiConsumer<Throwable, Run> validate) throws Throwable
+ {
+ Supplier<SchemaSpec> supplier = SchemaGenerators.progression(SchemaGenerators.DEFAULT_SWITCH_AFTER);
+ for (int i = 0; i < SchemaGenerators.DEFAULT_RUNS; i++)
+ {
+ SchemaSpec schema = supplier.get();
+ negativeTest(corrupt, validate, i, schema);
+ }
+ }
+
+ void negativeIntegrationTest(Configuration.RunnerConfiguration runnerConfig) throws Throwable
+ {
+ Supplier<SchemaSpec> supplier = SchemaGenerators.progression(1);
+ for (int i = 0; i < SchemaGenerators.DEFAULT_RUNS; i++)
+ {
+ SchemaSpec schema = supplier.get();
+ Configuration.ConfigurationBuilder builder = configuration(i, schema);
+
+ builder.setClock(new Configuration.ApproximateClockConfiguration((int) TimeUnit.MINUTES.toMillis(10),
+ 1, TimeUnit.SECONDS))
+ .setCreateSchema(false)
+ .setDropSchema(false)
+ .setRunner(runnerConfig);
+
+ Configuration config = builder.build();
+ Runner runner = config.createRunner();
+
+ Run run = runner.getRun();
+ beforeEach();
+ run.sut.schemaChange(run.schemaSpec.compile().cql());
+ runner.run();
+ }
+ }
+
+ protected abstract Configuration.ModelConfiguration modelConfiguration();
+
+ protected SingleValidator validator(Run run)
+ {
+ return new SingleValidator(100, run , modelConfiguration());
+ }
+
+ public Configuration.ConfigurationBuilder configuration(long seed, SchemaSpec schema)
+ {
+ return sharedConfiguration(seed, schema);
+ }
+
+ void negativeTest(Function<Run, Boolean> corrupt, BiConsumer<Throwable, Run> validate, int counter, SchemaSpec schemaSpec) throws Throwable
+ {
+ Configuration config = configuration(counter, schemaSpec)
+ .setCreateSchema(true)
+ .setKeyspaceDdl(String.format("CREATE KEYSPACE IF NOT EXISTS %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor': %d};",
+ schemaSpec.keyspace, cluster.size()))
+ .setDropSchema(true)
+ .build();
+
+ Run run = config.createRun();
+
+ new Runner.ChainRunner(run, config,
+ Arrays.asList(writer(ITERATIONS, 2, TimeUnit.MINUTES),
+ (r, c) -> new Runner.SingleVisitRunner(r, c, Collections.singletonList(this::validator)) {
+ @Override
+ public void runInternal()
+ {
+ if (!corrupt.apply(run))
+ {
+ System.out.println("Could not corrupt");
+ return;
+ }
+ try
+ {
+ super.runInternal();
+ throw new ShouldHaveThrownException();
+ }
+ catch (Throwable t)
+ {
+ validate.accept(t, run);
+ }
+ }
+ })).run();
+ }
+
+ public static Configuration.RunnerConfiguration writer(long iterations, int runtime, TimeUnit timeUnit)
+ {
+ return (run, config) -> {
+ return new UpToLtsRunner(run, config,
+ Collections.singletonList((r_) -> new MutatingVisitor(r_, MutatingRowVisitor::new)),
+ iterations,
+ runtime, timeUnit);
+ };
+ }
+
+ public static class ShouldHaveThrownException extends AssertionError
+ {
+
+ }
+}
+
diff --git a/test/distributed/org/apache/cassandra/fuzz/harry/integration/model/QuerySelectorNegativeTest.java b/test/distributed/org/apache/cassandra/fuzz/harry/integration/model/QuerySelectorNegativeTest.java
new file mode 100644
index 0000000000..11c65e4ad9
--- /dev/null
+++ b/test/distributed/org/apache/cassandra/fuzz/harry/integration/model/QuerySelectorNegativeTest.java
@@ -0,0 +1,157 @@
+/*
+ * 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.fuzz.harry.integration.model;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+import java.util.function.Supplier;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import org.apache.cassandra.harry.core.Configuration;
+import org.apache.cassandra.harry.core.Run;
+import org.apache.cassandra.harry.corruptor.AddExtraRowCorruptor;
+import org.apache.cassandra.harry.corruptor.ChangeValueCorruptor;
+import org.apache.cassandra.harry.corruptor.HideRowCorruptor;
+import org.apache.cassandra.harry.corruptor.HideValueCorruptor;
+import org.apache.cassandra.harry.corruptor.QueryResponseCorruptor;
+import org.apache.cassandra.harry.corruptor.ShowValueCorruptor;
+import org.apache.cassandra.harry.ddl.SchemaGenerators;
+import org.apache.cassandra.harry.model.Model;
+import org.apache.cassandra.harry.model.QuiescentChecker;
+import org.apache.cassandra.harry.visitors.MutatingVisitor;
+import org.apache.cassandra.harry.visitors.MutatingRowVisitor;
+import org.apache.cassandra.harry.operations.Query;
+import org.apache.cassandra.harry.operations.QueryGenerator;
+import org.apache.cassandra.harry.visitors.Visitor;
+
+import static org.apache.cassandra.harry.corruptor.QueryResponseCorruptor.SimpleQueryResponseCorruptor;
+
+@RunWith(Parameterized.class)
+public class QuerySelectorNegativeTest extends IntegrationTestBase
+{
+ private final int CYCLES = 1000;
+
+ private final Random rnd = new Random();
+
+ private final QueryResponseCorruptorFactory corruptorFactory;
+
+ public QuerySelectorNegativeTest(QueryResponseCorruptorFactory corruptorFactory)
+ {
+ this.corruptorFactory = corruptorFactory;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<QueryResponseCorruptorFactory> source()
+ {
+ return Arrays.asList((run) -> new SimpleQueryResponseCorruptor(run.schemaSpec,
+ run.clock,
+ ChangeValueCorruptor::new),
+ (run) -> new SimpleQueryResponseCorruptor(run.schemaSpec,
+ run.clock,
+ HideValueCorruptor::new),
+ (run) -> new SimpleQueryResponseCorruptor(run.schemaSpec,
+ run.clock,
+ ShowValueCorruptor::new),
+ (run) -> new SimpleQueryResponseCorruptor(run.schemaSpec,
+ run.clock,
+ HideRowCorruptor::new),
+ (run) -> new AddExtraRowCorruptor(run.schemaSpec,
+ run.clock,
+ run.tracker,
+ run.descriptorSelector)
+ );
+ }
+
+ interface QueryResponseCorruptorFactory
+ {
+ QueryResponseCorruptor create(Run run);
+ }
+
+ @Test
+ public void selectRows()
+ {
+ Map<Query.QueryKind, Integer> stats = new HashMap<>();
+ Supplier<Configuration.ConfigurationBuilder> gen = sharedConfiguration();
+
+ int rounds = SchemaGenerators.DEFAULT_RUNS;
+ int failureCounter = 0;
+ outer:
+ for (int counter = 0; counter < rounds; counter++)
+ {
+ beforeEach();
+ Configuration config = gen.get()
+ .setClusteringDescriptorSelector(sharedCDSelectorConfiguration()
+ .setOperationsPerLtsDistribution(new Configuration.ConstantDistributionConfig(2))
+ .setMaxPartitionSize(2000)
+ .build())
+ .build();
+ Run run = config.createRun();
+ run.sut.schemaChange(run.schemaSpec.compile().cql());
+ System.out.println(run.schemaSpec.compile().cql());
+
+ Visitor visitor = new MutatingVisitor(run, MutatingRowVisitor::new);
+ Model model = new QuiescentChecker(run);
+
+ QueryResponseCorruptor corruptor = this.corruptorFactory.create(run);
+
+ for (int i = 0; i < CYCLES; i++)
+ visitor.visit();
+
+ while (true)
+ {
+ long verificationLts = rnd.nextInt(1000);
+ QueryGenerator queryGen = new QueryGenerator(run.schemaSpec,
+ run.pdSelector,
+ run.descriptorSelector,
+ run.rng);
+
+ QueryGenerator.TypedQueryGenerator querySelector = new QueryGenerator.TypedQueryGenerator(run.rng, queryGen);
+ Query query = querySelector.inflate(verificationLts, counter);
+
+ model.validate(query);
+
+ if (!corruptor.maybeCorrupt(query, run.sut))
+ continue;
+
+ try
+ {
+ model.validate(query);
+ Assert.fail("Should've failed");
+ }
+ catch (Throwable t)
+ {
+ // expected
+ failureCounter++;
+ stats.compute(query.queryKind, (Query.QueryKind kind, Integer cnt) -> cnt == null ? 1 : (cnt + 1));
+ continue outer;
+ }
+ }
+ }
+
+ Assert.assertTrue(String.format("Seen only %d failures", failureCounter),
+ failureCounter > (rounds * 0.8));
+ }
+}
\ No newline at end of file
diff --git a/test/distributed/org/apache/cassandra/fuzz/harry/integration/model/QuerySelectorTest.java b/test/distributed/org/apache/cassandra/fuzz/harry/integration/model/QuerySelectorTest.java
new file mode 100644
index 0000000000..39fe2c89c5
--- /dev/null
+++ b/test/distributed/org/apache/cassandra/fuzz/harry/integration/model/QuerySelectorTest.java
@@ -0,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.fuzz.harry.integration.model;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Supplier;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.cassandra.harry.core.Configuration;
+import org.apache.cassandra.harry.core.Run;
+import org.apache.cassandra.harry.ddl.SchemaGenerators;
+import org.apache.cassandra.harry.ddl.SchemaSpec;
+import org.apache.cassandra.harry.model.Model;
+import org.apache.cassandra.harry.model.QuiescentChecker;
+import org.apache.cassandra.harry.model.SelectHelper;
+import org.apache.cassandra.harry.sut.SystemUnderTest;
+import org.apache.cassandra.harry.operations.CompiledStatement;
+import org.apache.cassandra.harry.visitors.MutatingVisitor;
+import org.apache.cassandra.harry.visitors.MutatingRowVisitor;
+import org.apache.cassandra.harry.operations.Query;
+import org.apache.cassandra.harry.operations.QueryGenerator;
+import org.apache.cassandra.harry.visitors.Visitor;
+
+import static org.apache.cassandra.harry.gen.DataGenerators.NIL_DESCR;
+
+public class QuerySelectorTest extends IntegrationTestBase
+{
+ private static int CYCLES = 300;
+
+ @Test
+ public void basicQuerySelectorTest()
+ {
+ Supplier<SchemaSpec> schemaGen = SchemaGenerators.progression(SchemaGenerators.DEFAULT_SWITCH_AFTER);
+ for (int cnt = 0; cnt < SchemaGenerators.DEFAULT_RUNS; cnt++)
+ {
+ beforeEach();
+ SchemaSpec schemaSpec = schemaGen.get();
+ int partitionSize = 200;
+
+ int[] fractions = new int[schemaSpec.clusteringKeys.size()];
+ int last = partitionSize;
+ for (int i = fractions.length - 1; i >= 0; i--)
+ {
+ fractions[i] = last;
+ last = last / 2;
+ }
+
+ Configuration config = sharedConfiguration(cnt, schemaSpec)
+ .setClusteringDescriptorSelector(sharedCDSelectorConfiguration()
+ .setMaxPartitionSize(partitionSize)
+ .setFractions(fractions)
+ .build())
+ .build();
+
+ Run run = config.createRun();
+ run.sut.schemaChange(run.schemaSpec.compile().cql());
+
+ Visitor visitor = new MutatingVisitor(run, MutatingRowVisitor::new);
+
+ for (int i = 0; i < CYCLES; i++)
+ visitor.visit();
+
+ QueryGenerator.TypedQueryGenerator querySelector = new QueryGenerator.TypedQueryGenerator(run);
+
+ for (int i = 0; i < CYCLES; i++)
+ {
+ Query query = querySelector.inflate(i, i);
+
+ Object[][] results = run.sut.execute(query.toSelectStatement(), SystemUnderTest.ConsistencyLevel.QUORUM);
+ Set<Long> matchingClusterings = new HashSet<>();
+ for (Object[] row : results)
+ {
+ long cd = SelectHelper.resultSetToRow(run.schemaSpec,
+ run.clock,
+ row).cd;
+ matchingClusterings.add(cd);
+ }
+
+ // the simplest test there can be: every row that is in the partition and was returned by the query,
+ // has to "match", every other row has to be a non-match
+ CompiledStatement selectPartition = SelectHelper.select(run.schemaSpec, run.pdSelector.pd(i, schemaSpec));
+ Object[][] partition = run.sut.execute(selectPartition, SystemUnderTest.ConsistencyLevel.QUORUM);
+ for (Object[] row : partition)
+ {
+ long cd = SelectHelper.resultSetToRow(run.schemaSpec,
+ run.clock,
+ row).cd;
+
+ // Skip static clustering
+ if (cd == NIL_DESCR)
+ continue;
+
+ boolean expected = matchingClusterings.contains(cd);
+ boolean actual = query.matchCd(cd);
+ Assert.assertEquals(String.format("Mismatch for clustering: %d. Expected: %s. Actual: %s.\nQuery: %s",
+ cd, expected, actual, query.toSelectStatement()),
+ expected,
+ actual);
+ }
+ }
+ }
+ }
+
+ @Test
+ public void querySelectorModelTest()
+ {
+ Supplier<SchemaSpec> gen = SchemaGenerators.progression(SchemaGenerators.DEFAULT_SWITCH_AFTER);
+ for (int cnt = 0; cnt < SchemaGenerators.DEFAULT_RUNS; cnt++)
+ {
+ SchemaSpec schemaSpec = gen.get();
+ int[] fractions = new int[schemaSpec.clusteringKeys.size()];
+ int partitionSize = 200;
+ int last = partitionSize;
+ for (int i = fractions.length - 1; i >= 0; i--)
+ {
+ fractions[i] = last;
+ last = last / 2;
+ }
+
+ Configuration config = sharedConfiguration(cnt, schemaSpec)
+ .setClusteringDescriptorSelector(sharedCDSelectorConfiguration()
+ .setMaxPartitionSize(partitionSize)
+ .setFractions(fractions)
+ .build())
+ .build();
+ Run run = config.createRun();
+ run.sut.schemaChange(run.schemaSpec.compile().cql());
+ Visitor visitor = new MutatingVisitor(run, MutatingRowVisitor::new);
+
+ for (int i = 0; i < CYCLES; i++)
+ visitor.visit();
+
+ QueryGenerator.TypedQueryGenerator querySelector = new QueryGenerator.TypedQueryGenerator(run);
+ Model model = new QuiescentChecker(run);
+
+ long verificationLts = 10;
+ for (int i = 0; i < CYCLES; i++)
+ {
+ Query query = querySelector.inflate(verificationLts, i);
+ model.validate(query);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/distributed/org/apache/cassandra/fuzz/harry/integration/model/QuiescentCheckerIntegrationTest.java b/test/distributed/org/apache/cassandra/fuzz/harry/integration/model/QuiescentCheckerIntegrationTest.java
new file mode 100644
index 0000000000..ffb0177897
--- /dev/null
+++ b/test/distributed/org/apache/cassandra/fuzz/harry/integration/model/QuiescentCheckerIntegrationTest.java
@@ -0,0 +1,224 @@
+/*
+ * 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.fuzz.harry.integration.model;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Test;
+
+import org.apache.cassandra.harry.core.Configuration;
+import org.apache.cassandra.harry.core.Configuration.AllPartitionsValidatorConfiguration;
+import org.apache.cassandra.harry.core.Configuration.ConcurrentRunnerConfig;
+import org.apache.cassandra.harry.core.Configuration.LoggingVisitorConfiguration;
+import org.apache.cassandra.harry.core.Configuration.RecentPartitionsValidatorConfiguration;
+import org.apache.cassandra.harry.core.Configuration.SequentialRunnerConfig;
+import org.apache.cassandra.harry.core.Configuration.SingleVisitRunnerConfig;
+import org.apache.cassandra.harry.core.Run;
+import org.apache.cassandra.harry.corruptor.AddExtraRowCorruptor;
+import org.apache.cassandra.harry.corruptor.ChangeValueCorruptor;
+import org.apache.cassandra.harry.corruptor.HideRowCorruptor;
+import org.apache.cassandra.harry.corruptor.HideValueCorruptor;
+import org.apache.cassandra.harry.corruptor.QueryResponseCorruptor;
+import org.apache.cassandra.harry.corruptor.QueryResponseCorruptor.SimpleQueryResponseCorruptor;
+import org.apache.cassandra.harry.ddl.SchemaSpec;
+import org.apache.cassandra.harry.model.Model;
+import org.apache.cassandra.harry.operations.Query;
+import org.apache.cassandra.harry.runner.StagedRunner.StagedRunnerConfig;
+import org.apache.cassandra.harry.visitors.QueryLogger;
+import org.apache.cassandra.harry.visitors.SingleValidator;
+
+public class QuiescentCheckerIntegrationTest extends ModelTestBase
+{
+ private final long CORRUPT_LTS = 0L;
+
+ @Override
+ protected SingleValidator validator(Run run)
+ {
+ return new SingleValidator(100, run, modelConfiguration()) {
+ public void visit()
+ {
+ visit(CORRUPT_LTS);
+ }
+ };
+ }
+
+ @Test
+ public void testNormalCondition() throws Throwable
+ {
+ negativeTest((run) -> true,
+ (t, run) -> {
+ if (!(t instanceof ShouldHaveThrownException))
+ throw new AssertionError(String.format("Throwable was supposed to be null. Schema: %s",
+ run.schemaSpec.compile().cql()),
+ t);
+ });
+ }
+
+ @Test
+ public void normalConditionIntegrationTest() throws Throwable
+ {
+ Model.ModelFactory factory = modelConfiguration();
+
+ SequentialRunnerConfig sequential =
+ new SequentialRunnerConfig(Arrays.asList(new LoggingVisitorConfiguration(new Configuration.MutatingRowVisitorConfiguration()),
+ new RecentPartitionsValidatorConfiguration(10, 10, factory::make, () -> QueryLogger.NO_OP),
+ new AllPartitionsValidatorConfiguration(10, factory::make, () -> QueryLogger.NO_OP)),
+ 1, TimeUnit.MINUTES);
+ negativeIntegrationTest(sequential);
+ }
+
+ @Test
+ public void normalConditionStagedIntegrationTest() throws Throwable
+ {
+ Model.ModelFactory factory = modelConfiguration();
+
+ ConcurrentRunnerConfig concurrent =
+ new ConcurrentRunnerConfig(Arrays.asList(new Configuration.VisitorPoolConfiguration("Writer", 4, new Configuration.MutatingVisitorConfiguation(new Configuration.MutatingRowVisitorConfiguration()))),
+ 30, TimeUnit.SECONDS);
+ SingleVisitRunnerConfig sequential =
+ new SingleVisitRunnerConfig(Collections.singletonList(new RecentPartitionsValidatorConfiguration(1024, 0, factory::make, () -> QueryLogger.NO_OP)));
+
+ StagedRunnerConfig staged = new StagedRunnerConfig(Arrays.asList(concurrent, sequential), 2, TimeUnit.MINUTES);
+
+ negativeIntegrationTest(staged);
+ }
+
+ @Test
+ public void testDetectsMissingRow() throws Throwable
+ {
+ negativeTest((run) -> {
+ SimpleQueryResponseCorruptor corruptor = new SimpleQueryResponseCorruptor(run.schemaSpec,
+ run.clock,
+ HideRowCorruptor::new);
+
+ Query query = Query.selectPartition(run.schemaSpec,
+ run.pdSelector.pd(CORRUPT_LTS, run.schemaSpec),
+ false);
+
+ return corruptor.maybeCorrupt(query, run.sut);
+ },
+ (t, run) -> {
+ // TODO: We can actually pinpoint the difference
+ String expected = "Expected results to have the same number of results, but expected result iterator has more results";
+ String expected2 = "Found a row in the model that is not present in the resultset";
+
+ if (t.getMessage().contains(expected) || t.getMessage().contains(expected2))
+ return;
+
+ throw new AssertionError(String.format("Exception string mismatch.\nExpected error: %s.\nActual error: %s", expected, t.getMessage()),
+ t);
+ });
+ }
+
+ @Test
+ public void testDetectsExtraRow() throws Throwable
+ {
+ negativeTest((run) -> {
+ QueryResponseCorruptor corruptor = new AddExtraRowCorruptor(run.schemaSpec,
+ run.clock,
+ run.tracker,
+ run.descriptorSelector);
+
+ return corruptor.maybeCorrupt(Query.selectPartition(run.schemaSpec,
+ run.pdSelector.pd(CORRUPT_LTS, run.schemaSpec),
+ false),
+ run.sut);
+ },
+ (t, run) -> {
+ String expected = "Found a row in the model that is not present in the resultset";
+ String expected2 = "Expected results to have the same number of results, but actual result iterator has more results";
+ String expected3 = "Found a row while model predicts statics only";
+ if (t.getMessage().contains(expected) || t.getMessage().contains(expected2) || t.getMessage().contains(expected3))
+ return;
+
+ throw new AssertionError(String.format("Exception string mismatch.\nExpected error: %s.\nActual error: %s", expected, t.getMessage()),
+ t);
+ });
+ }
+
+
+ @Test
+ public void testDetectsRemovedColumn() throws Throwable
+ {
+ negativeTest((run) -> {
+ SimpleQueryResponseCorruptor corruptor = new SimpleQueryResponseCorruptor(run.schemaSpec,
+ run.clock,
+ HideValueCorruptor::new);
+
+ return corruptor.maybeCorrupt(Query.selectPartition(run.schemaSpec,
+ run.pdSelector.pd(CORRUPT_LTS, run.schemaSpec),
+ false),
+ run.sut);
+ },
+ (t, run) -> {
+ String expected = "doesn't match the one predicted by the model";
+ String expected2 = "don't match ones predicted by the model";
+ String expected3 = "Found a row in the model that is not present in the resultset";
+ if (t.getMessage().contains(expected) || t.getMessage().contains(expected2) || t.getMessage().contains(expected3))
+ return;
+
+ throw new AssertionError(String.format("Exception string mismatch.\nExpected error: %s.\nActual error: %s", expected, t.getMessage()),
+ t);
+ });
+ }
+
+
+ @Test
+ public void testDetectsOverwrittenRow() throws Throwable
+ {
+ negativeTest((run) -> {
+ SimpleQueryResponseCorruptor corruptor = new SimpleQueryResponseCorruptor(run.schemaSpec,
+ run.clock,
+ ChangeValueCorruptor::new);
+
+ return corruptor.maybeCorrupt(Query.selectPartition(run.schemaSpec,
+ run.pdSelector.pd(CORRUPT_LTS, run.schemaSpec),
+ false),
+ run.sut);
+ },
+ (t, run) -> {
+ String expected = "Returned row state doesn't match the one predicted by the model";
+ String expected2 = "Timestamps in the row state don't match ones predicted by the model";
+
+ if (t.getMessage() != null &&
+ (t.getMessage().contains(expected) || t.getMessage().contains(expected2)))
+ return;
+
+ throw new AssertionError(String.format("Exception string mismatch.\nExpected error: %s.\nActual error: %s", expected, t.getMessage()),
+ t);
+ });
+ }
+
+ @Override
+ protected Configuration.ModelConfiguration modelConfiguration()
+ {
+ return new Configuration.QuiescentCheckerConfig();
+ }
+
+ public Configuration.ConfigurationBuilder configuration(long seed, SchemaSpec schema)
+ {
+ return super.configuration(seed, schema)
+ .setClusteringDescriptorSelector(sharedCDSelectorConfiguration()
+ .setOperationsPerLtsDistribution(new Configuration.ConstantDistributionConfig(2))
+ .setMaxPartitionSize(100)
+ .build());
+ }
+}
\ No newline at end of file
diff --git a/test/distributed/org/apache/cassandra/fuzz/harry/integration/model/QuiescentLocalStateCheckerIntegrationTest.java b/test/distributed/org/apache/cassandra/fuzz/harry/integration/model/QuiescentLocalStateCheckerIntegrationTest.java
new file mode 100644
index 0000000000..2ebfaa55cd
--- /dev/null
+++ b/test/distributed/org/apache/cassandra/fuzz/harry/integration/model/QuiescentLocalStateCheckerIntegrationTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.fuzz.harry.integration.model;
+
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import org.apache.cassandra.harry.core.Configuration;
+import org.apache.cassandra.harry.ddl.SchemaGenerators;
+import org.apache.cassandra.harry.ddl.SchemaSpec;
+import org.apache.cassandra.harry.sut.TokenPlacementModel;
+import org.apache.cassandra.harry.sut.injvm.InJvmSut;
+import org.apache.cassandra.harry.sut.injvm.InJvmSutBase;
+import org.apache.cassandra.harry.sut.injvm.QuiescentLocalStateChecker;
+import org.apache.cassandra.harry.runner.Runner;
+import org.apache.cassandra.harry.visitors.AllPartitionsValidator;
+import org.apache.cassandra.harry.visitors.MutatingVisitor;
+import org.apache.cassandra.distributed.Cluster;
+import org.apache.cassandra.distributed.api.Feature;
+
+public class QuiescentLocalStateCheckerIntegrationTest extends ModelTestBase
+{
+ @BeforeClass
+ public static void before() throws Throwable
+ {
+ cluster = init(Cluster.build()
+ .withNodes(2)
+ .withConfig(InJvmSutBase.defaultConfig().andThen((cfg) -> cfg.with(Feature.GOSSIP)))
+ .start());
+ sut = new InJvmSut(cluster);
+ }
+
+ @Test
+ public void testQuiescentLocalStateChecker() throws Throwable
+ {
+ Supplier<SchemaSpec> supplier = SchemaGenerators.progression(1);
+ for (int i = 0; i < SchemaGenerators.GENERATORS_COUNT; i++)
+ {
+ SchemaSpec schema = supplier.get();
+
+ Configuration config = configuration(i, schema)
+ .setKeyspaceDdl("CREATE KEYSPACE IF NOT EXISTS " + schema.keyspace + " WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1};")
+ .setCreateSchema(true)
+ .setDropSchema(true)
+ .build();
+
+ Runner.chain(config,
+ Runner.sequential(MutatingVisitor::new, 2, TimeUnit.SECONDS),
+ Runner.single(AllPartitionsValidator.factoryForTests(1, QuiescentLocalStateChecker.factory(new TokenPlacementModel.SimpleReplicationFactor(1)))))
+ .run();
+ break;
+ }
+ }
+
+ @Override
+ protected Configuration.ModelConfiguration modelConfiguration()
+ {
+ return new Configuration.QuiescentCheckerConfig();
+ }
+}
\ No newline at end of file
diff --git a/test/distributed/org/apache/cassandra/fuzz/harry/integration/model/ReconcilerIntegrationTest.java b/test/distributed/org/apache/cassandra/fuzz/harry/integration/model/ReconcilerIntegrationTest.java
new file mode 100644
index 0000000000..fecc321bbb
--- /dev/null
+++ b/test/distributed/org/apache/cassandra/fuzz/harry/integration/model/ReconcilerIntegrationTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.fuzz.harry.integration.model;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+
+import org.apache.cassandra.harry.ddl.ColumnSpec;
+import org.apache.cassandra.harry.ddl.SchemaSpec;
+import org.apache.cassandra.harry.dsl.ReplayingHistoryBuilder;
+import org.apache.cassandra.harry.gen.DataGenerators;
+import org.apache.cassandra.harry.sut.SystemUnderTest;
+import org.apache.cassandra.harry.sut.TokenPlacementModel;
+import org.apache.cassandra.harry.tracker.DefaultDataTracker;
+
+public class ReconcilerIntegrationTest extends IntegrationTestBase
+{
+ private final long seed = 1; // 88
+
+
+ @Test
+ public void testTrackingWithStatics() throws Throwable
+ {
+ SchemaSpec schema = new SchemaSpec(KEYSPACE, "tbl1",
+ Arrays.asList(ColumnSpec.pk("pk1", ColumnSpec.int64Type)),
+ Arrays.asList(ColumnSpec.ck("ck1", ColumnSpec.int64Type)),
+ Arrays.asList(ColumnSpec.regularColumn("v1", ColumnSpec.int64Type),
+ ColumnSpec.regularColumn("v2", ColumnSpec.int64Type)),
+ Arrays.asList(ColumnSpec.staticColumn("static1", ColumnSpec.int64Type),
+ ColumnSpec.staticColumn("static2", ColumnSpec.int64Type)))
+ .trackLts();
+
+ beforeEach();
+ sut.schemaChange(schema.compile().cql());
+
+ TokenPlacementModel.ReplicationFactor rf = new TokenPlacementModel.SimpleReplicationFactor(1);
+ ReplayingHistoryBuilder historyBuilder = new ReplayingHistoryBuilder(seed, 100, 1, new DefaultDataTracker(), sut, schema, rf, SystemUnderTest.ConsistencyLevel.QUORUM);
+ historyBuilder.visitPartition(1).insert(1,
+ new long[]{ DataGenerators.UNSET_DESCR, DataGenerators.UNSET_DESCR },
+ new long[]{ 1L, 1L });
+ historyBuilder.validate(1);
+
+ historyBuilder.visitPartition(2).insert(2,
+ new long[]{ 1L, 1L },
+ new long[]{ 1L, 1L });
+ historyBuilder.visitPartition(2).deleteRowRange(1, 3, true, true);
+ historyBuilder.validate(2);
+ }
+
+ @Test
+ public void testTrackingWithoutStatics() throws Throwable
+ {
+ SchemaSpec schema = new SchemaSpec(KEYSPACE, "tbl1",
+ Arrays.asList(ColumnSpec.pk("pk1", ColumnSpec.int64Type)),
+ Arrays.asList(ColumnSpec.ck("ck1", ColumnSpec.int64Type)),
+ Arrays.asList(ColumnSpec.regularColumn("v1", ColumnSpec.int64Type),
+ ColumnSpec.regularColumn("v2", ColumnSpec.int64Type)),
+ Arrays.asList())
+ .trackLts();
+
+ beforeEach();
+ sut.schemaChange(schema.compile().cql());
+
+ TokenPlacementModel.ReplicationFactor rf = new TokenPlacementModel.SimpleReplicationFactor(1);
+ ReplayingHistoryBuilder historyBuilder = new ReplayingHistoryBuilder(seed, 100, 1, new DefaultDataTracker(), sut, schema, rf, SystemUnderTest.ConsistencyLevel.QUORUM);
+ historyBuilder.visitPartition(2).insert(2,
+ new long[]{ 1L, 1L });
+ historyBuilder.visitPartition(2).deleteRowRange(1, 3, true, true);
+ historyBuilder.validate(2);
+
+ historyBuilder = new ReplayingHistoryBuilder(seed, 100, 1, new DefaultDataTracker(), sut, schema, rf, SystemUnderTest.ConsistencyLevel.QUORUM);
+ historyBuilder.visitPartition(2).insert(2,
+ new long[]{ 1L, 1L });
+ historyBuilder.visitPartition(2).deleteRowRange(1, 3, true, true);
+ historyBuilder.validate(2);
+ }
+}
diff --git a/test/distributed/org/apache/cassandra/fuzz/harry/integration/model/reconciler/SimpleReconcilerTest.java b/test/distributed/org/apache/cassandra/fuzz/harry/integration/model/reconciler/SimpleReconcilerTest.java
new file mode 100644
index 0000000000..d240ecf037
--- /dev/null
+++ b/test/distributed/org/apache/cassandra/fuzz/harry/integration/model/reconciler/SimpleReconcilerTest.java
@@ -0,0 +1,341 @@
+/*
+ * 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.fuzz.harry.integration.model.reconciler;
+
+import java.util.*;
+
+import org.junit.Test;
+
+import org.apache.cassandra.harry.core.Configuration;
+import org.apache.cassandra.harry.core.Run;
+import org.apache.cassandra.harry.ddl.ColumnSpec;
+import org.apache.cassandra.harry.ddl.SchemaGenerators;
+import org.apache.cassandra.harry.ddl.SchemaSpec;
+import org.apache.cassandra.harry.gen.Surjections;
+import org.apache.cassandra.harry.model.OpSelectors;
+import org.apache.cassandra.harry.model.QuiescentChecker;
+import org.apache.cassandra.harry.model.SelectHelper;
+import org.apache.cassandra.harry.model.reconciler.PartitionState;
+import org.apache.cassandra.harry.sut.injvm.InJvmSut;
+import org.apache.cassandra.harry.operations.CompiledStatement;
+import org.apache.cassandra.harry.operations.Query;
+import org.apache.cassandra.harry.operations.WriteHelper;
+import org.apache.cassandra.harry.util.BitSet;
+import org.apache.cassandra.distributed.api.ConsistencyLevel;
+import org.apache.cassandra.fuzz.harry.integration.model.IntegrationTestBase;
+
+public class SimpleReconcilerTest extends IntegrationTestBase
+{
+ public static Surjections.Surjection<SchemaSpec> defaultSchemaSpecGen(String ks, String table)
+ {
+ return new SchemaGenerators.Builder(ks, () -> table)
+ .partitionKeySpec(1, 3,
+ ColumnSpec.int64Type,
+ ColumnSpec.asciiType(5, 256))
+ .clusteringKeySpec(1, 3,
+ ColumnSpec.int64Type,
+ ColumnSpec.asciiType(2, 3),
+ ColumnSpec.ReversedType.getInstance(ColumnSpec.int64Type),
+ ColumnSpec.ReversedType.getInstance(ColumnSpec.asciiType(2, 3)))
+ .regularColumnSpec(50, 50,
+ ColumnSpec.int64Type,
+ ColumnSpec.asciiType(5, 256))
+ .staticColumnSpec(50, 50,
+ ColumnSpec.int64Type,
+ ColumnSpec.asciiType(4, 256))
+ .surjection();
+ }
+
+ @Test
+ public void testStatics() throws Throwable
+ {
+ int rowsPerPartition = 50;
+ SchemaSpec schema = defaultSchemaSpecGen("harry", "tbl").inflate(1);
+ Configuration config = sharedConfiguration(1, schema).build();
+ Run run = config.createRun();
+ SyntheticTest test = new SyntheticTest(run.rng, schema);
+ beforeEach();
+ cluster.schemaChange(schema.compile().cql());
+
+ ModelState state = new ModelState(new HashMap<>());
+ InJvmSut sut = (InJvmSut) run.sut;
+ Random rng = new Random(1);
+
+ int partitionIdx = 0;
+
+ for (int i = 0; i < 100; i++)
+ {
+ BitSet subset = BitSet.allUnset(schema.allColumns.size());
+ for (int j = 0; j < subset.size(); j++)
+ {
+ if (rng.nextBoolean())
+ subset.set(j);
+ }
+ if (!isValidSubset(schema.allColumns, subset))
+ continue;
+ int pdIdx = partitionIdx++;
+ long pd = test.pd(pdIdx);
+
+ for (int j = 0; j < 10; j++)
+ {
+ int cdIdx = rng.nextInt(rowsPerPartition);
+ long cd = test.cd(pdIdx, cdIdx);
+
+ long[] vds = run.descriptorSelector.descriptors(pd, cd, state.lts, 0, schema.regularColumns,
+ schema.regularColumnsMask(),
+ subset,
+ schema.regularColumnsOffset);
+ long[] sds = run.descriptorSelector.descriptors(pd, cd, state.lts, 0, schema.staticColumns,
+ schema.staticColumnsMask,
+ subset,
+ schema.staticColumnsOffset);
+
+ CompiledStatement statement = WriteHelper.inflateUpdate(schema, pd, cd, vds, sds, run.clock.rts(state.lts));
+ sut.cluster.coordinator(1).execute(statement.cql(), ConsistencyLevel.QUORUM, statement.bindings());
+
+ PartitionState partitionState = state.state.get(pd);
+ if (partitionState == null)
+ {
+ partitionState = new PartitionState(pd, -1, schema);
+ state.state.put(pd, partitionState);
+ }
+
+ partitionState.writeStaticRow(sds, state.lts);
+ partitionState.write(cd, vds, state.lts, true);
+
+ state.lts++;
+ }
+ }
+
+ // Validate that all partitions correspond to our expectations
+ for (Long pd : state.state.keySet())
+ {
+ ArrayList<Long> clusteringDescriptors = new ArrayList<>(state.state.get(pd).rows().keySet());
+
+ // TODO: allow sub-selection
+ // Try different column subsets
+ for (int i = 0; i < 10; i++)
+ {
+ BitSet bitset = BitSet.allUnset(schema.allColumns.size());
+ for (int j = 0; j < bitset.size(); j++)
+ {
+ if (rng.nextBoolean())
+ bitset.set(j);
+ }
+ Set<ColumnSpec<?>> subset = i == 0 ? null : subset(schema.allColumns, bitset);
+ if (subset != null && !isValidSubset(schema.allColumns, bitset))
+ continue;
+
+ int a = rng.nextInt(clusteringDescriptors.size());
+ long cd1tmp = clusteringDescriptors.get(a);
+ long cd2tmp;
+ int b;
+ while (true)
+ {
+ b = rng.nextInt(clusteringDescriptors.size());
+ long tmp = clusteringDescriptors.get(b);
+ if (tmp != cd1tmp)
+ {
+ cd2tmp = tmp;
+ break;
+ }
+ }
+
+ long cd1 = Math.min(cd1tmp, cd2tmp);
+ long cd2 = Math.max(cd1tmp, cd2tmp);
+
+ for (boolean reverse : new boolean[]{ true, false })
+ {
+ Query query;
+
+ query = Query.selectPartition(schema, pd, reverse);
+
+ QuiescentChecker.validate(schema,
+ run.tracker,
+ subset,
+ state.state.get(pd),
+ SelectHelper.execute(sut, run.clock, query, subset),
+ query);
+
+ query = Query.singleClustering(schema, pd, cd1, false);
+ QuiescentChecker.validate(schema,
+ run.tracker,
+ subset,
+ state.state.get(pd).apply(query),
+ SelectHelper.execute(sut, run.clock, query, subset),
+ query);
+
+ for (boolean isGt : new boolean[]{ true, false })
+ {
+ for (boolean isEquals : new boolean[]{ true, false })
+ {
+ try
+ {
+ query = Query.clusteringSliceQuery(schema, pd, cd1, rng.nextLong(), isGt, isEquals, reverse);
+ }
+ catch (IllegalArgumentException impossibleQuery)
+ {
+ continue;
+ }
+
+ QuiescentChecker.validate(schema,
+ run.tracker,
+ subset,
+ state.state.get(pd).apply(query),
+ SelectHelper.execute(sut, run.clock, query, subset),
+ query);
+ }
+ }
+
+ for (boolean isMinEq : new boolean[]{ true, false })
+ {
+ for (boolean isMaxEq : new boolean[]{ true, false })
+ {
+ try
+ {
+ query = Query.clusteringRangeQuery(schema, pd, cd1, cd2, rng.nextLong(), isMinEq, isMaxEq, reverse);
+ }
+ catch (IllegalArgumentException impossibleQuery)
+ {
+ continue;
+ }
+ QuiescentChecker.validate(schema,
+ run.tracker,
+ subset,
+ state.state.get(pd).apply(query),
+ SelectHelper.execute(sut, run.clock, query, subset),
+ query);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ public static boolean isValidSubset(List<ColumnSpec<?>> superset, BitSet bitSet)
+ {
+ boolean hasRegular = false;
+ for (int i = 0; i < superset.size(); i++)
+ {
+ ColumnSpec<?> column = superset.get(i);
+ if (column.kind == ColumnSpec.Kind.PARTITION_KEY && !bitSet.isSet(i))
+ return false;
+ if (column.kind == ColumnSpec.Kind.CLUSTERING && !bitSet.isSet(i))
+ return false;
+ if (column.kind == ColumnSpec.Kind.REGULAR && bitSet.isSet(i))
+ hasRegular = true;
+ }
+
+ return hasRegular;
+ }
+
+ public static Set<ColumnSpec<?>> subset(List<ColumnSpec<?>> superset, BitSet bitSet)
+ {
+ Set<ColumnSpec<?>> subset = new HashSet<>();
+ for (int i = 0; i < superset.size(); i++)
+ {
+ if (bitSet.isSet(i))
+ subset.add(superset.get(i));
+ }
+
+ return subset;
+ }
+
+ public static Set<ColumnSpec<?>> randomSubset(List<ColumnSpec<?>> superset, Random e)
+ {
+ Set<ColumnSpec<?>> set = new HashSet<>();
+ boolean hadRegular = false;
+ for (ColumnSpec<?> v : superset)
+ {
+ // TODO: allow selecting without partition and clustering key, too
+ if (e.nextBoolean() || v.kind == ColumnSpec.Kind.CLUSTERING || v.kind == ColumnSpec.Kind.PARTITION_KEY)
+ {
+ set.add(v);
+ hadRegular |= v.kind == ColumnSpec.Kind.REGULAR;
+ }
+ }
+
+ // TODO: this is an oversimplification and a workaround for "Invalid restrictions on clustering columns since the UPDATE statement modifies only static columns"
+ if (!hadRegular)
+ return randomSubset(superset, e);
+
+ return set;
+ }
+
+ public static <T> BitSet subsetToBitset(List<T> superset, Set<T> subset)
+ {
+ BitSet bitSet = new BitSet.BitSet64Bit(superset.size());
+ for (int i = 0; i < superset.size(); i++)
+ {
+ if (subset.contains(superset.get(i)))
+ bitSet.set(i);
+ }
+ return bitSet;
+ }
+
+ public static class ModelState
+ {
+ public long lts = 0;
+ public final Map<Long, PartitionState> state;
+
+ public ModelState(Map<Long, PartitionState> state)
+ {
+ this.state = state;
+ }
+ }
+
+ public static class SyntheticTest // TODO: horrible name
+ {
+ private static long PD_STREAM = System.nanoTime();
+ private final OpSelectors.PureRng rng;
+ private final SchemaSpec schema;
+
+ public SyntheticTest(OpSelectors.PureRng rng, SchemaSpec schema)
+ {
+ this.schema = schema;
+ this.rng = rng;
+ }
+
+ public long pd(int pdIdx)
+ {
+ long pd = this.rng.randomNumber(pdIdx + 1, PD_STREAM);
+ long adjusted = schema.adjustPdEntropy(pd);
+ assert adjusted == pd : "Partition descriptors not utilising all entropy bits are not supported.";
+ return pd;
+ }
+
+ public long pdIdx(long pd)
+ {
+ return this.rng.sequenceNumber(pd, PD_STREAM) - 1;
+ }
+
+ public long cd(int pdIdx, int cdIdx)
+ {
+ long cd = this.rng.randomNumber(cdIdx + 1, pd(pdIdx));
+ long adjusted = schema.adjustCdEntropy(cd);
+ assert adjusted == cd : "Clustering descriptors not utilising all entropy bits are not supported.";
+ return cd;
+ }
+
+ public long cdIdx(long pd)
+ {
+ return this.rng.sequenceNumber(pd, PD_STREAM) - 1;
+ }
+ }
+}
diff --git a/test/distributed/org/apache/cassandra/fuzz/harry/integration/op/RowVisitorTest.java b/test/distributed/org/apache/cassandra/fuzz/harry/integration/op/RowVisitorTest.java
new file mode 100644
index 0000000000..5978f1e877
--- /dev/null
+++ b/test/distributed/org/apache/cassandra/fuzz/harry/integration/op/RowVisitorTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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.fuzz.harry.integration.op;
+
+import java.util.function.Supplier;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.apache.cassandra.harry.core.MetricReporter;
+import org.apache.cassandra.harry.core.Run;
+import org.apache.cassandra.harry.ddl.SchemaGenerators;
+import org.apache.cassandra.harry.ddl.SchemaSpec;
+import org.apache.cassandra.harry.gen.EntropySource;
+import org.apache.cassandra.harry.gen.distribution.Distribution;
+import org.apache.cassandra.harry.model.OpSelectors;
+import org.apache.cassandra.harry.clock.OffsetClock;
+import org.apache.cassandra.harry.sut.SystemUnderTest;
+import org.apache.cassandra.harry.tracker.DataTracker;
+import org.apache.cassandra.harry.visitors.GeneratingVisitor;
+import org.apache.cassandra.harry.visitors.MutatingRowVisitor;
+import org.apache.cassandra.harry.visitors.MutatingVisitor;
+import org.apache.cassandra.cql3.CQLTester;
+
+import static org.apache.cassandra.harry.model.OpSelectors.DefaultDescriptorSelector.DEFAULT_OP_SELECTOR;
+
+public class RowVisitorTest extends CQLTester
+{
+ @Before
+ public void beforeTest() throws Throwable {
+ super.beforeTest();
+ schemaChange(String.format("CREATE KEYSPACE IF NOT EXISTS %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}", SchemaGenerators.DEFAULT_KEYSPACE_NAME));
+ }
+
+ @Test
+ public void rowWriteGeneratorTest()
+ {
+ Supplier<SchemaSpec> specGenerator = SchemaGenerators.progression(SchemaGenerators.DEFAULT_SWITCH_AFTER);
+ EntropySource rand = EntropySource.forTests(6371747244598697093L);
+
+ OpSelectors.PureRng rng = new OpSelectors.PCGFast(1);
+
+ OpSelectors.PdSelector pdSelector = new OpSelectors.DefaultPdSelector(rng, 10, 10);
+
+ for (int i = 0; i < SchemaGenerators.DEFAULT_RUNS; i++)
+ {
+ SchemaSpec schema = specGenerator.get();
+ createTable(schema.compile().cql());
+
+ OpSelectors.DescriptorSelector descriptorSelector = new OpSelectors.DefaultDescriptorSelector(rng,
+ new OpSelectors.ColumnSelectorBuilder().forAll(schema)
+ .build(),
+ DEFAULT_OP_SELECTOR,
+ new Distribution.ScaledDistribution(1, 30),
+ 100);
+
+ Run run = new Run(rng,
+ new OffsetClock(10000),
+ pdSelector,
+ descriptorSelector,
+ schema,
+ DataTracker.NO_OP,
+ SystemUnderTest.NO_OP,
+ MetricReporter.NO_OP);
+
+ long[] descriptors = new long[4];
+ for (int j = 0; j < descriptors.length; j++)
+ descriptors[j] = rand.next();
+
+ // some improvement wont hurt here
+ new GeneratingVisitor(run, new MutatingVisitor(run, MutatingRowVisitor::new)).visit();
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/distributed/org/apache/cassandra/fuzz/harry/model/ApproximateClockTest.java b/test/distributed/org/apache/cassandra/fuzz/harry/model/ApproximateClockTest.java
new file mode 100644
index 0000000000..4751b4789c
--- /dev/null
+++ b/test/distributed/org/apache/cassandra/fuzz/harry/model/ApproximateClockTest.java
@@ -0,0 +1,180 @@
+/*
+ * 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.fuzz.harry.model;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.LockSupport;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.cassandra.harry.clock.ApproximateClock;
+
+public class ApproximateClockTest
+{
+ @Test
+ public void approximateClockTest() throws InterruptedException
+ {
+ ConcurrentHashMap<Long, Long> m = new ConcurrentHashMap<>();
+ ConcurrentHashMap<Long, Long> inverse = new ConcurrentHashMap<>();
+ TimeUnit timeUnit = TimeUnit.MILLISECONDS;
+ int duration = 1000;
+ int concurrency = 5;
+ long maxTicks = timeUnit.toMicros(duration) / (4 * concurrency);
+
+ ApproximateClock clock = new ApproximateClock(duration, timeUnit);
+
+ ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(1);
+ ExecutorService executor = Executors.newFixedThreadPool(concurrency);
+ final Lock lock = new ReentrantLock();
+ AtomicReference<Throwable> throwable = new AtomicReference();
+ final Condition signalError = lock.newCondition();
+ lock.lock();
+ for (int i = 0; i < concurrency; i++)
+ {
+ executor.submit(() -> {
+ try
+ {
+ int sleepCnt = 0;
+
+ while (!executor.isShutdown() && !Thread.currentThread().isInterrupted())
+ {
+ sleepCnt++;
+ if (sleepCnt >= maxTicks)
+ {
+ LockSupport.parkNanos(timeUnit.toNanos(duration));
+ sleepCnt = 0;
+ }
+
+ if (executor.isShutdown() || Thread.currentThread().isInterrupted())
+ return;
+
+ long lts = clock.nextLts();
+
+ // Make sure to test "history" path
+ if (lts % 10000 == 0)
+ {
+ scheduledExecutor.schedule(() -> {
+ try
+ {
+ long rts = clock.rts(lts);
+ Assert.assertNull(m.put(lts, rts));
+ Assert.assertNull(inverse.put(rts, lts));
+ }
+ catch (Throwable t)
+ {
+ throwable.set(t);
+ signalError.signalAll();
+ t.printStackTrace();
+ }
+ }, 2 * duration, timeUnit);
+ continue;
+ }
+
+ try
+ {
+ long rts = clock.rts(lts);
+ Assert.assertNull(m.put(lts, rts));
+ Assert.assertNull(inverse.put(rts, lts));
+ }
+ catch (Throwable t)
+ {
+ throwable.set(t);
+ signalError.signalAll();
+ }
+ }
+ }
+ catch (Throwable t)
+ {
+ throwable.set(t);
+ signalError.signalAll();
+ t.printStackTrace();
+ }
+ });
+ }
+ signalError.await(10, TimeUnit.SECONDS);
+ lock.unlock();
+ executor.shutdown();
+ Assert.assertTrue(executor.awaitTermination(30, TimeUnit.SECONDS));
+ scheduledExecutor.shutdown();
+ Assert.assertTrue(scheduledExecutor.awaitTermination(10, TimeUnit.SECONDS));
+ Throwable t = throwable.get();
+ if (t != null)
+ throw new AssertionError("Caught an exception while executing", t);
+
+ Assert.assertEquals(m.size(), inverse.size());
+ Iterator<Map.Entry<Long, Long>> iter = m.entrySet().iterator();
+
+ Map.Entry<Long, Long> previous = iter.next();
+ while (iter.hasNext())
+ {
+ if (previous == null)
+ {
+ previous = iter.next();
+ continue;
+ }
+
+ Map.Entry<Long, Long> current = iter.next();
+ long lts = current.getKey();
+ long rts = current.getValue();
+ Assert.assertEquals(String.format("%s and %s sort wrong", previous, current),
+ Long.compare(previous.getKey(), current.getKey()),
+ Long.compare(previous.getValue(), current.getValue()));
+
+ Assert.assertEquals(clock.rts(lts), rts);
+ Assert.assertEquals(clock.lts(rts), lts);
+ previous = current;
+ }
+ }
+
+ @Test
+ public void approximateClockInvertibilityTest()
+ {
+ ConcurrentHashMap<Long, Long> m = new ConcurrentHashMap<>();
+ TimeUnit timeUnit = TimeUnit.MILLISECONDS;
+ int duration = 100;
+ int cycles = 10_000;
+ ApproximateClock clock = new ApproximateClock(duration, timeUnit);
+
+ for (long i = 0; i < cycles; i++)
+ {
+ long lts = clock.nextLts();
+ Assert.assertEquals(lts, i);
+ long rts = clock.rts(lts);
+ Assert.assertNull(m.put(lts, rts));
+ }
+
+ for (Map.Entry<Long, Long> entry : m.entrySet())
+ {
+ Assert.assertEquals(entry.getKey(),
+ Long.valueOf(clock.lts(entry.getValue())));
+ }
+ }
+
+}
diff --git a/test/distributed/org/apache/cassandra/fuzz/harry/model/OpSelectorsTest.java b/test/distributed/org/apache/cassandra/fuzz/harry/model/OpSelectorsTest.java
new file mode 100644
index 0000000000..e9984c1ef3
--- /dev/null
+++ b/test/distributed/org/apache/cassandra/fuzz/harry/model/OpSelectorsTest.java
@@ -0,0 +1,418 @@
+/*
+ * 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.fuzz.harry.model;
+
+import java.util.*;
+import java.util.function.BiConsumer;
+import java.util.function.Supplier;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.cassandra.harry.core.MetricReporter;
+import org.apache.cassandra.harry.core.Run;
+import org.apache.cassandra.harry.ddl.ColumnSpec;
+import org.apache.cassandra.harry.ddl.SchemaGenerators;
+import org.apache.cassandra.harry.ddl.SchemaSpec;
+import org.apache.cassandra.harry.gen.Surjections;
+import org.apache.cassandra.harry.gen.distribution.Distribution;
+import org.apache.cassandra.harry.clock.OffsetClock;
+import org.apache.cassandra.harry.model.OpSelectors;
+import org.apache.cassandra.harry.sut.SystemUnderTest;
+import org.apache.cassandra.harry.operations.CompiledStatement;
+import org.apache.cassandra.harry.tracker.DataTracker;
+import org.apache.cassandra.harry.visitors.LtsVisitor;
+import org.apache.cassandra.harry.visitors.MutatingVisitor;
+import org.apache.cassandra.harry.visitors.OperationExecutor;
+import org.apache.cassandra.harry.util.BitSet;
+import org.apache.cassandra.harry.visitors.VisitExecutor;
+
+public class OpSelectorsTest
+{
+ private static int RUNS = 1000;
+ private static SchemaSpec SCHEMA = new SchemaSpec("ks", "tbl",
+ Collections.singletonList(ColumnSpec.pk("pk", ColumnSpec.int64Type)),
+ Collections.singletonList(ColumnSpec.pk("ck", ColumnSpec.int64Type)),
+ Collections.emptyList(), Collections.emptyList());
+
+ @Test
+ public void testRowDataDescriptorSupplier()
+ {
+ OpSelectors.PureRng rng = new OpSelectors.PCGFast(1);
+ SchemaSpec schema = new SchemaSpec("ks", "tbl1",
+ Arrays.asList(ColumnSpec.pk("pk1", ColumnSpec.asciiType),
+ ColumnSpec.pk("pk2", ColumnSpec.int64Type)),
+ Arrays.asList(ColumnSpec.ck("ck1", ColumnSpec.asciiType, false),
+ ColumnSpec.ck("ck2", ColumnSpec.int64Type, false)),
+ Arrays.asList(ColumnSpec.regularColumn("v1", ColumnSpec.int32Type),
+ ColumnSpec.regularColumn("v2", ColumnSpec.int64Type)),
+ Arrays.asList(ColumnSpec.staticColumn("static1", ColumnSpec.asciiType),
+ ColumnSpec.staticColumn("static2", ColumnSpec.int64Type)));
+ OpSelectors.DefaultDescriptorSelector descriptorSelector = new OpSelectors.DefaultDescriptorSelector(rng,
+ new OpSelectors.ColumnSelectorBuilder().forAll(schema)
+ .build(),
+ OpSelectors.DefaultDescriptorSelector.DEFAULT_OP_SELECTOR,
+ new Distribution.ScaledDistribution(2, 10),
+ 50);
+
+ OpSelectors.PdSelector pdSupplier = new OpSelectors.DefaultPdSelector(rng,
+ 100,
+ 100);
+
+ for (int lts = 0; lts < RUNS; lts++)
+ {
+ long pd = pdSupplier.pd(lts, schema);
+
+ int opsPerLts = descriptorSelector.operationsPerLts(lts);
+ for (int opId = 0; opId < opsPerLts; opId++)
+ {
+ long cd = descriptorSelector.cd(pd, lts, opId, schema);
+ Assert.assertEquals(opId, descriptorSelector.opId(pd, lts, cd));
+ Assert.assertTrue(descriptorSelector.isCdVisitedBy(pd, lts, cd));
+ for (int col = 0; col < 10; col++)
+ {
+ long vd = descriptorSelector.vd(pd, cd, lts, opId, col);
+ }
+ }
+ }
+ }
+
+ @Test
+ public void pdSelectorSymmetryTest()
+ {
+ OpSelectors.PureRng rng = new OpSelectors.PCGFast(1);
+ Supplier<SchemaSpec> gen = SchemaGenerators.progression(SchemaGenerators.DEFAULT_SWITCH_AFTER);
+ SchemaSpec schema = gen.get();
+
+ for (long[] positions : new long[][]{ { 0, Long.MAX_VALUE }, { 100, Long.MAX_VALUE }, { 1000, Long.MAX_VALUE } })
+ {
+ for (int repeats = 2; repeats <= 1000; repeats++)
+ {
+ for (int windowSize = 2; windowSize <= 10; windowSize++)
+ {
+ OpSelectors.DefaultPdSelector pdSelector = new OpSelectors.DefaultPdSelector(rng, windowSize, repeats, positions[0], positions[1]);
+
+ Map<Long, List<Long>> m = new HashMap<>();
+ final long maxLts = 10_000;
+ for (long lts = 0; lts <= maxLts; lts++)
+ {
+ long pd = pdSelector.pd(lts, schema);
+ m.computeIfAbsent(pd, (k) -> new ArrayList<>()).add(lts);
+ }
+
+ for (Long pd : m.keySet())
+ {
+ long currentLts = pdSelector.minLtsFor(pd);
+ List<Long> predicted = new ArrayList<>();
+ while (currentLts <= maxLts && currentLts >= 0)
+ {
+ predicted.add(currentLts);
+ currentLts = pdSelector.nextLts(currentLts);
+ }
+ Assert.assertEquals(m.get(pd), predicted);
+ }
+
+
+ }
+ }
+ }
+ }
+
+ @Test
+ public void pdSelectorTest()
+ {
+ OpSelectors.PureRng rng = new OpSelectors.PCGFast(1);
+ int cycles = 10000;
+ for (long[] positions : new long[][]{ { 0, Long.MAX_VALUE }, { 100, Long.MAX_VALUE }, { 1000, Long.MAX_VALUE } })
+ {
+ for (int repeats = 2; repeats <= 1000; repeats++)
+ {
+ for (int windowSize = 2; windowSize <= 10; windowSize++)
+ {
+ OpSelectors.DefaultPdSelector pdSupplier = new OpSelectors.DefaultPdSelector(rng, windowSize, repeats, positions[0], positions[1]);
+ long[] pds = new long[cycles];
+ for (int i = 0; i < cycles; i++)
+ {
+ long pd = pdSupplier.pd(i, SCHEMA);
+ pds[i] = pd;
+ Assert.assertEquals(pdSupplier.positionFor(i), pdSupplier.positionForPd(pd));
+ }
+
+ Set<Long> noNext = new HashSet<>();
+ for (int i = 0; i < cycles; i++)
+ {
+ long nextLts = pdSupplier.nextLts(i);
+ Assert.assertFalse(noNext.contains(pds[i]));
+ if (nextLts == -1)
+ {
+ noNext.add(nextLts);
+ }
+ else if (nextLts < cycles)
+ {
+ Assert.assertEquals(pds[(int) nextLts], pdSupplier.pd(i, SCHEMA));
+ }
+ }
+
+ Set<Long> noPrev = new HashSet<>();
+ for (int i = cycles - 1; i >= 0; i--)
+ {
+ long prevLts = pdSupplier.prevLts(i);
+ Assert.assertFalse(noPrev.contains(pds[i]));
+ if (prevLts == -1)
+ {
+ noPrev.add(prevLts);
+ }
+ else if (prevLts >= 0)
+ {
+ Assert.assertEquals(pds[(int) prevLts], pdSupplier.pd(i, SCHEMA));
+ }
+ }
+
+ Set<Long> seen = new HashSet<>();
+ for (int i = 0; i < cycles; i++)
+ {
+ long pd = pdSupplier.pd(i, SCHEMA);
+ if (!seen.contains(pd))
+ {
+ Assert.assertEquals(i, pdSupplier.minLtsAt(pdSupplier.positionFor(i)));
+ seen.add(pd);
+ }
+ }
+
+ for (int i = 0; i < cycles; i++)
+ {
+ long pd = pdSupplier.pd(i, SCHEMA);
+ long maxLts = pdSupplier.maxLtsFor(pd);
+ Assert.assertEquals(-1, pdSupplier.nextLts(maxLts));
+ Assert.assertEquals(pdSupplier.pd(i, SCHEMA), pdSupplier.pd(maxLts, SCHEMA));
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ public void ckSelectorTest()
+ {
+ Supplier<SchemaSpec> gen = SchemaGenerators.progression(SchemaGenerators.DEFAULT_SWITCH_AFTER);
+ for (int i = 0; i < SchemaGenerators.DEFAULT_RUNS; i++)
+ ckSelectorTest(gen.get());
+ }
+
+ public void ckSelectorTest(SchemaSpec schema)
+ {
+ OpSelectors.PureRng rng = new OpSelectors.PCGFast(1);
+ OpSelectors.PdSelector pdSelector = new OpSelectors.DefaultPdSelector(rng, 10, 10);
+ OpSelectors.DescriptorSelector ckSelector = new OpSelectors.DefaultDescriptorSelector(rng,
+ new OpSelectors.ColumnSelectorBuilder().forAll(schema, Surjections.pick(schema.regularColumnsMask())).build(),
+ OpSelectors.OperationSelector.weighted(Surjections.weights(10, 10, 40, 40),
+ OpSelectors.OperationKind.DELETE_ROW,
+ OpSelectors.OperationKind.DELETE_COLUMN,
+ OpSelectors.OperationKind.INSERT,
+ OpSelectors.OperationKind.UPDATE),
+ new Distribution.ConstantDistribution(10),
+ 10);
+
+ Map<Long, Set<Long>> partitionMap = new HashMap<>();
+ CompiledStatement compiledStatement = new CompiledStatement("");
+ BiConsumer<Long, Long> consumer = (pd, cd) -> {
+ partitionMap.compute(pd, (pk, list) -> {
+ if (list == null)
+ list = new HashSet<>();
+ list.add(cd);
+ return list;
+ });
+ };
+
+ Run run = new Run(rng,
+ new OffsetClock(0),
+ pdSelector,
+ ckSelector,
+ schema,
+ DataTracker.NO_OP,
+ SystemUnderTest.NO_OP,
+ MetricReporter.NO_OP);
+
+ LtsVisitor visitor = new MutatingVisitor(run,
+ (r) -> new OperationExecutor()
+ {
+ public CompiledStatement insert(VisitExecutor.WriteOp op)
+ {
+ consumer.accept(op.pd(), op.cd());
+ return compiledStatement;
+ }
+
+ public CompiledStatement update(VisitExecutor.WriteOp op)
+ {
+ consumer.accept(op.pd(), op.cd());
+ return compiledStatement;
+ }
+
+ public CompiledStatement insertWithStatics(VisitExecutor.WriteStaticOp op)
+ {
+ consumer.accept(op.pd(), op.cd());
+ return compiledStatement;
+ }
+
+ public CompiledStatement updateWithStatics(VisitExecutor.WriteStaticOp op)
+ {
+ consumer.accept(op.pd(), op.cd());
+ return compiledStatement;
+ }
+
+ public CompiledStatement deleteColumn(VisitExecutor.DeleteColumnsOp op)
+ {
+ consumer.accept(op.pd(), op.cd());
+ return compiledStatement;
+ }
+
+ public CompiledStatement deleteColumnWithStatics(VisitExecutor.DeleteColumnsOp op)
+ {
+ consumer.accept(op.pd(), op.cd());
+ return compiledStatement;
+ }
+
+ public CompiledStatement deleteRow(VisitExecutor.DeleteRowOp op)
+ {
+ consumer.accept(op.pd(), op.cd());
+ return compiledStatement;
+ }
+
+ public CompiledStatement deletePartition(VisitExecutor.DeleteOp op)
+ {
+ // ignore
+ return compiledStatement;
+ }
+
+ public CompiledStatement deleteRange(VisitExecutor.DeleteOp op)
+ {
+ // ignore
+ return compiledStatement;
+ }
+
+ public CompiledStatement deleteSlice(VisitExecutor.DeleteOp op)
+ {
+ // ignore
+ return compiledStatement;
+ }
+ });
+
+ for (int lts = 0; lts < 1000; lts++)
+ visitor.visit();
+
+ for (Collection<Long> value : partitionMap.values())
+ Assert.assertEquals(10, value.size());
+ }
+
+ @Test
+ public void hierarchicalDescriptorSelector()
+ {
+ SchemaSpec schema = new SchemaSpec("ks", "tbl1",
+ Collections.singletonList(ColumnSpec.pk("pk1", ColumnSpec.asciiType)),
+ Arrays.asList(ColumnSpec.ck("ck1", ColumnSpec.asciiType),
+ ColumnSpec.ck("ck2", ColumnSpec.asciiType),
+ ColumnSpec.ck("ck3", ColumnSpec.asciiType)),
+ Collections.singletonList(ColumnSpec.regularColumn("v1", ColumnSpec.asciiType)),
+ Collections.emptyList());
+
+ OpSelectors.PureRng rng = new OpSelectors.PCGFast(1);
+ OpSelectors.DescriptorSelector ckSelector = new OpSelectors.HierarchicalDescriptorSelector(rng,
+ new int[] {10, 20},
+ OpSelectors.columnSelectorBuilder().forAll(schema, Surjections.pick(BitSet.allUnset(0))).build(),
+ OpSelectors.OperationSelector.weighted(Surjections.weights(10, 10, 40, 40),
+ OpSelectors.OperationKind.DELETE_ROW,
+ OpSelectors.OperationKind.DELETE_COLUMN,
+ OpSelectors.OperationKind.INSERT,
+ OpSelectors.OperationKind.UPDATE),
+ new Distribution.ConstantDistribution(10),
+ 100);
+
+ Set<Long> ck1 = new TreeSet<>();
+ Set<Long> ck2 = new TreeSet<>();
+ Set<Long> ck3 = new TreeSet<>();
+ for (int i = 0; i < 1000; i++)
+ {
+ long[] part = schema.ckGenerator.slice(ckSelector.cd(0, i, 0, schema));
+ ck1.add(part[0]);
+ ck2.add(part[1]);
+ ck3.add(part[2]);
+ }
+ Assert.assertEquals(10, ck1.size());
+ Assert.assertEquals(20, ck2.size());
+ Assert.assertEquals(100, ck3.size());
+ }
+
+ @Test
+ public void testWeights()
+ {
+ Map<OpSelectors.OperationKind, Integer> config = new EnumMap<>(OpSelectors.OperationKind.class);
+ config.put(OpSelectors.OperationKind.DELETE_RANGE, 1);
+ config.put(OpSelectors.OperationKind.DELETE_SLICE, 1);
+ config.put(OpSelectors.OperationKind.DELETE_ROW, 1);
+ config.put(OpSelectors.OperationKind.DELETE_COLUMN, 1);
+ config.put(OpSelectors.OperationKind.DELETE_PARTITION, 1);
+ config.put(OpSelectors.OperationKind.DELETE_COLUMN_WITH_STATICS, 1);
+ config.put(OpSelectors.OperationKind.UPDATE, 500);
+ config.put(OpSelectors.OperationKind.INSERT, 500);
+ config.put(OpSelectors.OperationKind.UPDATE_WITH_STATICS, 500);
+ config.put(OpSelectors.OperationKind.INSERT_WITH_STATICS, 500);
+
+ int[] weights = new int[config.size()];
+ for (int i = 0; i < config.values().size(); i++)
+ weights[i] = config.get(OpSelectors.OperationKind.values()[i]);
+ OpSelectors.OperationSelector selector = OpSelectors.OperationSelector.weighted(Surjections.weights(weights),
+ OpSelectors.OperationKind.values());
+
+ OpSelectors.PureRng rng = new OpSelectors.PCGFast(1);
+ OpSelectors.PdSelector pdSelector = new OpSelectors.DefaultPdSelector(rng, 10, 10);
+ OpSelectors.DescriptorSelector descriptorSelector = new OpSelectors.DefaultDescriptorSelector(rng,
+ null,
+ selector,
+ new Distribution.ConstantDistribution(10),
+ 100);
+
+ EnumMap<OpSelectors.OperationKind, Integer> m = new EnumMap<OpSelectors.OperationKind, Integer>(OpSelectors.OperationKind.class);
+ for (int lts = 0; lts < 1000000; lts++)
+ {
+ int total = descriptorSelector.operationsPerLts(lts);
+ long pd = pdSelector.pd(lts, SCHEMA);
+ for (int opId = 0; opId < total; opId++)
+ {
+ m.compute(descriptorSelector.operationType(pd, lts, opId),
+ (OpSelectors.OperationKind k, Integer old) -> {
+ if (old == null) return 1;
+ else return old + 1;
+ });
+ }
+ }
+
+ for (OpSelectors.OperationKind l : OpSelectors.OperationKind.values())
+ {
+ for (OpSelectors.OperationKind r : OpSelectors.OperationKind.values())
+ {
+ if (l != r)
+ {
+ Assert.assertEquals(m.get(l) * 1.0 / m.get(r),
+ config.get(l) * 1.0 / config.get(r),
+ (config.get(l) * 1.0 / config.get(r)) * 0.10);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/distributed/org/apache/cassandra/fuzz/harry/operations/RelationTest.java b/test/distributed/org/apache/cassandra/fuzz/harry/operations/RelationTest.java
new file mode 100644
index 0000000000..975a87a13e
--- /dev/null
+++ b/test/distributed/org/apache/cassandra/fuzz/harry/operations/RelationTest.java
@@ -0,0 +1,204 @@
+/*
+ * 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.fuzz.harry.operations;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.cassandra.harry.ddl.ColumnSpec;
+import org.apache.cassandra.harry.ddl.SchemaSpec;
+import org.apache.cassandra.fuzz.harry.gen.DataGeneratorsTest;
+import org.apache.cassandra.harry.model.OpSelectors;
+import org.apache.cassandra.harry.operations.Query;
+import org.apache.cassandra.harry.operations.QueryGenerator;
+import org.apache.cassandra.harry.util.BitSet;
+
+public class RelationTest
+{
+ private static int RUNS = 50;
+
+ @Test
+ public void testKeyGenerators()
+ {
+ for (int size = 1; size < 5; size++)
+ {
+ Iterator<ColumnSpec.DataType[]> iter = DataGeneratorsTest.permutations(size,
+ ColumnSpec.DataType.class,
+ ColumnSpec.int8Type,
+ ColumnSpec.asciiType,
+ ColumnSpec.int16Type,
+ ColumnSpec.int32Type,
+ ColumnSpec.int64Type,
+ ColumnSpec.floatType,
+ ColumnSpec.doubleType
+ );
+ while (iter.hasNext())
+ {
+ ColumnSpec.DataType[] types = iter.next();
+ List<ColumnSpec<?>> spec = new ArrayList<>(types.length);
+ for (int i = 0; i < types.length; i++)
+ spec.add(ColumnSpec.ck("r" + i, types[i], false));
+
+ SchemaSpec schemaSpec = new SchemaSpec("ks",
+ "tbl",
+ Collections.singletonList(ColumnSpec.pk("pk", ColumnSpec.int64Type)),
+ spec,
+ Collections.emptyList(),
+ Collections.emptyList());
+
+ long[] cds = new long[RUNS];
+
+ int[] fractions = new int[schemaSpec.clusteringKeys.size()];
+ int last = cds.length;
+ for (int i = fractions.length - 1; i >= 0; i--)
+ {
+ fractions[i] = last;
+ last = last / 2;
+ }
+
+ for (int i = 0; i < cds.length; i++)
+ {
+ long cd = OpSelectors.HierarchicalDescriptorSelector.cd(i, fractions, schemaSpec, new OpSelectors.PCGFast(1L), 1L);
+ cds[i] = schemaSpec.adjustPdEntropy(cd);
+ }
+ Arrays.sort(cds);
+
+ OpSelectors.PureRng rng = new OpSelectors.PCGFast(1L);
+
+ // TODO: replace with mocks?
+ QueryGenerator querySelector = new QueryGenerator(schemaSpec,
+ new OpSelectors.PdSelector()
+ {
+ protected long pd(long lts)
+ {
+ return lts;
+ }
+
+ public long nextLts(long lts)
+ {
+ throw new RuntimeException("not implemented");
+ }
+
+ public long prevLts(long lts)
+ {
+ throw new RuntimeException("not implemented");
+ }
+
+ public long maxLtsFor(long pd)
+ {
+ throw new RuntimeException("not implemented");
+ }
+
+ public long minLtsAt(long position)
+ {
+ throw new RuntimeException("not implemented");
+ }
+
+
+ public long minLtsFor(long pd)
+ {
+ throw new RuntimeException("not implemented");
+ }
+
+ public long maxPosition(long maxLts)
+ {
+ throw new RuntimeException("not implemented");
+ }
+ },
+ new OpSelectors.DescriptorSelector()
+ {
+ public int operationsPerLts(long lts)
+ {
+ throw new RuntimeException("not implemented");
+ }
+
+ public int maxPartitionSize()
+ {
+ throw new RuntimeException("not implemented");
+ }
+
+ public boolean isCdVisitedBy(long pd, long lts, long cd)
+ {
+ throw new RuntimeException("not implemented");
+ }
+
+ protected long cd(long pd, long lts, long opId)
+ {
+ throw new RuntimeException("not implemented");
+ }
+
+ public long randomCd(long pd, long entropy)
+ {
+ return Math.abs(rng.prev(entropy)) % cds.length;
+ }
+
+ protected long vd(long pd, long cd, long lts, long opId, int col)
+ {
+ throw new RuntimeException("not implemented");
+ }
+
+ public OpSelectors.OperationKind operationType(long pd, long lts, long opId)
+ {
+ throw new RuntimeException("not implemented");
+ }
+
+ public BitSet columnMask(long pd, long lts, long opId, OpSelectors.OperationKind opType)
+ {
+ throw new RuntimeException("not implemented");
+ }
+
+ public long opId(long pd, long lts, long cd)
+ {
+ return 0;
+ }
+ },
+ rng);
+
+ QueryGenerator.TypedQueryGenerator gen = new QueryGenerator.TypedQueryGenerator(rng, querySelector);
+
+ try
+ {
+ for (int i = 0; i < RUNS; i++)
+ {
+ Query query = gen.inflate(i, 0);
+ for (int j = 0; j < cds.length; j++)
+ {
+ long cd = schemaSpec.ckGenerator.adjustEntropyDomain(cds[i]);
+ // the only thing we care about here is that query
+ Assert.assertEquals(String.format("Error caught while running a query %s with cd %d",
+ query, cd),
+ Query.simpleMatch(query, cd),
+ query.matchCd(cd));
+ }
+ }
+ }
+ catch (Throwable t)
+ {
+ throw new AssertionError("Caught error for the type combination " + Arrays.toString(types), t);
+ }
+ }
+ }
+ }
+}
diff --git a/test/distributed/org/apache/cassandra/fuzz/harry/runner/LockingDataTrackerTest.java b/test/distributed/org/apache/cassandra/fuzz/harry/runner/LockingDataTrackerTest.java
new file mode 100644
index 0000000000..94b9f2e131
--- /dev/null
+++ b/test/distributed/org/apache/cassandra/fuzz/harry/runner/LockingDataTrackerTest.java
@@ -0,0 +1,133 @@
+/*
+ * 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.fuzz.harry.runner;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.cassandra.concurrent.ExecutorFactory;
+import org.apache.cassandra.concurrent.Interruptible;
+import org.apache.cassandra.harry.ddl.SchemaGenerators;
+import org.apache.cassandra.harry.ddl.SchemaSpec;
+import org.apache.cassandra.harry.model.OpSelectors;
+import org.apache.cassandra.harry.runner.Runner;
+import org.apache.cassandra.harry.tracker.LockingDataTracker;
+import org.apache.cassandra.utils.concurrent.WaitQueue;
+
+import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.Daemon.NON_DAEMON;
+import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.Interrupts.UNSYNCHRONIZED;
+import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.SimulatorSafe.SAFE;
+
+public class LockingDataTrackerTest
+{
+ @Test
+ public void testDataTracker() throws Throwable
+ {
+ SchemaSpec schemaSpec = SchemaGenerators.defaultSchemaSpecGen("test").inflate(1L);
+ OpSelectors.PureRng rng = new OpSelectors.PCGFast(1L);
+ OpSelectors.PdSelector pdSelector = new OpSelectors.DefaultPdSelector(rng, 5, 2);
+ LockingDataTracker tracker = new LockingDataTracker(pdSelector, schemaSpec);
+
+ AtomicReference<State> excluded = new AtomicReference<>(State.UNLOCKED);
+ AtomicInteger readers = new AtomicInteger(0);
+ AtomicInteger writers = new AtomicInteger(0);
+
+ WaitQueue queue = WaitQueue.newWaitQueue();
+ WaitQueue.Signal interrupt = queue.register();
+ List<Throwable> errors = new CopyOnWriteArrayList<>();
+
+ long lts = 1;
+ long pd = pdSelector.pd(lts, schemaSpec);
+ int parallelism = 2;
+ for (int i = 0; i < parallelism; i++)
+ {
+ ExecutorFactory.Global.executorFactory().infiniteLoop("write-" + i, Runner.wrapInterrupt(state -> {
+ try
+ {
+ if (state == Interruptible.State.NORMAL)
+ {
+ tracker.beginModification(lts);
+ Assert.assertEquals(0, readers.get());
+ writers.incrementAndGet();
+ excluded.updateAndGet((prev) -> {
+ assert (prev == State.UNLOCKED || prev == State.LOCKED_FOR_WRITE) : prev;
+ return State.LOCKED_FOR_WRITE;
+ });
+ Assert.assertEquals(0, readers.get());
+ excluded.updateAndGet((prev) -> {
+ assert (prev == State.UNLOCKED || prev == State.LOCKED_FOR_WRITE) : prev;
+ return State.UNLOCKED;
+ });
+ Assert.assertEquals(0, readers.get());
+ writers.decrementAndGet();
+ tracker.endModification(lts);
+ }
+ }
+ catch (Throwable t)
+ {
+ t.printStackTrace();
+ throw t;
+ }
+ }, interrupt::signal, errors::add), SAFE, NON_DAEMON, UNSYNCHRONIZED);
+ }
+
+ for (int i = 0; i < parallelism; i++)
+ {
+ ExecutorFactory.Global.executorFactory().infiniteLoop("read-" + i, Runner.wrapInterrupt(state -> {
+ try
+ {
+ if (state == Interruptible.State.NORMAL)
+ {
+ tracker.beginValidation(pd);
+ Assert.assertEquals(0, writers.get());
+ readers.incrementAndGet();
+ excluded.updateAndGet((prev) -> {
+ assert (prev == State.UNLOCKED || prev == State.LOCKED_FOR_READ) : prev;
+ return State.LOCKED_FOR_READ;
+ });
+ Assert.assertEquals(0, writers.get());
+ excluded.updateAndGet((prev) -> {
+ assert (prev == State.UNLOCKED || prev == State.LOCKED_FOR_READ) : prev;
+ return State.UNLOCKED;
+ });
+ Assert.assertEquals(0, writers.get());
+ readers.decrementAndGet();
+ tracker.endValidation(pd);
+ }
+ }
+ catch (Throwable t)
+ {
+ t.printStackTrace();
+ throw t;
+ }
+ }, interrupt::signal, errors::add), SAFE, NON_DAEMON, UNSYNCHRONIZED);
+ }
+
+ interrupt.await(1, TimeUnit.MINUTES);
+ if (!errors.isEmpty())
+ Runner.mergeAndThrow(errors);
+ }
+ enum State { UNLOCKED, LOCKED_FOR_READ, LOCKED_FOR_WRITE }
+}
diff --git a/test/distributed/org/apache/cassandra/fuzz/harry/util/BitSetTest.java b/test/distributed/org/apache/cassandra/fuzz/harry/util/BitSetTest.java
new file mode 100644
index 0000000000..ee992a36dc
--- /dev/null
+++ b/test/distributed/org/apache/cassandra/fuzz/harry/util/BitSetTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.fuzz.harry.util;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.cassandra.harry.util.BitSet;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+// TODO: these are not real tests
+public class BitSetTest
+{
+ @Test
+ public void testBitMask()
+ {
+ Assert.assertEquals(0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000L, BitSet.bitMask(-1));
+ assertEquals(0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000L, BitSet.bitMask(0));
+ assertEquals(0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000001L, BitSet.bitMask(1));
+ assertEquals(0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000011L, BitSet.bitMask(2));
+ assertEquals(0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_00001111L, BitSet.bitMask(4));
+ assertEquals(0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_11111111L, BitSet.bitMask(8));
+ assertEquals(0b00000000_00000000_00000000_00000000_00000000_00000000_11111111_11111111L, BitSet.bitMask(16));
+ assertEquals(0b00000000_00000000_00000000_00000000_11111111_11111111_11111111_11111111L, BitSet.bitMask(32));
+ assertEquals(0b11111111_11111111_11111111_11111111_11111111_11111111_11111111_11111111L, BitSet.bitMask(64));
+ assertEquals(0b11111111_11111111_11111111_11111111_11111111_11111111_11111111_11111111L, BitSet.bitMask(65));
+ }
+
+ @Test
+ public void testSetBits()
+ {
+ BitSet bs = BitSet.allUnset(32);
+ bs.set(10);
+ bs.set(13);
+ bs.set(15);
+ assertEquals(3, bs.setCount());
+ }
+
+ @Test
+ public void testEachSetBit()
+ {
+ BitSet bs = BitSet.allUnset(32);
+ bs.set(10);
+ bs.set(13);
+ bs.set(15);
+ bs.eachSetBit(i -> assertTrue(i == 10 || i == 13 || i == 15));
+ }
+}
diff --git a/test/distributed/org/apache/cassandra/fuzz/harry/util/RangesTest.java b/test/distributed/org/apache/cassandra/fuzz/harry/util/RangesTest.java
new file mode 100644
index 0000000000..38231ee249
--- /dev/null
+++ b/test/distributed/org/apache/cassandra/fuzz/harry/util/RangesTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.fuzz.harry.util;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.cassandra.harry.util.DescriptorRanges;
+
+public class RangesTest
+{
+
+ @Test
+ public void simpleRangesTest()
+ {
+ List<DescriptorRanges.DescriptorRange> list = Arrays.asList(inclusiveRange(10, 20, 10),
+ inclusiveRange(40, 50, 10),
+ inclusiveRange(60, 70, 10),
+ inclusiveRange(80, 90, 10));
+ Collections.shuffle(list);
+ DescriptorRanges ranges = new DescriptorRanges(list);
+
+ Assert.assertTrue(ranges.isShadowed(10, 5));
+ Assert.assertFalse(ranges.isShadowed(10, 20));
+ Assert.assertFalse(ranges.isShadowed(15, 20));
+ Assert.assertTrue(ranges.isShadowed(49, 5));
+ Assert.assertFalse(ranges.isShadowed(55, 5));
+ Assert.assertFalse(ranges.isShadowed(50, 20));
+ Assert.assertTrue(ranges.isShadowed(90, 9));
+ }
+
+ @Test
+ public void randomizedRangesTest()
+ {
+ for (int i = 0; i < 1000; i++)
+ _randomizedRangesTest();
+ }
+
+ private void _randomizedRangesTest()
+ {
+ List<DescriptorRanges.DescriptorRange> rangesList = new ArrayList<>();
+ Random rnd = new Random();
+ for (int i = 0; i < 100; i++)
+ {
+ long a = rnd.nextInt(1000);
+ long b = rnd.nextInt(1000);
+ DescriptorRanges.DescriptorRange range = new DescriptorRanges.DescriptorRange(Math.min(a, b),
+ Math.max(a,b),
+ rnd.nextBoolean(),
+ rnd.nextBoolean(),
+ rnd.nextInt(1000));
+ rangesList.add(range);
+ }
+ DescriptorRanges ranges = new DescriptorRanges(rangesList);
+
+ for (int i = 0; i < 10000; i++)
+ {
+ long descriptor = rnd.nextLong();
+ long ts = rnd.nextInt(1000);
+ Assert.assertEquals(matchLinear(rangesList, descriptor, ts),
+ ranges.isShadowed(descriptor, ts));
+
+ }
+ }
+
+ public boolean matchLinear(List<DescriptorRanges.DescriptorRange> ranges, long descriptor, long ts)
+ {
+ for (DescriptorRanges.DescriptorRange range : ranges)
+ {
+ if (range.contains(descriptor, ts))
+ return true;
+ }
+ return false;
+ }
+
+ public DescriptorRanges.DescriptorRange inclusiveRange(long start, long end, long ts)
+ {
+ return new DescriptorRanges.DescriptorRange(start, end, true, true, 10);
+ }
+}
diff --git a/test/distributed/org/apache/cassandra/distributed/upgrade/ClusterMetadataUpgradeHarryTest.java b/test/distributed/org/apache/cassandra/fuzz/ring/ClusterMetadataUpgradeHarryTest.java
similarity index 74%
rename from test/distributed/org/apache/cassandra/distributed/upgrade/ClusterMetadataUpgradeHarryTest.java
rename to test/distributed/org/apache/cassandra/fuzz/ring/ClusterMetadataUpgradeHarryTest.java
index 776e7b1073..1d8a5db820 100644
--- a/test/distributed/org/apache/cassandra/distributed/upgrade/ClusterMetadataUpgradeHarryTest.java
+++ b/test/distributed/org/apache/cassandra/fuzz/ring/ClusterMetadataUpgradeHarryTest.java
@@ -16,7 +16,7 @@
* limitations under the License.
*/
-package org.apache.cassandra.distributed.upgrade;
+package org.apache.cassandra.fuzz.ring;
import java.util.Collections;
import java.util.concurrent.ExecutorService;
@@ -26,32 +26,37 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import com.google.common.util.concurrent.Uninterruptibles;
+
+import org.apache.cassandra.distributed.upgrade.UpgradeTestBase;
+import org.apache.cassandra.harry.HarryHelper;
+import org.apache.cassandra.harry.core.Configuration;
+import org.apache.cassandra.harry.ddl.SchemaSpec;
+
+import org.apache.cassandra.harry.runner.FlaggedRunner;
+import org.apache.cassandra.harry.sut.injvm.ClusterState;
+import org.apache.cassandra.harry.sut.injvm.ExistingClusterSUT;
+
+import org.apache.cassandra.harry.visitors.MutatingVisitor;
+import org.apache.cassandra.harry.visitors.QueryLogger;
+import org.apache.cassandra.harry.visitors.RandomPartitionValidator;
+
import org.junit.Test;
-import harry.core.Configuration;
-import harry.ddl.SchemaSpec;
-import harry.visitors.MutatingVisitor;
-import harry.visitors.QueryLogger;
-import harry.visitors.RandomPartitionValidator;
import org.apache.cassandra.distributed.Constants;
import org.apache.cassandra.distributed.api.Feature;
-import org.apache.cassandra.distributed.fuzz.FixedSchemaProviderConfiguration;
-import org.apache.cassandra.distributed.fuzz.HarryHelper;
-import org.apache.cassandra.distributed.harry.ClusterState;
-import org.apache.cassandra.distributed.harry.ExistingClusterSUT;
-import org.apache.cassandra.distributed.harry.FlaggedRunner;
import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.utils.concurrent.CountDownLatch;
-import static harry.core.Configuration.VisitorPoolConfiguration.pool;
-import static harry.ddl.ColumnSpec.asciiType;
-import static harry.ddl.ColumnSpec.ck;
-import static harry.ddl.ColumnSpec.int64Type;
-import static harry.ddl.ColumnSpec.pk;
-import static harry.ddl.ColumnSpec.regularColumn;
-import static harry.ddl.ColumnSpec.staticColumn;
+
import static java.util.Arrays.asList;
import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory;
+import static org.apache.cassandra.harry.core.Configuration.VisitorPoolConfiguration.pool;
+import static org.apache.cassandra.harry.ddl.ColumnSpec.asciiType;
+import static org.apache.cassandra.harry.ddl.ColumnSpec.int64Type;
+import static org.apache.cassandra.harry.ddl.ColumnSpec.ck;
+import static org.apache.cassandra.harry.ddl.ColumnSpec.pk;
+import static org.apache.cassandra.harry.ddl.ColumnSpec.regularColumn;
+import static org.apache.cassandra.harry.ddl.ColumnSpec.staticColumn;
import static org.apache.cassandra.tcm.log.SystemKeyspaceStorage.NAME;
import static org.junit.Assert.assertEquals;
@@ -72,15 +77,15 @@ public class ClusterMetadataUpgradeHarryTest extends UpgradeTestBase
.upgradesToCurrentFrom(v41)
.withUpgradeListener(listener)
.setup((cluster) -> {
- SchemaSpec schema = new SchemaSpec("harry", "test_table",
- asList(pk("pk1", asciiType), pk("pk2", int64Type)),
- asList(ck("ck1", asciiType), ck("ck2", int64Type)),
- asList(regularColumn("regular1", asciiType), regularColumn("regular2", int64Type)),
- asList(staticColumn("static1", asciiType), staticColumn("static2", int64Type)));
+ SchemaSpec schema = new SchemaSpec("harry", "test_table",
+ asList(pk("pk1", asciiType), pk("pk2", int64Type)),
+ asList(ck("ck1", asciiType), ck("ck2", int64Type)),
+ asList(regularColumn("regular1", asciiType), regularColumn("regular2", int64Type)),
+ asList(staticColumn("static1", asciiType), staticColumn("static2", int64Type)));
Configuration config = HarryHelper.defaultConfiguration()
.setKeyspaceDdl(String.format("CREATE KEYSPACE IF NOT EXISTS %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor': %d};", schema.keyspace, 3))
- .setSchemaProvider(new FixedSchemaProviderConfiguration(schema))
+ .setSchemaProvider(new Configuration.FixedSchemaProviderConfiguration(schema))
.setDataTracker(new Configuration.LockingDataTrackerConfiguration(-1l, -1l, Collections.emptyList()))
.setSUT(new ExistingClusterSUT(cluster, listener))
.build();
@@ -91,7 +96,7 @@ public class ClusterMetadataUpgradeHarryTest extends UpgradeTestBase
new FlaggedRunner(config.createRun(),
config,
asList(pool("Writer", 1, MutatingVisitor::new),
- pool("Reader", 1, (run) -> new RandomPartitionValidator(run, new Configuration.QuiescentCheckerConfig(), QueryLogger.NO_OP))),
+ pool("Reader", 1, (run) -> new RandomPartitionValidator(run, new Configuration.QuiescentCheckerConfig(), QueryLogger.NO_OP.NO_OP))),
stopLatch).run();
}
catch (Throwable e)
diff --git a/test/distributed/org/apache/cassandra/distributed/test/ring/ConsistentBootstrapTest.java b/test/distributed/org/apache/cassandra/fuzz/ring/ConsistentBootstrapTest.java
similarity index 68%
rename from test/distributed/org/apache/cassandra/distributed/test/ring/ConsistentBootstrapTest.java
rename to test/distributed/org/apache/cassandra/fuzz/ring/ConsistentBootstrapTest.java
index cab68089af..c03047478f 100644
--- a/test/distributed/org/apache/cassandra/distributed/test/ring/ConsistentBootstrapTest.java
+++ b/test/distributed/org/apache/cassandra/fuzz/ring/ConsistentBootstrapTest.java
@@ -16,20 +16,13 @@
* limitations under the License.
*/
-package org.apache.cassandra.distributed.test.ring;
+package org.apache.cassandra.fuzz.ring;
import java.util.concurrent.Callable;
import org.junit.Assert;
import org.junit.Test;
-import harry.core.Configuration;
-import harry.core.Run;
-import harry.operations.Query;
-import harry.visitors.LoggingVisitor;
-import harry.visitors.MutatingRowVisitor;
-import harry.visitors.MutatingVisitor;
-import harry.visitors.Visitor;
import org.apache.cassandra.distributed.Cluster;
import org.apache.cassandra.distributed.Constants;
import org.apache.cassandra.distributed.api.ConsistencyLevel;
@@ -37,10 +30,13 @@ import org.apache.cassandra.distributed.api.Feature;
import org.apache.cassandra.distributed.api.IInstanceConfig;
import org.apache.cassandra.distributed.api.IInvokableInstance;
import org.apache.cassandra.distributed.api.TokenSupplier;
-import org.apache.cassandra.distributed.fuzz.HarryHelper;
-import org.apache.cassandra.distributed.fuzz.InJvmSut;
+import org.apache.cassandra.harry.HarryHelper;
import org.apache.cassandra.distributed.shared.NetworkTopology;
import org.apache.cassandra.distributed.test.log.FuzzTestBase;
+import org.apache.cassandra.harry.dsl.ReplayingHistoryBuilder;
+import org.apache.cassandra.harry.sut.SystemUnderTest;
+import org.apache.cassandra.harry.sut.TokenPlacementModel;
+import org.apache.cassandra.harry.sut.injvm.InJvmSut;
import org.apache.cassandra.metrics.TCMMetrics;
import org.apache.cassandra.net.Verb;
import org.apache.cassandra.tcm.Epoch;
@@ -55,49 +51,35 @@ public class ConsistentBootstrapTest extends FuzzTestBase
{
private static int WRITES = 500;
- private static final Configuration.ConfigurationBuilder configBuilder;
-
- static
- {
- try
- {
- configBuilder = HarryHelper.defaultConfiguration()
- .setPartitionDescriptorSelector(new Configuration.DefaultPDSelectorConfiguration(1, 1))
- .setClusteringDescriptorSelector(HarryHelper.defaultClusteringDescriptorSelectorConfiguration().setMaxPartitionSize(100).build());
- }
- catch (Exception e)
- {
- throw new RuntimeException(e);
- }
- }
-
@Test
public void bootstrapFuzzTest() throws Throwable
{
+ IInvokableInstance cmsInstance = null;
try (Cluster cluster = builder().withNodes(3)
.withTokenSupplier(TokenSupplier.evenlyDistributedTokens(4))
.withNodeIdTopology(NetworkTopology.singleDcNetworkTopology(4, "dc0", "rack0"))
.withConfig((config) -> config.with(Feature.NETWORK, Feature.GOSSIP).set("metadata_snapshot_frequency", 5))
.start())
{
- IInvokableInstance cmsInstance = cluster.get(1);
+ cmsInstance = cluster.get(1);
waitForCMSToQuiesce(cluster, cmsInstance);
- configBuilder.setSUT(() -> new InJvmSut(cluster));
- Run run = configBuilder.build().createRun();
- cluster.coordinator(1).execute("CREATE KEYSPACE " + run.schemaSpec.keyspace +
- " WITH replication = {'class': 'SimpleStrategy', 'replication_factor' : 3};",
+ ReplayingHistoryBuilder harry = HarryHelper.dataGen(new InJvmSut(cluster),
+ new TokenPlacementModel.SimpleReplicationFactor(3),
+ SystemUnderTest.ConsistencyLevel.ALL);
+ cluster.coordinator(1).execute(String.format("CREATE KEYSPACE %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor' : 3};", HarryHelper.KEYSPACE),
ConsistencyLevel.ALL);
- cluster.coordinator(1).execute(run.schemaSpec.compile().cql(), ConsistencyLevel.ALL);
+ cluster.coordinator(1).execute(harry.schema().compile().cql(), ConsistencyLevel.ALL);
waitForCMSToQuiesce(cluster, cluster.get(1));
- Visitor visitor = new LoggingVisitor(run, MutatingRowVisitor::new);
- QuiescentLocalStateChecker model = new QuiescentLocalStateChecker(run);
- System.out.println("Starting write phase...");
- for (int i = 0; i < WRITES; i++)
- visitor.visit();
- System.out.println("Starting validate phase...");
- for (int lts = 0; lts < run.clock.peek(); lts++)
- model.validate(Query.selectPartition(run.schemaSpec, run.pdSelector.pd(lts, run.schemaSpec), false));
+ Runnable writeAndValidate = () -> {
+ System.out.println("Starting write phase...");
+ for (int i = 0; i < WRITES; i++)
+ harry.insert();
+
+ System.out.println("Starting validate phase...");
+ harry.validateAll(harry.quiescentLocalChecker());
+ };
+ writeAndValidate.run();
IInstanceConfig config = cluster.newInstanceConfig()
.set("auto_bootstrap", true)
@@ -109,20 +91,7 @@ public class ConsistentBootstrapTest extends FuzzTestBase
new Thread(() -> newInstance.startup()).start();
pending.call();
- for (int i = 0; i < WRITES; i++)
- visitor.visit();
-
- try
- {
- for (int lts = 0; lts < run.clock.peek(); lts++)
- model.validate(Query.selectPartition(run.schemaSpec, run.pdSelector.pd(lts, run.schemaSpec), false));
- }
- catch (Throwable t)
- {
- // Unpause, since otherwise validation exception will prevent graceful shutdown
- unpauseCommits(cmsInstance);
- throw t;
- }
+ writeAndValidate.run();
// Make sure there can be only one FinishJoin in flight
waitForCMSToQuiesce(cluster, cmsInstance);
@@ -133,43 +102,50 @@ public class ConsistentBootstrapTest extends FuzzTestBase
unpauseCommits(cmsInstance);
waitForCMSToQuiesce(cluster, bootstrapVisible.call());
- for (int i = 0; i < WRITES; i++)
- visitor.visit();
- model.validateAll();
+ writeAndValidate.run();
+ }
+ catch (Throwable t)
+ {
+ if (cmsInstance != null)
+ unpauseCommits(cmsInstance);
+ throw t;
}
}
@Test
public void coordinatorIsBehindTest() throws Throwable
{
+ IInvokableInstance cmsInstance = null;
try (Cluster cluster = builder().withNodes(3)
.withTokenSupplier(TokenSupplier.evenlyDistributedTokens(4))
.withNodeIdTopology(NetworkTopology.singleDcNetworkTopology(4, "dc0", "rack0"))
.withConfig((config) -> config.with(Feature.NETWORK, Feature.GOSSIP).set("metadata_snapshot_frequency", 5))
.start())
{
- IInvokableInstance cmsInstance = cluster.get(1);
+ cmsInstance = cluster.get(1);
waitForCMSToQuiesce(cluster, cmsInstance);
- configBuilder.setSUT(() -> new InJvmSut(cluster, () -> 2, (t) -> false) {
- public Object[][] execute(String statement, ConsistencyLevel cl, int coordinator, Object... bindings)
- {
- try
- {
- return super.execute(statement, cl, coordinator, bindings);
- }
- catch (Throwable t)
- {
- // Avoid retries
- return new Object[][]{};
- }
- }
- });
- Run run = configBuilder.build().createRun();
- cluster.coordinator(1).execute("CREATE KEYSPACE " + run.schemaSpec.keyspace +
- " WITH replication = {'class': 'SimpleStrategy', 'replication_factor' : 3};",
+ ReplayingHistoryBuilder harry = HarryHelper.dataGen(new InJvmSut(cluster, () -> 2, (t) -> false)
+ {
+ public Object[][] execute(String statement, ConsistencyLevel cl, int coordinator, Object... bindings)
+ {
+ try
+ {
+ return super.execute(statement, cl, coordinator, bindings);
+ }
+ catch (Throwable t)
+ {
+ // Avoid retries
+ return new Object[][]{};
+ }
+ }
+ },
+ new TokenPlacementModel.SimpleReplicationFactor(3),
+ SystemUnderTest.ConsistencyLevel.ALL);
+
+ cluster.coordinator(1).execute(String.format("CREATE KEYSPACE %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor' : 3};", HarryHelper.KEYSPACE),
ConsistencyLevel.ALL);
- cluster.coordinator(1).execute(run.schemaSpec.compile().cql(), ConsistencyLevel.ALL);
+ cluster.coordinator(1).execute(harry.schema().compile().cql(), ConsistencyLevel.ALL);
waitForCMSToQuiesce(cluster, cluster.get(1));
cluster.filters().verbs(Verb.TCM_REPLICATION.id,
@@ -180,7 +156,6 @@ public class ConsistentBootstrapTest extends FuzzTestBase
.drop()
.on();
- Visitor visitor = new MutatingVisitor(run, MutatingRowVisitor::new);
IInstanceConfig config = cluster.newInstanceConfig()
.set("auto_bootstrap", true)
.set(Constants.KEY_DTEST_FULL_STARTUP, true)
@@ -189,7 +164,7 @@ public class ConsistentBootstrapTest extends FuzzTestBase
// Prime the CMS node to pause before the finish join event is committed
Callable<?> pending = pauseBeforeCommit(cmsInstance, (e) -> e instanceof PrepareJoin.MidJoin);
- long [] metricCounts = new long[4];
+ long[] metricCounts = new long[4];
for (int i = 1; i <= 4; i++)
metricCounts[i - 1] = cluster.get(i).callOnInstance(() -> TCMMetrics.instance.coordinatorBehindPlacements.getCount());
Thread thread = new Thread(() -> newInstance.startup());
@@ -206,7 +181,7 @@ public class ConsistentBootstrapTest extends FuzzTestBase
try
{
- visitor.visit();
+ harry.insert();
}
catch (Throwable t)
{
@@ -246,6 +221,11 @@ public class ConsistentBootstrapTest extends FuzzTestBase
unpauseCommits(cmsInstance);
thread.join();
}
+ catch (Throwable t)
+ {
+ if (cmsInstance != null)
+ unpauseCommits(cmsInstance);
+ throw t;
+ }
}
-
-}
+}
\ No newline at end of file
diff --git a/test/distributed/org/apache/cassandra/distributed/test/log/ConsistentLeaveTest.java b/test/distributed/org/apache/cassandra/fuzz/ring/ConsistentLeaveTest.java
similarity index 75%
rename from test/distributed/org/apache/cassandra/distributed/test/log/ConsistentLeaveTest.java
rename to test/distributed/org/apache/cassandra/fuzz/ring/ConsistentLeaveTest.java
index 7e5a608e30..8c98eb2e2f 100644
--- a/test/distributed/org/apache/cassandra/distributed/test/log/ConsistentLeaveTest.java
+++ b/test/distributed/org/apache/cassandra/fuzz/ring/ConsistentLeaveTest.java
@@ -16,7 +16,7 @@
* limitations under the License.
*/
-package org.apache.cassandra.distributed.test.log;
+package org.apache.cassandra.fuzz.ring;
import java.util.List;
import java.util.concurrent.Callable;
@@ -27,23 +27,22 @@ import com.google.common.util.concurrent.Uninterruptibles;
import org.junit.Assert;
import org.junit.Test;
-import harry.core.Configuration;
-import harry.core.Run;
-import harry.model.sut.SystemUnderTest;
-import harry.visitors.*;
import org.apache.cassandra.distributed.Cluster;
import org.apache.cassandra.distributed.api.ConsistencyLevel;
import org.apache.cassandra.distributed.api.Feature;
import org.apache.cassandra.distributed.api.IInvokableInstance;
import org.apache.cassandra.distributed.api.TokenSupplier;
-import org.apache.cassandra.distributed.fuzz.HarryHelper;
-import org.apache.cassandra.distributed.fuzz.InJvmSut;
-import org.apache.cassandra.distributed.fuzz.InJvmSutBase;
+import org.apache.cassandra.harry.HarryHelper;
import org.apache.cassandra.distributed.shared.NetworkTopology;
+import org.apache.cassandra.distributed.test.log.FuzzTestBase;
import org.apache.cassandra.gms.ApplicationState;
import org.apache.cassandra.gms.Gossiper;
+import org.apache.cassandra.harry.dsl.ReplayingHistoryBuilder;
+import org.apache.cassandra.harry.sut.SystemUnderTest;
+import org.apache.cassandra.harry.sut.TokenPlacementModel;
+import org.apache.cassandra.harry.sut.injvm.InJvmSut;
+import org.apache.cassandra.harry.sut.injvm.InJvmSutBase;
import org.apache.cassandra.locator.InetAddressAndPort;
-import org.apache.cassandra.locator.ReplicationFactor;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.tcm.transformations.PrepareLeave;
@@ -62,35 +61,36 @@ public class ConsistentLeaveTest extends FuzzTestBase
@Test
public void decommissionTest() throws Throwable
{
- Configuration.ConfigurationBuilder configBuilder = HarryHelper.defaultConfiguration()
- .setPartitionDescriptorSelector(new Configuration.DefaultPDSelectorConfiguration(1, 1))
- .setClusteringDescriptorSelector(HarryHelper.defaultClusteringDescriptorSelectorConfiguration().setMaxPartitionSize(100).build());
-
+ IInvokableInstance cmsInstance = null;
try (Cluster cluster = builder().withNodes(3)
.withTokenSupplier(TokenSupplier.evenlyDistributedTokens(3))
.withNodeIdTopology(NetworkTopology.singleDcNetworkTopology(3, "dc0", "rack0"))
.appendConfig(c -> c.with(Feature.NETWORK))
.start())
{
- IInvokableInstance cmsInstance = cluster.get(1);
+ cmsInstance = cluster.get(1);
IInvokableInstance leavingInstance = cluster.get(2);
waitForCMSToQuiesce(cluster, cmsInstance);
- configBuilder.setSUT(() -> new InJvmSut(cluster, () -> 1, InJvmSutBase.retryOnTimeout()));
- Run run = configBuilder.build().createRun();
- cluster.coordinator(1).execute("CREATE KEYSPACE " + run.schemaSpec.keyspace +
- " WITH replication = {'class': 'SimpleStrategy', 'replication_factor' : 2};",
+ ReplayingHistoryBuilder harry = HarryHelper.dataGen(new InJvmSut(cluster, () -> 1, InJvmSutBase.retryOnTimeout()),
+ new TokenPlacementModel.SimpleReplicationFactor(2),
+ SystemUnderTest.ConsistencyLevel.ALL);
+ cluster.coordinator(1).execute(String.format("CREATE KEYSPACE %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor' : 2};", HarryHelper.KEYSPACE),
ConsistencyLevel.ALL);
- cluster.coordinator(1).execute(run.schemaSpec.compile().cql(), ConsistencyLevel.ALL);
+ cluster.coordinator(1).execute(harry.schema().compile().cql(), ConsistencyLevel.ALL);
waitForCMSToQuiesce(cluster, cmsInstance);
- QuiescentLocalStateChecker model = new QuiescentLocalStateChecker(run, ReplicationFactor.fullOnly(2));
- Visitor visitor = new LoggingVisitor(run, MutatingRowVisitor::new);
- for (int i = 0; i < WRITES; i++)
- visitor.visit();
+ Runnable writeAndValidate = () -> {
+ System.out.println("Starting write phase...");
+ for (int i = 0; i < WRITES; i++)
+ harry.insert();
+
+ System.out.println("Starting validate phase...");
+ harry.validateAll(harry.quiescentLocalChecker());
+ };
+ writeAndValidate.run();
- model.validateAll();
// Prime the CMS node to pause before the finish leave event is committed
Callable<?> pending = pauseBeforeCommit(cmsInstance, (e) -> e instanceof PrepareLeave.FinishLeave);
new Thread(() -> leavingInstance.runOnInstance(() -> StorageService.instance.decommission(true))).start();
@@ -98,11 +98,8 @@ public class ConsistentLeaveTest extends FuzzTestBase
waitForCMSToQuiesce(cluster, cmsInstance);
assertGossipStatus(cluster, leavingInstance.config().num(), "LEAVING");
- visitor = new GeneratingVisitor(run, new MutatingVisitor.MutatingVisitExecutor(run, new MutatingRowVisitor(run), SystemUnderTest.ConsistencyLevel.ALL));
- for (int i = 0; i < WRITES; i++)
- visitor.visit();
- model.validateAll();
+ writeAndValidate.run();
// Make sure there can be only one FinishLeave in flight
waitForCMSToQuiesce(cluster, cmsInstance);
@@ -119,9 +116,13 @@ public class ConsistentLeaveTest extends FuzzTestBase
assertGossipStatus(cluster, leavingInstance.config().num(), "LEFT");
- for (int i = 0; i < WRITES; i++)
- visitor.visit();
- model.validateAll();
+ writeAndValidate.run();
+ }
+ catch (Throwable t)
+ {
+ if (cmsInstance != null)
+ unpauseCommits(cmsInstance);
+ throw t;
}
}
diff --git a/test/distributed/org/apache/cassandra/distributed/test/log/ConsistentMoveTest.java b/test/distributed/org/apache/cassandra/fuzz/ring/ConsistentMoveTest.java
similarity index 74%
rename from test/distributed/org/apache/cassandra/distributed/test/log/ConsistentMoveTest.java
rename to test/distributed/org/apache/cassandra/fuzz/ring/ConsistentMoveTest.java
index 4c6a75358e..4ee7bc6487 100644
--- a/test/distributed/org/apache/cassandra/distributed/test/log/ConsistentMoveTest.java
+++ b/test/distributed/org/apache/cassandra/fuzz/ring/ConsistentMoveTest.java
@@ -16,7 +16,7 @@
* limitations under the License.
*/
-package org.apache.cassandra.distributed.test.log;
+package org.apache.cassandra.fuzz.ring;
import java.util.List;
import java.util.Random;
@@ -27,26 +27,21 @@ import java.util.stream.Collectors;
import com.google.common.util.concurrent.Uninterruptibles;
import org.junit.Test;
-import harry.core.Configuration;
-import harry.core.Run;
-import harry.model.sut.SystemUnderTest;
-import harry.visitors.GeneratingVisitor;
-import harry.visitors.LoggingVisitor;
-import harry.visitors.MutatingRowVisitor;
-import harry.visitors.MutatingVisitor;
-import harry.visitors.Visitor;
import org.apache.cassandra.distributed.Cluster;
import org.apache.cassandra.distributed.api.ConsistencyLevel;
import org.apache.cassandra.distributed.api.Feature;
import org.apache.cassandra.distributed.api.IInvokableInstance;
import org.apache.cassandra.distributed.api.TokenSupplier;
-import org.apache.cassandra.distributed.fuzz.HarryHelper;
-import org.apache.cassandra.distributed.fuzz.InJvmSut;
+import org.apache.cassandra.harry.HarryHelper;
import org.apache.cassandra.distributed.shared.NetworkTopology;
+import org.apache.cassandra.distributed.test.log.FuzzTestBase;
import org.apache.cassandra.gms.ApplicationState;
import org.apache.cassandra.gms.Gossiper;
+import org.apache.cassandra.harry.dsl.ReplayingHistoryBuilder;
+import org.apache.cassandra.harry.sut.SystemUnderTest;
+import org.apache.cassandra.harry.sut.TokenPlacementModel;
+import org.apache.cassandra.harry.sut.injvm.InJvmSut;
import org.apache.cassandra.locator.InetAddressAndPort;
-import org.apache.cassandra.locator.ReplicationFactor;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.tcm.transformations.PrepareMove;
@@ -65,35 +60,35 @@ public class ConsistentMoveTest extends FuzzTestBase
@Test
public void moveTest() throws Throwable
{
- Configuration.ConfigurationBuilder configBuilder = HarryHelper.defaultConfiguration()
- .setPartitionDescriptorSelector(new Configuration.DefaultPDSelectorConfiguration(1, 1))
- .setClusteringDescriptorSelector(HarryHelper.defaultClusteringDescriptorSelectorConfiguration().setMaxPartitionSize(100).build());
-
+ IInvokableInstance cmsInstance = null;
try (Cluster cluster = builder().withNodes(3)
.withTokenSupplier(TokenSupplier.evenlyDistributedTokens(3))
.withNodeIdTopology(NetworkTopology.singleDcNetworkTopology(3, "dc0", "rack0"))
.appendConfig(c -> c.with(Feature.NETWORK))
.start())
{
- IInvokableInstance cmsInstance = cluster.get(1);
+ cmsInstance = cluster.get(1);
IInvokableInstance movingInstance = cluster.get(2);
waitForCMSToQuiesce(cluster, cmsInstance);
- configBuilder.setSUT(() -> new InJvmSut(cluster));
- Run run = configBuilder.build().createRun();
-
- cluster.coordinator(1).execute("CREATE KEYSPACE " + run.schemaSpec.keyspace +
- " WITH replication = {'class': 'SimpleStrategy', 'replication_factor' : 2};",
+ ReplayingHistoryBuilder harry = HarryHelper.dataGen(new InJvmSut(cluster),
+ new TokenPlacementModel.SimpleReplicationFactor(2),
+ SystemUnderTest.ConsistencyLevel.ALL);
+ cluster.coordinator(1).execute(String.format("CREATE KEYSPACE %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor' : 2};", HarryHelper.KEYSPACE),
ConsistencyLevel.ALL);
- cluster.coordinator(1).execute(run.schemaSpec.compile().cql(), ConsistencyLevel.ALL);
+ cluster.coordinator(1).execute(harry.schema().compile().cql(), ConsistencyLevel.ALL);
waitForCMSToQuiesce(cluster, cmsInstance);
- FuzzTestBase.QuiescentLocalStateChecker model = new FuzzTestBase.QuiescentLocalStateChecker(run, ReplicationFactor.fullOnly(2));
- Visitor visitor = new LoggingVisitor(run, MutatingRowVisitor::new);
- for (int i = 0; i < WRITES; i++)
- visitor.visit();
+ Runnable writeAndValidate = () -> {
+ System.out.println("Starting write phase...");
+ for (int i = 0; i < WRITES; i++)
+ harry.insert();
+
+ System.out.println("Starting validate phase...");
+ harry.validateAll(harry.quiescentLocalChecker());
+ };
+ writeAndValidate.run();
- model.validateAll();
// Make sure there can be only one FinishLeave in flight
waitForCMSToQuiesce(cluster, cmsInstance);
@@ -114,15 +109,7 @@ public class ConsistentMoveTest extends FuzzTestBase
waitForCMSToQuiesce(cluster, nextEpoch);
// TODO: rewrite the test to check only PENDING ranges.
- visitor = new GeneratingVisitor(run, new MutatingVisitor.MutatingVisitExecutor(run, new MutatingRowVisitor(run), SystemUnderTest.ConsistencyLevel.ALL));
- for (int i = 0; i < WRITES; i++)
- visitor.visit();
-
- model.validateAll();
-
- for (int i = 0; i < WRITES; i++)
- visitor.visit();
- model.validateAll();
+ writeAndValidate.run();
int clusterSize = cluster.size();
List<InetAddressAndPort> endpoints = cluster.stream().map(i -> InetAddressAndPort.getByAddress(i.config().broadcastAddress())).collect(Collectors.toList());
@@ -135,6 +122,12 @@ public class ConsistentMoveTest extends FuzzTestBase
}
}));
}
+ catch (Throwable t)
+ {
+ if (cmsInstance != null)
+ unpauseCommits(cmsInstance);
+ throw t;
+ }
}
private void assertGossipStatus(Cluster cluster, int leavingInstance, String status)
diff --git a/test/harry/main/README.md b/test/harry/main/README.md
new file mode 100644
index 0000000000..a2f2cfacb9
--- /dev/null
+++ b/test/harry/main/README.md
@@ -0,0 +1,647 @@
+# Harry, a fuzz testing tool for Apache Cassandra
+
+The project aims to generate _reproducible_ workloads that are as close to real-life as possible, while being able to
+_efficiently_ verify the cluster state against the model without pausing the workload itself.
+
+## Getting Started in under 5 minutes
+
+Harry can operate as a straightforward read/write "correctness stress tool" that will check to ensure reads on a cluster
+are consistent with what it knows it wrote to the cluster. You have a couple options for this.
+
+### Option 2: Running things manually lower in the stack:
+
+To start a workload that performs a concurrent read/write workload, 2 read and 2 write threads for 60 seconds
+against a in-jvm cluster you can use the following code:
+
+```
+try (Cluster cluster = builder().withNodes(3)
+ .start())
+{
+ SchemaSpec schema = new SchemaSpec("harry", "test_table",
+ asList(pk("pk1", asciiType), pk("pk1", int64Type)),
+ asList(ck("ck1", asciiType), ck("ck1", int64Type)),
+ asList(regularColumn("regular1", asciiType), regularColumn("regular1", int64Type)),
+ asList(staticColumn("static1", asciiType), staticColumn("static1", int64Type)));
+
+ Configuration config = HarryHelper.defaultConfiguration()
+ .setKeyspaceDdl(String.format("CREATE KEYSPACE IF NOT EXISTS %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor': %d};", schema.keyspace, 3))
+ .setSUT(() -> new InJvmSut(cluster))
+ .build();
+
+ Run run = config.createRun();
+
+ concurrent(run, config,
+ asList(pool("Writer", 2, MutatingVisitor::new),
+ pool("Reader", 2, RandomPartitionValidator::new)),
+ 2, TimeUnit.MINUTES)
+ .run();
+}
+```
+
+# I've found a falsification. What now?
+
+There is no one-size-fits-all solution for debugging a falsification. We did try to create a shrinker, but unfortunately
+without Simulator, shrinker only works for issues that are non-concurrent in nature, since there's no way to create a
+stable repro otherwise. That said, there are several things that might get you started and inspire further ideas about
+how to debug the issue.
+
+First of all, understand whether or not the issue is likely to be concurrent in nature. If you re-run your test with the
+same seed, but see no falsification, and it fails only sporadically, and often on different logical timestamp, it is
+likely that the issue is, in fact concurrent. Here, it is important to note that when you are running concurrent
+read/write workload, you will get different interleaving of reads and writes every time you do this.
+
+If you can get a stable repro with a sequential runner, you're in luck. Now all you need to do is to add logs everywhere
+and understand what might be causing it. But even if you do not have a stable repro, you are still likely to follow the
+same steps:
+
+* Inspect the error itself. Do Cassandra-returned results make sense? Is anything out of order? Are there any duplicates
+ or gaps?
+* Switch to logging mutating visitor and closely inspect its output. Closely inspect the output of the model. Do the
+ values make sense?
+* Check the output of data tracker. Does the model or Cassandra have missing columns or rows? Do these outputs contain
+ latest logical timestamps for each of the operations from the log? How about in-flight operations?
+* Filter out relevant operation log entries and inspect them closely. Given these operations, does the output of the
+ model, or output of the database make most sense?
+
+Next, you might want to try to narrow down the scope of the problem. Depending on what the falsification looks like, use
+your Cassandra knowledge to see what might apply in your situation:
+
+* Try checking if changing schema to use different column types does anything.
+* Try disabling range deletes, regular deletes, or column deletes.
+* Try changing the size of partition and see if the issue still reproduces.
+* Try disabling static columns.
+
+To avoid listing every feature in Harry, it suffices to say you should try to enable/disable features that make sense
+in the given context, and try to find the combination that avoids the failure, or a minimal combination that still
+reproduces the issue. Your first goal should be to find a _stable repro_, even if it involves modifying Cassandra or
+Harry, or taking the operations, and composing the repro manually. Having a stable repro will make finding a cause much
+simpler. Sometimes you will find the cause before you have a stable repro, in which case, you _still_ have to produce a
+stable repro to make things simpler for the reviewer, and to include it into the test suite of your patch.
+
+Lastly, *be patient*. Debugging falsifications is often a multi-hour endeavour, and things do not always jump out at you,
+so you might have to spend a significant amount of time tracking the problem down. Once you have found it, it is very
+rewarding.
+
+## Further Reading
+* [Harry: An open-source fuzz testing and verification tool for Apache Cassandra](https://cassandra.apache.org/_/blog/Harry-an-Open-Source-Fuzz-Testing-and-Verification-Tool-for-Apache-Cassandra.html)
+
+---
+# Technical and Implementation Details
+
+## System Under Test implementations
+
+* `in_jvm/InJvmSut` - simple in-JVM-dtest system under test.
+* `println/PrintlnSut` - system under test that prints to sdtout instead of executing queries on the cluster; useful for
+ debugging.
+* `mixed_in_jvm/MixedVersionInJvmSut` - in-JVM-dtest system under test that works with mixed version clusters.
+* `external/ExternalClusterSut` - system under test that works with CCM, Docker, Kubernetes, or cluster you may. have
+ deployed elsewhere
+
+Both in-JVM SUTs have fault-injecting functionality available.
+
+## Visitors
+
+* `single/SingleValidator` - visitor that runs several different read queries against a single partition that is
+ associated with current logical timestamp, and validates their results using given model.
+* `all_partitions/AllPartitionsValidator` - concurrently validates all partitions that were visited during this run.
+* `repair_and_validate_local_states/RepairingLocalStateValidator` - similar to `AllPartitionsValidator`, but performs
+ repair before checking node states.
+* `mutating/MutatingVisitor` - visitor that performs all sorts of mutations.
+* `logging/LoggingVisitor` - similar to `MutatingVisitor`, but also logs all operations to a file; useful for debug
+ purposes.
+* `corrupting/CorruptingVisitor` - visitor that will deliberately change data in the partition it visits. Useful for
+ negative tests (i.e. to ensure that your model actually detects data inconsistencies).
+
+And more.
+
+## Models
+
+* `querying_no_op/QueryingNoOpValidator` - a model that can be used to "simply" run random queries.
+* `quiescent_checker/QuiescentChecker` - a model that can be used to verify results of any read that has no writes to
+ the same partition_ concurrent to it. Should be used in conjunction with locking data tracker.
+* `quiescent_local_state_checker/QuiescentLocalStateChecker` - a model that can check local states of each replica that
+ has to own
+
+## Runners
+
+* `sequential/SequentialRunner` - runs all visitors sequentially, in the loop, for a specified amount of time; useful
+ for simple tests that do not have to exercise concurrent read/write path.
+* `concurrent/ConcurrentRunner` - runs all visitors concurrently, each visitor in its own thread, looped, for a
+ specified amount of time; useful for things like concurrent read/write workloads.
+* `chain/ChainRunner` - receives other runners as input, and runs them one after another once. Useful for both simple
+ and complex scenarios that involve both read/write workloads, validating all partitions, exercising other node-local
+ or cluster-wide operations.
+* `staged/StagedRunner` - receives other runners (stages) as input, and runs them one after another in a loop; useful
+ for implementing complex scenarios, such as read/write workloads followed by some cluster changing operations.
+
+## Clock
+
+* `approximate_monotonic/ApproximateMonotonicClock` - a timestamp supplier implementation that tries to keep as close to
+ real time as possible, while preserving mapping from real-time to logical timestamps.
+* `offset/OffsetClock` - a (monotonic) clock that supplies timestamps that do not have any relation to real time.
+
+# Introduction
+
+Harry has two primary modes of functionality:
+
+* Unit test mode: in which you define specific sequences of
+ operations and let Harry test these operations using different
+ schemas and conditions.
+* Exploratory/fuzz mode: in which you define distributions of events
+ rather rather than sequences themselves, and let Harry try out
+ different things.
+
+Usually, in unit-test mode, we’re applying several write operations to the cluster state and then run different read
+queries and validate their results. To learn more about writing unit tests, refer to the "Writing Unit Tests" section.
+
+In exploratory mode, we continuously apply write operations to the cluster and validate their state, allowing data size
+to grow and simulating real-life behaviour. To learn more about implementing test cases using fuzz mode, refer to the "
+Implementing Tests" section of this guide, but it's likely you'll have to read the rest of this document to implement
+more complex scenarios.
+
+# Writing Unit Tests
+
+To write unit tests with Harry, there's no special knowledge required. Usually, unit tests are written by simply
+hardcoding the schema and then writing several modification statements one after the other, and then manually validating
+results of a `SELECT` query. This might work for simple scenarios, but there’s still a chance that for some other schema
+or some combination of values the tested feature may not work.
+
+To improve the situation, we can express the test in more abstract terms and, instead of writing it using specific
+statements, we can describe which statement _types_ are to be used:
+
+```
+test(new SchemaGenerators.Builder("harry")
+ .partitionKeySpec(1, 5)
+ .clusteringKeySpec(1, 5)
+ .regularColumnSpec(1, 10)
+ .generator(),
+ historyBuilder -> {
+ historyBuilder.insert();
+ historyBuilder.deletePartition();
+ historyBuilder.deleteRowSlice();
+ });
+```
+
+This spec can be used to generate clusters of different sizes, configured with different schemas, executing the given a
+sequence of actions both in isolation, and combined with other randomly generated ones, with failure-injection.
+
+Best of all is that this test will _not only_ ensure that such a sequence of actions does not produce an exception, but
+also would ensure that cluster will respond with correct results to _any_ allowed read query.
+
+To begin specifying operations for a new partition, either start calling methods on the `HistoryBuilder`, or, if you
+would like to specify the partition which Harry needs to visit use `#visitPartition` or `#beginBatch` have to be called,
+for starting a visit to a particular partition with a single or multiple actions.
+
+After that, the actions are self-explanatory: `#insert`, `#update`, `#deleteRow`, `#deleteColumns`, `#deleteRowRange`, `#deleteRowSlice`
+`#deletePartition`.
+
+After history generated by `HistoryBuilder` is replayed using `ReplayingVisitor` (or by using a `ReplayingHistoryBuilder`
+which combines the two for your convenience), you can use any model (`QuiescentChecker` by default) to validate queries.
+Queries can be provided manually or generated using `QueryGenerator` or `TypedQueryGenerator`.
+
+# Basic Terminology
+
+* Inflate / inflatable: a process of producing a value (for example, string, or a blob) from a `long` descriptor that
+ uniquely identifies the value. See data generation section of this guide for more details.
+* Deflate / deflatable: a process of producing the descriptor the value was inflated from during verification.
+ See model section of this guide for more details.
+
+For definitions of logical timestamp, descriptor, and other entities used during inflation and deflation, refer
+to formal relationships section.
+
+# Features
+
+Currently, Harry can exercise the following Cassandra functionality:
+
+* Supported data types: `int8`, `int16`, `int32`, `int64`, `boolean`, `float`, `double`, `ascii`, `uuid`, `timestamp`.
+ Collections are only _inflatable_.
+* Random schema generation, with an arbitrary number of partition and clustering keys.
+* Schemas with arbitrary `CLUSTERING ORDER BY`
+* Randomly generated `INSERT` and `UPDATE` queries with all columns or arbitrary column subset
+* Randomly generated `DELETE` queries: for a single column, single row, or a range of rows
+* Inflating and validating entire partitions (with allowed in-flight queries)
+* Inflating and validating random `SELECT` queries: single row, slices (with single open end), and ranges (with both
+ ends of clusterings specified)
+
+Inflating partitions is done using `Reconciler`. Validating partitions and random queries can be done using `QuiescentChecker`.
+
+## Outstanding Work
+
+#### The following have not yet been implemented:
+
+* Some types (such as collections) are not deflatable
+* 2i queries are not implemented
+* Fault injection is not implemented (available via Cassandra Simulator)
+* TTL is not supported
+* Some SELECT queries are not supported: `LIMIT`, `IN`, `GROUP BY`, token range queries
+
+#### Some features can be improved upon or further optimized:
+
+* Pagination is currently implemented but hard-coded to a page size of 1
+* RNG should be able to yield less than 64 bits of entropy per step
+* State tracking should be done in a compact off-heap data structure
+* Inflated partition state and per-row operation log should be done in a compact
+ off-heap data structure
+* Decision-making about _when_ we visit partitions and/or rows should be improved
+
+While this list of improvements is incomplete, t should give the reader a rough idea about the state of the project.
+The original goal of the project was to drive to stability after the significant storage engine rewrite in
+CASSANDRA-8099 and help remove data loss bugs from the codebase before they got out into the wild. Next steps are to
+integrate it into both CI and into regular daily dev workflows.
+
+# Goals: Reproducibility and Efficiency
+
+_Reproducibility_ is achieved by using the PCG family of random number generators and generating schema, configuration,
+and every step of the workload from the repeatable sequence of random numbers. Schema and configuration are generated
+from the _seed_. Each operation is assigned its own monotonically increasing _logical timestamp_, which preserves
+logical operation order between different runs.
+
+_Efficiency_ is achieved by employing the features of the PCG random number generator (walking the sequence of random
+numbers back and forth), and writing value generators in a way that preserves properties of the descriptor it was
+generated from.
+
+Given a `long` _descriptor_ can be _inflated_ into some value:
+
+* value can be _deflated_ back to the descriptor it was generated from (in other words, generation is *invertible*)
+* two inflated values will sort the same way as two descriptors they were generated from (in other words, generation is
+ *order-preserving*)
+
+These properties are also preserved for the composite values, such as clustering and partition keys.
+
+# Components
+
+Every Harry run starts from Configuration. You can find an example configuration under `conf/example.yml`.
+
+*Clock* is a component responsible for mapping _logical_ timestamps to _real-time_ ones. When reproducing test failures,
+and for validation purposes, a snapshot of such be taken to map a real-time timestamp from the value retrieved from the
+database to map it back to the logical timestamp of the operation that wrote this value. Given a real-time timestamp,
+the clock can return a logical timestamp, and vice versa.
+
+*Runner* is a component that schedules operations that change the cluster (system under test) and model state.
+
+*System under test*: a Cassandra node or cluster. Default implementation is in_jvm (in-JVM DTest cluster). Harry also
+supports external clusters.
+
+*Model* is responsible for tracking logical timestamps that system under test was notified about.
+
+*Partition descriptor selector* controls how partitions are selected based on the current logical timestamp. The default
+implementation is a sliding window of partition descriptors that will visit one partition after the other in the
+window `slide_after_repeats` times. After that, it will retire one partition descriptor, and pick a new one.
+
+*Clustering descriptor selector* controls how clustering keys are picked within the partition: how many rows there can
+be in a partition, how many rows are visited for a logical timestamp, how many operations there will be in batch, what
+kind of operations there will and how often each kind of operation is going to occur.
+
+# Implementing Tests
+
+All Harry components are pluggable and can be redefined. However, many of the default implementations will cover most of
+the use-cases, so in this guide we’ll focus on ones that are most often used to implement different use cases:
+
+* System Under Test: defines how Harry can communicate with Cassandra instances and issue common queries. Examples of a
+ system under test can be a CCM cluster, a “real” Cassandra cluster, or an in-JVM dtest cluster.
+* Visitor: defines behaviour that gets triggered at a specific logical timestamp. One of the default implementations is
+ MutatingVisitor, which executes write workload against SystemUnderTest. Examples of a visitor, besides a mutating
+ visitor, could be a validator that uses the model to validate results of different queries, a repair runner, or a
+ fault injector.
+* Model: validates results of read queries by comparing its own internal representation against the results returned by
+ system under test. You can find three simplified implementations of model in this document: Visible Rows Checker,
+ Quiescent Checker, and an Exhaustive Checker.
+* Runner: defines how operations defined by visitors are executed. Harry includes two default implementations: a
+ sequential and a concurrent runner. Sequential runner allows no overlap between different visitors or logical
+ timestamps. Concurrent runner allows visitors for different timestamps to overlap.
+
+System under test is the simplest one to implement: you only need a way to execute Cassandra queries. At the moment of
+writing, all custom things, such as nodetool commands, failure injection, etc, are implemented using a SUT / visitor
+combo: visitor knows about internals of the cluster it is dealing with.
+
+Generally, visitor has to follow the rules specified by DescriptorSelector and PdSelector: (it can only visit issue
+mutations against the partition that PdSelector has picked for this LTS), and DescriptorSelector (it can visit exactly
+DescriptorSelector#numberOfModifications rows within this partition, operations have to have a type specified by
+#operationKind, clustering and value descriptors have to be in accordance with DescriptorSelector#cd and
+DescriptorSelector#vds). The reason for these limitations is because model has to be able to reproduce the exact
+sequence of events that was applied to system under test.
+
+Default implementations of partition and clustering descriptors, used in fuzz mode allow to optimise verification. For
+example, it is possible to go find logical timestamps that are visiting the same partition as any given logical
+timestamp. When running Harry in unit-test mode, we use a special generating visitor that keeps an entire given sequence
+of events in memory rather than producing it on the fly. For reasons of efficiency, we do not use generating visitors
+for generating and verifying large datasets.
+
+# Formal Relations Between Entities
+
+To be able to implement efficient models, we had to reduce the amount of state required for validation to a minimum and
+try to operate on primitive data values most of the time. Any Harry run starts with a `seed`. Given the same
+configuration, and the same seed, we're able to make runs deterministic (in other words, records in two clusters created
+from the same seed are going to have different real-time timestamps, but will be otherwise identical; logical time
+stamps will also be identical).
+
+Since it's clear how to generate things like random schemas, cluster configurations, etc., let's discuss how we're
+generating data, and why this type of generation makes validation efficient.
+
+First, we're using PCG family of random number generators, which, besides having nice characteristics that any RNG
+should have, have two important features:
+
+* Streams: for single seed, we can have several independent _different_ streams of random numbers.
+* Walkability: PCG generators generate a stream of numbers you can walk _back_ and _forth_. That is, for any number _n_
+ that represents a _position_ of the random number in the stream of random numbers, we can get the random number at
+ this position. Conversely, given a random number, we can determine what is its position in the stream. Moreover,
+ knowing a random number, we can determine which number precedes it in the stream of random numbers, and, finally, we
+ can determine how many numbers there are in a stream between the two random numbers.
+
+Out of these operations, determining the _next_ random number in the sequence can be done in constant time, `O(1)`.
+Advancing generator by _n_ steps can be done in `O(log(n))` steps. Since generation is cyclical, advancing the iterator
+backward is equivalent to advancing it by `cardinality - 1` steps. If we're generating 64 bits of entropy, advancing
+by `-1` can be done in 64 steps.
+
+Let's introduce some definitions:
+
+* `lts` is a *logical timestamp*, an entity (number in our case), given by the clock, on which some action occurs
+* `m` is a *modification id*, a sequential number of the modification that occurs on `lts`
+* `rts` is an approximate real-time as of clock for this run
+* `pid` is a partition position, a number between `0` and N, for `N` unique generated partitions
+* `pd` is a partition descriptor, a unique descriptor identifying the partition
+* `cd` is a clustering descriptor, a unique descriptor identifying row within some partition
+
+A small introduction that can help to understand the relation between these
+entities. Hierarchically, the generation process looks as follows:
+
+* `lts` is an entry point, from which the decision process starts
+* `pd` is picked from `lts`, and determines which partition is going to be visited
+* for `(pd, lts)` combination, `#mods` (the number of modification batches) and `#rows` (the number of rows per
+ modification batch) is determined. `m` is an index of the modification batch, and `i` is an index of the operation in
+ the modification batch.
+* `cd` is picked based on `(pd, lts)`, and `n`, a sequential number of the operation among all modification batches
+* operation type (whether we're going to perform a write, delete, range delete, etc), columns involved in this
+ operation, and values for the modification are picked depending on the `pd`, `lts`, `m`, and `i`
+
+Most of this formalization is implemented in `OpSelectors`, and is relied upon in`PartitionVisitor` and any
+implementation of a `Model`.
+
+Random number generation (see `OpSelectors#Rng`):
+
+* `rng(i, stream[, e])`: returns i'th number drawn from random sequence `stream` producing values with `e` bits of
+ entropy (64 bits for now).
+* `rng'(s, stream[, e])`: returns `i` of the random number `s` drawn from the random sequence `stream`. This function is
+ an inverse of `rng`.
+* `next(rnd, stream[, e])` and `prev(rnd, stream[, e])`: the next/previous number relative to `rnd` drawn from random
+ sequence `stream`.
+
+A simple example of a partition descriptor selector is one that is based on a sliding window of a size `s`, that slides
+every `n` iterations. First, we determine _where_ the window should start for a given `lts` (in other words, how many
+times it has already slid). After that, we determine which `pd` we pick out of `s` available ones. After picking each
+one of the `s` descriptors `n` times, we retire the oldest descriptor and pick a new one to the window. Window start and
+offset are then used as input for the `rng(start + offset, stream)` to make sure descriptors are uniformly distributed.
+
+We can build a clustering descriptor selector in a similar manner. Each partition will use its `pd` as a stream id, and
+pick `cd` from a universe of possible `cds` of size `#cds`. On each `lts`, we pick a random `offset`, and start
+picking `#ops` clusterings from this `offset < #cds`, and wrap around to index 0 after that. This way, each operation
+maps to a unique `cd`, and `#op` can be determined from `cd` deterministically.
+
+# Data Generation
+
+So far, we have established how to generate partition, clustering, and value _descriptors_. Now, we need to understand
+how we can generate data modification statements out of these descriptors in a way that helps us to validate data later.
+
+Since every run has a predefined schema, and by the time we visit a partition we have a logical timestamp, we can make
+the rest of the decisions: pick a number of batches we're about to perform, determine what kind of operations each one
+of the batches is going to contain, which rows we're going to visit (clustering for each modification operation).
+
+To generate a write, we need to know _which partition_ we're going to visit (in other words, partition descriptor),
+_which row_ we'd like to modify (in other words, clustering descriptor), _which columns_ we're modifying (in other
+words, a column mask), and, for each modified column - its value. By the time we're ready to make an actual query to the
+database, we already know `pd`, `cd`, `rts`, and `vds[]`, which is all we need to "inflate" a write.
+
+To inflate each value descriptor, we take a generator for its datatype, and turn its descriptor into the object. This
+generation process has the following important properties:
+
+* it is invertible: for every `inflate(vd) -> value`, there's `deflate(value) -> vd`
+* it is order-preserving: `compare(vd1, vd2) == compare(inflate(vd1), inflate(vd2))`
+
+Inflating `pd` and `cd` is slightly more involved than inflating `vds`, since partition and clustering keys are often
+composite. This means that `inflate(pd)` returns an array of objects, rather just a single
+object: `inflate(pd) -> value[]`, and `deflate(value[]) -> pd`. Just like inflating value descriptors, inflating keys
+preserves order.
+
+It is easy to see that, given two modifications: `Update(pd1, cd1, [vd1_1, vd2_1, vd3_1], lts1)`
+and `Update(pd1, cd1, [vd1_2, vd3_2], lts2)`, we will end up with a resultset that contains effects of both
+operations: `ResultSetRow(pd1, cd1, [vd1_2@rts2, vd2_1@rts1, vd3_2@rts2])`.
+
+# Model
+
+`Model` in Harry ties the rest of the components together and allows us to check whether or not data returned by the
+cluster actually makes sense. The model relies on the clock, since we have to convert real-time timestamps of the
+returned values back to logical timestamps, and on descriptor selectors to pick the right partition and rows.
+
+## Visible Rows Checker
+
+Let's try to put it all together and build a simple model. The simplest one is a visible row checker. It can check if
+any row in the response returned from the database could have been produced by one of the operations. However, it won't
+be able to find errors related to missing rows, and will only notice some cases of erroneously overwritten rows.
+
+In the model, we can see a response from the database in its deflated state. In other words, instead of the actual
+values returned, we see their descriptors. Every resultset row consists of `pd`, `cd`, `vds[]` (value descriptors),
+and `lts[]` (logical timestamps at which these values were written).
+
+To validate, we need to iterate through all operations for this partition, starting with the latest one the model is
+aware of. This model has no internal state, and validates entire partitions:
+
+```
+void validatePartitionState(long validationLts, List<ResultSetRow> rows) {
+ long pd = pdSelector.pd(validationLts, schema);
+
+ for (ResultSetRow row : rows) {
+ // iterator that gives us unique lts from the row in descending order
+ LongIterator rowLtsIter = descendingIterator(row.lts);
+ // iterator that gives us unique lts from the model in descending order
+ LongIterator modelLtsIter = descendingIterator(pdSelector, validationLts);
+
+ outer:
+ while (rowLtsIter.hasNext()) {
+ long rowLts = rowLtsIter.nextLong();
+
+ // this model can not check columns whose values were never written or were deleted
+ if (rowLts == NO_TIMESTAMP)
+ continue outer;
+
+ if (!modelLtsIter.hasNext())
+ throw new ValidationException(String.format("Model iterator is exhausted, could not verify %d lts for the row: \n%s %s",
+ rowLts, row));
+
+ while (modelLtsIter.hasNext()) {
+ long modelLts = modelLtsIter.nextLong();
+ // column was written by the operation that has a lower lts than the current one from the model
+ if (modelLts > rowLts)
+ continue;
+ // column was written by the operation that has a higher lts, which contradicts to the model, since otherwise we'd validate it by now
+ if (modelLts < rowLts)
+ throw new RuntimeException("Can't find a corresponding event id in the model for: " + rowLts + " " + modelLts);
+
+ // Compare values for columns that were supposed to be written with this lts
+ for (int col = 0; col < row.lts.length; col++) {
+ if (row.lts[col] != rowLts)
+ continue;
+
+ long m = descriptorSelector.modificationId(pd, row.cd, rowLts, row.vds[col], col);
+ long vd = descriptorSelector.vd(pd, row.cd, rowLts, m, col);
+
+ // If the value model predicts doesn't match the one received from the database, throw an exception
+ if (vd != row.vds[col])
+ throw new RuntimeException("Returned value doesn't match the model");
+ }
+ continue outer;
+ }
+ }
+ }
+}
+```
+
+As you can see, all validation is done using deflated `ResultSetRows`, which contain enough data to say which logical
+timestamp each value was written with, and which value descriptor each value has. This model can also validate data
+concurrently to the ongoing data modification operations.
+
+## Quiescent Checker
+
+Let's consider one more checker. It'll be more powerful than the visible rows checker in one way since it can find any
+inconsistency in data (incorrect timestamp, missing or additional row, rows coming in the wrong order, etc), but it'll
+also have one limitation: it won't be able to run concurrently with data modification statements. This means that for
+this model to be used, we should have no _in-flight_ queries, and all queries have to be in a deterministic state by the
+time we're validating their results.
+
+For this checker, we assume that we have a component that is called `Reconciler`, which can inflate partition state _up
+to some_ `lts`. `Reconciler` works by simply applying each modification in the same order they were applied to the
+cluster, and using standard Cassandra data reconciliation rules (last write wins / DELETE wins over INSERT in case of a
+timestamp collision).
+
+With this component, and knowing that there can be no in-fight queries, we can validate data in the following way:
+
+```
+public void validatePartitionState(Iterator<ResultSetRow> actual, Query query) {
+ // find out up the highest completed logical timestamp
+ long maxCompleteLts = tracker.maxComplete();
+
+ // get the expected state from reconciler
+ Iterator<Reconciler.RowState> expected = reconciler.inflatePartitionState(query.pd, maxCompleteLts, query).iterator(query.reverse);
+
+ // compare actual and expected rows one-by-one in-order
+ while (actual.hasNext() && expected.hasNext()) {
+ ResultSetRow actualRowState = actual.next();
+ Reconciler.RowState expectedRowState = expected.next();
+
+ if (actualRowState.cd != expectedRowState.cd)
+ throw new ValidationException("Found a row in the model that is not present in the resultset:\nExpected: %s\nActual: %s",
+ expectedRowState, actualRowState);
+
+ if (!Arrays.equals(actualRowState.vds, expectedRowState.vds))
+ throw new ValidationException("Returned row state doesn't match the one predicted by the model:\nExpected: %s (%s)\nActual: %s (%s).",
+ Arrays.toString(expectedRowState.vds), expectedRowState,
+ Arrays.toString(actualRowState.vds), actualRowState);
+
+ if (!Arrays.equals(actualRowState.lts, expectedRowState.lts))
+ throw new ValidationException("Timestamps in the row state don't match ones predicted by the model:\nExpected: %s (%s)\nActual: %s (%s).",
+ Arrays.toString(expectedRowState.lts), expectedRowState,
+ Arrays.toString(actualRowState.lts), actualRowState);
+ }
+
+ if (actual.hasNext() || expected.hasNext()) {
+ throw new ValidationException("Expected results to have the same number of results, but %s result iterator has more results",
+ actual.hasNext() ? "actual" : "expected");
+ }
+}
+```
+
+If there's any mismatch, it'll be caught right away: if there's an extra row
+(for example, there were issues in Cassandra that caused it to have duplicate
+rows), or if some row or even value in the row is missing.
+
+## Exhaustive Checker
+
+To be able to both run validation concurrently to modifications and be able to
+catch all kinds of inconsistencies, we need a more involved checker.
+
+In this checker, we rely on inflating partition state. However, we're most
+interested in `lts`, `opId`, and visibility (whether or not it is still
+in-flight) of each modification operation. To be able to give a reliable result,
+we need to make sure we follow these rules:
+
+* every operation model _thinks_ should be visible, has to be visible
+* every operation model _thinks_ should be invisible, has to be invisible
+* every operation model doesn't know the state of (i.e., it is still
+ in-flight) can be _either_ visible _invisible_
+* there can be no state in the database that model is not aware of (in other words,
+ we either can _explain_ how a row came to be, or we conclude that the row is
+ erroneous)
+
+A naive way to do this would be to inflate every possible partition state, where
+every in-flight operation would be either visible or invisible, but this gets
+costly very quickly since the number of possible combinations grows
+exponentially. A better (and simpler) way to do this is to iterate all
+operations and keep the state of "explained" operations:
+
+```
+public class RowValidationState {
+ // every column starts in UNOBSERVED, and has to move to either REMOVED, or OBSERVED state
+ private final ColumnState[] columnStates;
+ // keep track of operations related to each column state
+ private final Operation[] causingOperations;
+}
+```
+
+Now, we move through all operations for a given row, starting from the _newest_ ones, towards
+the oldest ones:
+
+```
+public void validatePartitionState(long verificationLts, PeekingIterator<ResultSetRow> actual_, Query query) {
+ // get a list of operations for each cd
+ NavigableMap<Long, List<Operation>> operations = inflatePartitionState(query);
+
+ for (Map.Entry<Long, List<Operation>> entry : operations.entrySet()) {
+ long cd = entry.getKey();
+ List<Operation> ops = entry.getValue();
+
+ // Found a row that is present both in the model and in the resultset
+ if (actual.hasNext() && actual.peek().cd == cd) {
+ validateRow(new RowValidationState(actual.next), operations);
+ } else {
+ validateNoRow(cd, operations);
+
+ // Row is not present in the resultset, and we currently look at modifications with a clustering past it
+ if (actual.hasNext() && cmp.compare(actual.peek().cd, cd) < 0)
+ throw new ValidationException("Couldn't find a corresponding explanation for the row in the model");
+ }
+ }
+
+ // if there are more rows in the resultset, and we don't have model explanation for them, we've found an issue
+ if (actual.hasNext())
+ throw new ValidationException("Observed unvalidated rows");
+}
+```
+
+Now, we have to implement `validateRow` and `validateNoRow`. `validateNoRow` is easy: we only need to make sure that a
+set of operations results in an invisible row. Since we're iterating operations in reverse order, if we encounter a
+delete not followed by any writes, we can conclude that the row is invisible and exit early. If there's a write that is
+not followed by a delete, and the row isn't covered by a range tombstone, we know it's an error.
+
+`validateRow` only has to iterate operations in reverse order until it can explain the value in every column. For
+example, if a value is `UNOBSERVED`, and the first thing we encounter is a `DELETE` that removes this column, we only
+need to make sure that the value is actually `null`, in which case we can conclude that the value can be explained
+as `REMOVED`.
+
+Similarly, if we encounter an operation that has written the expected value, we conclude that the value is `OBSERVED`.
+If there are any seeming inconsistencies between the model and resultset, we have to check whether or not the operation
+in question is still in flight. If it is, its results may still not be visible, so we can't reliably say it's an error.
+
+To summarize, in order for us to implement an exhaustive checker, we have to iterate operations for each of the rows
+present in the model in reverse order until we either detect inconsistency that can't be explained by an in-flight
+operation or until we explain every value in the row.
+
+## Conclusion
+
+As you can see, all checkers up till now are almost entirely stateless. Exhaustive and quiescent models rely
+on `DataTracker` component that is aware of the in-flight and completed `lts`, but don't need any other state apart from
+that, since we can always inflate a complete partition from scratch every time we validate.
+
+While not relying on the state is a useful feature, at least _some_ state is useful to have. For example, if we're
+validating just a few rows in the partition, right now we have to iterate through each and every `lts` that has visited
+this partition and filter out only modifications that have visited it. However, since the model is notified of each
+_started_, and, later, finished modification via `recordEvent`, we can keep track of `pd -> (cd -> lts)` map. You can
+check out `VisibleRowsChecker` as an example of that.
diff --git a/test/distributed/org/apache/cassandra/distributed/fuzz/HarryHelper.java b/test/harry/main/org/apache/cassandra/harry/HarryHelper.java
similarity index 84%
rename from test/distributed/org/apache/cassandra/distributed/fuzz/HarryHelper.java
rename to test/harry/main/org/apache/cassandra/harry/HarryHelper.java
index e2d8a0bbb6..09cc574082 100644
--- a/test/distributed/org/apache/cassandra/distributed/fuzz/HarryHelper.java
+++ b/test/harry/main/org/apache/cassandra/harry/HarryHelper.java
@@ -16,32 +16,37 @@
* limitations under the License.
*/
-package org.apache.cassandra.distributed.fuzz;
+package org.apache.cassandra.harry;
import java.io.File;
-import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
-import harry.core.Configuration;
-import harry.ddl.ColumnSpec;
-import harry.ddl.SchemaGenerators;
-import harry.ddl.SchemaSpec;
-import harry.generators.Surjections;
-import harry.model.OpSelectors;
-import harry.model.clock.OffsetClock;
-import harry.model.sut.PrintlnSut;
+import org.apache.cassandra.harry.clock.OffsetClock;
+import org.apache.cassandra.harry.core.Configuration;
+import org.apache.cassandra.harry.ddl.ColumnSpec;
+import org.apache.cassandra.harry.ddl.SchemaGenerators;
+import org.apache.cassandra.harry.ddl.SchemaSpec;
+import org.apache.cassandra.harry.dsl.ReplayingHistoryBuilder;
+import org.apache.cassandra.harry.gen.Surjections;
+import org.apache.cassandra.harry.model.OpSelectors;
+import org.apache.cassandra.harry.runner.HarryRunner;
+import org.apache.cassandra.harry.sut.SystemUnderTest;
+import org.apache.cassandra.harry.sut.TokenPlacementModel;
+import org.apache.cassandra.harry.tracker.DefaultDataTracker;
import static org.apache.cassandra.config.CassandraRelevantProperties.CASSANDRA_ALLOW_SIMPLE_STRATEGY;
import static org.apache.cassandra.config.CassandraRelevantProperties.CASSANDRA_MINIMUM_REPLICATION_FACTOR;
import static org.apache.cassandra.config.CassandraRelevantProperties.DISABLE_TCACTIVE_OPENSSL;
+import static org.apache.cassandra.config.CassandraRelevantProperties.IO_NETTY_TRANSPORT_NONATIVE;
import static org.apache.cassandra.config.CassandraRelevantProperties.LOG4J2_DISABLE_JMX;
import static org.apache.cassandra.config.CassandraRelevantProperties.LOG4J2_DISABLE_JMX_LEGACY;
import static org.apache.cassandra.config.CassandraRelevantProperties.LOG4J_SHUTDOWN_HOOK_ENABLED;
import static org.apache.cassandra.config.CassandraRelevantProperties.ORG_APACHE_CASSANDRA_DISABLE_MBEAN_REGISTRATION;
-import static org.apache.cassandra.config.CassandraRelevantProperties.IO_NETTY_TRANSPORT_NONATIVE;
public class HarryHelper
{
+ public static final String KEYSPACE = "harry";
+
public static void init()
{
// setting both ways as changes between versions
@@ -53,17 +58,11 @@ public class HarryHelper
DISABLE_TCACTIVE_OPENSSL.setBoolean(true);
IO_NETTY_TRANSPORT_NONATIVE.setBoolean(true);
ORG_APACHE_CASSANDRA_DISABLE_MBEAN_REGISTRATION.setBoolean(true);
-
- InJvmSut.init();
- QueryingNoOpChecker.init();
- Configuration.registerSubtypes(PrintlnSut.PrintlnSutConfiguration.class);
- Configuration.registerSubtypes(Configuration.NoOpMetricReporterConfiguration.class);
- Configuration.registerSubtypes(Configuration.RecentPartitionsValidatorConfiguration.class);
}
public static Configuration configuration(String... args) throws Exception
{
- File configFile = harry.runner.HarryRunner.loadConfig(args);
+ File configFile = HarryRunner.loadConfig(args);
Configuration configuration = Configuration.fromFile(configFile);
System.out.println("Using configuration generated from: " + configFile);
return configuration;
@@ -121,10 +120,8 @@ public class HarryHelper
.setTruncateTable(false)
.setDropSchema(false)
.setSchemaProvider((seed, sut) -> schemaSpecGen("harry", "tbl_").inflate(seed))
- .setClock(new Configuration.ApproximateMonotonicClockConfiguration(7300, 1, TimeUnit.SECONDS))
.setClusteringDescriptorSelector(defaultClusteringDescriptorSelectorConfiguration().build())
- .setPartitionDescriptorSelector(new Configuration.DefaultPDSelectorConfiguration(100, 10))
- .setSUT(new PrintlnSut.PrintlnSutConfiguration())
+ .setPartitionDescriptorSelector(new Configuration.DefaultPDSelectorConfiguration(1, 1))
.setDataTracker(new Configuration.DefaultDataTrackerConfiguration())
.setRunner((run, configuration) -> {
throw new IllegalArgumentException("Runner is not configured by default.");
@@ -132,11 +129,17 @@ public class HarryHelper
.setMetricReporter(new Configuration.NoOpMetricReporterConfiguration());
}
+ public static ReplayingHistoryBuilder dataGen(SystemUnderTest sut, TokenPlacementModel.ReplicationFactor rf, SystemUnderTest.ConsistencyLevel writeCl)
+ {
+ long seed = 1L;
+ SchemaSpec schema = schemaSpecGen("harry", "tbl_").inflate(seed);
+ return new ReplayingHistoryBuilder(seed, 100, 1, new DefaultDataTracker(), sut, schema, rf, writeCl);
+ }
+
public static Configuration.CDSelectorConfigurationBuilder defaultClusteringDescriptorSelectorConfiguration()
{
return new Configuration.CDSelectorConfigurationBuilder()
- .setNumberOfModificationsDistribution(new Configuration.ConstantDistributionConfig(1))
- .setRowsPerModificationDistribution(new Configuration.ConstantDistributionConfig(1))
+ .setOperationsPerLtsDistribution(new Configuration.ConstantDistributionConfig(1))
.setMaxPartitionSize(100)
.setOperationKindWeights(new Configuration.OperationKindSelectorBuilder()
.addWeight(OpSelectors.OperationKind.DELETE_ROW, 1)
@@ -155,8 +158,7 @@ public class HarryHelper
public static Configuration.CDSelectorConfigurationBuilder singleRowPerModification()
{
return new Configuration.CDSelectorConfigurationBuilder()
- .setNumberOfModificationsDistribution(new Configuration.ConstantDistributionConfig(1))
- .setRowsPerModificationDistribution(new Configuration.ConstantDistributionConfig(1))
+ .setOperationsPerLtsDistribution(new Configuration.ConstantDistributionConfig(1))
.setMaxPartitionSize(100)
.setOperationKindWeights(new Configuration.OperationKindSelectorBuilder()
.addWeight(OpSelectors.OperationKind.INSERT_WITH_STATICS, 100)
diff --git a/test/harry/main/org/apache/cassandra/harry/checker/ModelChecker.java b/test/harry/main/org/apache/cassandra/harry/checker/ModelChecker.java
new file mode 100644
index 0000000000..829075d871
--- /dev/null
+++ b/test/harry/main/org/apache/cassandra/harry/checker/ModelChecker.java
@@ -0,0 +1,230 @@
+/*
+ * 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.harry.checker;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import java.util.function.Consumer;
+
+import org.apache.cassandra.harry.gen.EntropySource;
+import org.apache.cassandra.harry.gen.rng.JdkRandomEntropySource;
+
+public class ModelChecker<STATE>
+{
+ private final List<StepExecutor<STATE>> steps;
+ private final List<Precondition<STATE>> invariants;
+ private Precondition<STATE> exitCondition;
+ private Consumer<STATE> beforeAll;
+ private Consumer<STATE> afterAll;
+ private STATE init;
+
+ public ModelChecker()
+ {
+ steps = new ArrayList<>();
+ invariants = new ArrayList<>();
+ }
+
+ public void run() throws Throwable
+ {
+ run(Integer.MAX_VALUE, System.currentTimeMillis());
+ }
+
+ public void run(int maxSteps, long seed) throws Throwable
+ {
+ assert init != null : "Initial condition is not specified";
+
+ Ref<STATE> state = new Ref<>(init);
+ EntropySource entropySource = new JdkRandomEntropySource(new Random(seed));
+ if (beforeAll != null)
+ beforeAll.accept(state.get());
+
+ for (int i = 0; i < maxSteps; i++)
+ {
+ if (exitCondition != null && exitCondition.test(state.get()))
+ return;
+
+ // TODO: add randomisation / probability for triggering a specific step
+ steps.get(entropySource.nextInt(steps.size())).execute(state, entropySource.derive());
+ for (Precondition<STATE> invariant : invariants)
+ invariant.test(state.get());
+ }
+
+ if (afterAll != null)
+ afterAll.accept(state.get());
+ }
+ public ModelChecker<STATE> init(STATE state)
+ {
+ this.init = state;
+ return this;
+ }
+
+ public ModelChecker<STATE> beforeAll(Consumer<STATE> precondition)
+ {
+ this.beforeAll = precondition;
+ return this;
+ }
+
+ public ModelChecker<STATE> afterAll(Consumer<STATE> postcondition)
+ {
+ this.afterAll = postcondition;
+ return this;
+ }
+
+ public ModelChecker<STATE> exitCondition(Precondition<STATE> precondition)
+ {
+ this.exitCondition = precondition;
+ return this;
+ }
+
+ public ModelChecker<STATE> step(Precondition<STATE> precondition, Step<STATE> step)
+ {
+ steps.add((ref, entropySource) -> {
+ ref.map(state -> {
+ if (!precondition.test(state))
+ return state;
+
+ return step.next(state, entropySource);
+ });
+ });
+
+ return this;
+ }
+
+ public ModelChecker<STATE> invariant(Precondition<STATE> invariant)
+ {
+ invariants.add(invariant);
+ return this;
+ }
+
+ public ModelChecker<STATE> step(Step<STATE> step)
+ {
+ return step(Precondition.alwaysTrue(), step);
+ }
+
+ public ModelChecker<STATE> step(StatePrecondition<STATE> precondition, ThrowingFunction<STATE, STATE> step)
+ {
+ steps.add((ref, entropySource) -> {
+ ref.map(state -> {
+ if (!precondition.test(state))
+ return state;
+
+ return step.apply(state);
+ });
+ });
+
+ return this;
+ }
+
+ public ModelChecker<STATE> step(ThrowingConsumer<STATE> step)
+ {
+ return step((t, entropySource) -> {
+ step.consume(t);
+ return t;
+ });
+ }
+ public ModelChecker<STATE> step(ThrowingFunction<STATE, STATE> step)
+ {
+ return step((t, entropySource) -> {
+ return step.apply(t);
+ });
+ }
+
+ interface StepExecutor<STATE>
+ {
+ void execute(Ref<STATE> state, EntropySource entropySource) throws Throwable;
+ }
+
+ public interface StatePrecondition<STATE>
+ {
+ boolean test(STATE state) throws Throwable;
+ }
+
+ public interface Precondition<STATE>
+ {
+ boolean test(STATE state) throws Throwable;
+
+ static <STATE> Precondition<STATE> alwaysTrue()
+ {
+ return (a) -> true;
+ }
+ }
+
+ public interface Step<STATE>
+ {
+ STATE next(STATE t, EntropySource entropySource) throws Throwable;
+ }
+
+ public interface ThrowingConsumer<I>
+ {
+ void consume(I t) throws Throwable;
+ }
+
+ public interface ThrowingFunction<I, O>
+ {
+ O apply(I t) throws Throwable;
+ }
+
+ public interface ThrowingBiFunction<I1, I2, O>
+ {
+ O apply(I1 t1, I2 t2) throws Throwable;
+ }
+
+ public static class Ref<T>
+ {
+ public T ref;
+
+ public Ref(T init)
+ {
+ this.ref = init;
+ }
+
+ public T get()
+ {
+ return ref;
+ }
+
+ public void set(T v)
+ {
+ this.ref = v;
+ }
+
+ public void map(ThrowingFunction<T, T> fn) throws Throwable
+ {
+ this.ref = fn.apply(ref);
+ }
+ }
+
+ public static class State<MODEL, SUT>
+ {
+ public final MODEL model;
+ public final SUT sut;
+
+ public State(MODEL model, SUT sut)
+ {
+ this.model = model;
+ this.sut = sut;
+ }
+
+ public State<MODEL, SUT> next(MODEL state)
+ {
+ return new State<>(state, this.sut);
+ }
+ }
+}
diff --git a/test/harry/main/org/apache/cassandra/harry/clock/ApproximateClock.java b/test/harry/main/org/apache/cassandra/harry/clock/ApproximateClock.java
new file mode 100644
index 0000000000..5bb4aa00ef
--- /dev/null
+++ b/test/harry/main/org/apache/cassandra/harry/clock/ApproximateClock.java
@@ -0,0 +1,279 @@
+/*
+ * 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.harry.clock;
+
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.locks.LockSupport;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import org.apache.cassandra.harry.core.Configuration;
+import org.apache.cassandra.harry.model.OpSelectors;
+
+/**
+ * Monotonic clock, that guarantees that any LTS can be converted to a unique RTS, given
+ * the number of LTS does not exceed the number of RTS (e.g., we do not draw LTS more frequently
+ * than once per microsecond).
+ * <p>
+ * This conversion works as follows:
+ * <p>
+ * * every `period`, we record the current timestamp and maximum seen LTS, and keep history of up to
+ * `historySize` LTS/timestamp combinations
+ * * when queried to retrieve the LTS, we find a timestamp, relative to which we can calculate RTS.
+ * After that, we calculate a difference between the largest LTS that is still smaller than the converted
+ * one, and add this difference to the timestamp.
+ * <p>
+ * This way, later LTS can only be mapped to later RTS, and any LTS that was drawn previously, will be
+ * uniquely mapped relative to some timestamp, with the order matching the LTS order.
+ */
+public class ApproximateClock implements OpSelectors.Clock
+{
+ public static final long START_VALUE = 0;
+ public static final long DEFUNCT = Long.MIN_VALUE;
+ public static final long REBASE_IN_PROGRESS = Long.MIN_VALUE + 1;
+
+ // TODO: there's a theoretical possibility of a bug; when we have several consecutive epochs without
+ // change in LTS, current implementation will return the latest epoch instead of the earliest one.
+ // This is not a big deal in terms of monotonicity but can cause some problems when validating TTL.
+ // The simples fix would be to find the smallest matching epoch.
+ private final ScheduledExecutorService executor;
+ private final int historySize;
+ private final CopyOnWriteArrayList<Long> ltsHistory;
+ private final long startTimeMicros;
+ private volatile int idx;
+ private final AtomicLong lts;
+
+ private final long periodMicros;
+
+ private final long epoch;
+ private final TimeUnit epochTimeUnit;
+
+ public ApproximateClock(long period, TimeUnit timeUnit)
+ {
+ this(10000, period, timeUnit);
+ }
+
+ public ApproximateClock(int historySize, long epoch, TimeUnit epochTimeUnit)
+ {
+ this(TimeUnit.MILLISECONDS.toMicros(System.currentTimeMillis()),
+ historySize, new CopyOnWriteArrayList<>(), START_VALUE, 0, epoch, epochTimeUnit);
+ rebase();
+ }
+
+ ApproximateClock(long startTimeMicros,
+ int historySize,
+ CopyOnWriteArrayList<Long> history,
+ long lts,
+ int idx,
+ long epoch,
+ TimeUnit epochTimeUnit)
+ {
+ this.startTimeMicros = startTimeMicros;
+ this.historySize = historySize;
+ this.ltsHistory = history;
+ this.lts = new AtomicLong(lts);
+ this.idx = idx;
+ this.periodMicros = epochTimeUnit.toMicros(epoch);
+ this.executor = Executors.newSingleThreadScheduledExecutor(r -> {
+ Thread t = new Thread(r);
+ t.setName("ApproximateMonotonicClock-ScheduledTasks");
+ t.setDaemon(true);
+ return t;
+ });
+ this.executor.scheduleAtFixedRate(this::rebase, epoch, epoch, epochTimeUnit);
+ this.epoch = epoch;
+ this.epochTimeUnit = epochTimeUnit;
+ }
+
+ @VisibleForTesting
+ public static ApproximateClock forDebug(long startTimeMicros, int historySize, long lts, int idx, long period, TimeUnit timeUnit, long... values)
+ {
+ CopyOnWriteArrayList<Long> history = new CopyOnWriteArrayList<>();
+ for (int i = 0; i < values.length; i++)
+ history.set(i, values[i]);
+
+ assert values.length == idx; // sanity check
+ return new ApproximateClock(startTimeMicros, historySize, history, lts, idx, period, timeUnit);
+ }
+
+ public long get(long idx)
+ {
+ return ltsHistory.get((int) (idx % historySize));
+ }
+
+ private void rebase()
+ {
+ int arrayIdx = idx % historySize;
+ long rebaseLts = lts.get();
+ if (rebaseLts == DEFUNCT)
+ throw new IllegalStateException();
+
+ while (!lts.compareAndSet(rebaseLts, REBASE_IN_PROGRESS))
+ rebaseLts = lts.get();
+
+ ltsHistory.add(arrayIdx, rebaseLts == START_VALUE ? START_VALUE : (rebaseLts + 1));
+
+ // If we happen to exhaust counter, we just need to make operations "wider".
+ // It is unsafe to proceed, so we defunct the clock.
+ //
+ // We could make a clock implementation that would sleep on `get`, but it will
+ // be more expensive, since we'll have to check for overflow each time before
+ // returning anything.
+ if (idx > 1 && get(idx) - get(idx - 1) > periodMicros)
+ {
+ lts.set(DEFUNCT);
+ executor.shutdown();
+ throwCounterExhaustedException();
+ }
+
+ idx = idx + 1;
+ if (!lts.compareAndSet(REBASE_IN_PROGRESS, rebaseLts))
+ throw new IllegalStateException("No thread should have changed LTS during rebase. " + lts.get());
+ }
+
+ @Override
+ public long nextLts()
+ {
+ long current = lts.get();
+ while (true)
+ {
+ if (current >= 0)
+ {
+ if (lts.compareAndSet(current, current + 1))
+ return current;
+
+ current = lts.get();
+ continue;
+ }
+
+ if (current == REBASE_IN_PROGRESS)
+ {
+ LockSupport.parkNanos(1);
+ current = lts.get();
+ continue;
+ }
+
+ if (current == DEFUNCT)
+ throwCounterExhaustedException();
+
+ throw new IllegalStateException("This should have been unreachable: " + current);
+ }
+ }
+
+ public long peek()
+ {
+ while (true)
+ {
+ long ret = lts.get();
+
+ if (ret == REBASE_IN_PROGRESS)
+ {
+ LockSupport.parkNanos(1);
+ continue;
+ }
+
+ if (ret == DEFUNCT)
+ throwCounterExhaustedException();
+
+ return ret;
+ }
+ }
+
+ public Configuration.ClockConfiguration toConfig()
+ {
+ int idx = this.idx;
+ long[] history = new long[Math.min(idx, historySize)];
+ for (int i = 0; i < history.length; i++)
+ history[i] = ltsHistory.get(i);
+ return new Configuration.DebugApproximateClockConfiguration(startTimeMicros,
+ ltsHistory.size(),
+ history,
+ lts.get(),
+ idx,
+ epoch,
+ epochTimeUnit);
+ }
+
+ public long lts(final long rts)
+ {
+ final int historyIdx = idx - 1;
+ for (int i = 0; i < historySize - 1 && historyIdx - i >= 0; i++)
+ {
+ long periodStartRts = startTimeMicros + periodMicros * (historyIdx - i);
+ if (rts >= periodStartRts)
+ {
+ long periodStartLts = get(historyIdx - i);
+ return periodStartLts + rts - periodStartRts;
+ }
+ }
+ throw new IllegalStateException("RTS is too old to convert to LTS: " + rts + "\n " + ltsHistory);
+ }
+
+ public long rts(final long lts)
+ {
+ assert lts <= peek() : String.format("Queried for LTS we haven't yet issued %d. Max is %d.", lts, peek());
+
+ final int historyIdx = idx - 1;
+ for (int i = 0; i < historySize - 1 && historyIdx - i >= 0; i++)
+ {
+ long periodStartLts = get(historyIdx - i);
+ if (lts >= periodStartLts)
+ {
+ long periodStartRts = startTimeMicros + periodMicros * (historyIdx - i);
+ return periodStartRts + lts - periodStartLts;
+ }
+ }
+
+ throw new IllegalStateException("LTS is too old to convert to RTS: " + lts + "\n " + dumpHistory());
+ }
+
+ private String dumpHistory()
+ {
+ String s = "";
+ int idx = this.idx;
+ for (int i = 0; i < Math.min(idx, historySize); i++)
+ {
+ s += ltsHistory.get(i) + ",";
+ }
... 20485 lines suppressed ...
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cassandra.apache.org
For additional commands, e-mail: commits-help@cassandra.apache.org