Skip to content
Permalink
Browse files
Numerious minor improvements while preparing for fuzz-testing 4.0 in …
…earnest:

  * Refactor Run to make it an entrypoint
  * Separate Partition visitors from Row visitors
  * Make it possible to effortlessly check local states
  * Introduce CLs
  * More clear distinction between the components allowing to implement visitors (such as repairing validator)
  * Implement fault injecting partition visitor
  * Extract DataTracker
  * Minor bug fixes

Patch by Alex Petrov for CASSANDRA-16262.
  • Loading branch information
ifesdjeen committed Jul 12, 2021
1 parent 42b4fd0 commit 1c7fbc6fb8da25877a845962d20717ab981e7b37
Showing 59 changed files with 2,943 additions and 1,355 deletions.

Large diffs are not rendered by default.

@@ -16,29 +16,34 @@
* limitations under the License.
*/

package harry.model.sut;
package harry.core;

import java.util.concurrent.CompletableFuture;

public class NoOpSut implements SystemUnderTest
public interface MetricReporter
{
public boolean isShutdown()
{
return false;
}
void columnDelete();
void rowDelete();
void insert();
void rangeDelete();

public void shutdown()
{
}
void validatePartition();
void validateRandomQuery();

public Object[][] execute(String statement, Object... bindings)
interface MetricReporterFactory
{
return new Object[0][];
MetricReporter make();
}

public CompletableFuture<Object[][]> executeAsync(String statement, Object... bindings)
MetricReporter NO_OP = new NoOpMetricReporter();

class NoOpMetricReporter implements MetricReporter
{
return CompletableFuture.supplyAsync(() -> execute(statement, bindings),
Runnable::run);
private NoOpMetricReporter() {}

public void columnDelete(){}
public void rowDelete(){}
public void insert(){}
public void rangeDelete(){}
public void validatePartition(){}
public void validateRandomQuery(){}
}
}
@@ -18,56 +18,62 @@

package harry.core;

import java.util.function.Supplier;

import harry.ddl.SchemaSpec;
import harry.model.Model;
import harry.model.OpSelectors;
import harry.model.sut.SystemUnderTest;
import harry.runner.PartitionVisitor;
import harry.runner.RowVisitor;
import harry.runner.Validator;
import harry.runner.DataTracker;
import harry.runner.QueryGenerator;

public class Run
{
public final OpSelectors.Rng rng;
public final OpSelectors.MonotonicClock clock;
public final OpSelectors.PdSelector pdSelector;

public final OpSelectors.DescriptorSelector descriptorSelector;
public final QueryGenerator rangeSelector;

public final SchemaSpec schemaSpec;
public final Model model;
public final DataTracker tracker;
public final SystemUnderTest sut;
public final Validator validator;
public final RowVisitor rowVisitor;
public final Supplier<PartitionVisitor> visitorFactory;

public final Configuration snapshot;
public final MetricReporter metricReporter;


public Run(OpSelectors.Rng rng,
OpSelectors.MonotonicClock clock,
OpSelectors.PdSelector pdSelector,
OpSelectors.DescriptorSelector descriptorSelector,

Run(OpSelectors.Rng rng,
OpSelectors.MonotonicClock clock,
OpSelectors.PdSelector pdSelector,
OpSelectors.DescriptorSelector descriptorSelector,
SchemaSpec schemaSpec,
DataTracker tracker,
SystemUnderTest sut,
MetricReporter metricReporter)
{
this(rng, clock, pdSelector, descriptorSelector,
new QueryGenerator(schemaSpec, pdSelector, descriptorSelector, rng),
schemaSpec, tracker, sut, metricReporter);
}

SchemaSpec schemaSpec,
Model model,
SystemUnderTest sut,
Validator validator,
RowVisitor rowVisitor,
Supplier<PartitionVisitor> visitorFactory,
Configuration snapshot)
private Run(OpSelectors.Rng rng,
OpSelectors.MonotonicClock clock,
OpSelectors.PdSelector pdSelector,
OpSelectors.DescriptorSelector descriptorSelector,
QueryGenerator rangeSelector,

SchemaSpec schemaSpec,
DataTracker tracker,
SystemUnderTest sut,
MetricReporter metricReporter)
{

this.rng = rng;
this.clock = clock;
this.pdSelector = pdSelector;
this.descriptorSelector = descriptorSelector;
this.rangeSelector = rangeSelector;
this.schemaSpec = schemaSpec;
this.model = model;
this.tracker = tracker;
this.sut = sut;
this.validator = validator;
this.rowVisitor = rowVisitor;
this.visitorFactory = visitorFactory;
this.snapshot = snapshot;
this.metricReporter = metricReporter;
}
}
}
@@ -53,7 +53,7 @@ public boolean maybeCorrupt(Query query, SystemUnderTest sut)
{
Set<Long> cds = new HashSet<>();
long maxLts = 0;
for (Object[] obj : sut.execute(query.toSelectStatement()))
for (Object[] obj : sut.execute(query.toSelectStatement(), SystemUnderTest.ConsistencyLevel.ALL))
{
ResultSetRow row = SelectHelper.resultSetToRow(schema, clock, obj);
// TODO: extract CD cheaper
@@ -84,7 +84,7 @@ public boolean maybeCorrupt(Query query, SystemUnderTest sut)
// written value and tombstone are resolved in favour of tombstone, so we're
// just going to take the next lts.
logger.info("Corrupting the resultset by writing a row with cd {}", cd);
sut.execute(WriteHelper.inflateInsert(schema, query.pd, cd, vds, clock.rts(maxLts) + 1));
sut.execute(WriteHelper.inflateInsert(schema, query.pd, cd, vds, clock.rts(maxLts) + 1), SystemUnderTest.ConsistencyLevel.ALL);
return true;
}
}
@@ -52,7 +52,7 @@ public boolean maybeCorrupt(Query query, SystemUnderTest sut)
{
List<ResultSetRow> result = new ArrayList<>();
CompiledStatement statement = query.toSelectStatement();
for (Object[] obj : sut.execute(statement.cql(), statement.bindings()))
for (Object[] obj : sut.execute(statement.cql(), SystemUnderTest.ConsistencyLevel.ALL, statement.bindings()))
result.add(SelectHelper.resultSetToRow(schema, clock, obj));

// TODO: technically, we can do this just depends on corruption strategy
@@ -36,7 +36,7 @@ default boolean maybeCorrupt(ResultSetRow row, SystemUnderTest sut)
if (canCorrupt(row))
{
CompiledStatement statement = corrupt(row);
sut.execute(statement.cql(), statement.bindings());
sut.execute(statement.cql(), SystemUnderTest.ConsistencyLevel.ALL, statement.bindings());
return true;
}
return false;
@@ -18,15 +18,19 @@

package harry.ddl;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.function.Supplier;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;

import com.fasterxml.jackson.annotation.JsonProperty;
import harry.generators.Generator;
import harry.generators.Surjections;

@@ -40,28 +44,40 @@ public static Builder schema(String ks)
}

public static final Collection<ColumnSpec.DataType<?>> clusteringKeyTypes;
public static final Map<String, ColumnSpec.DataType<?>> nameToTypeMap;
public static final Collection<ColumnSpec.DataType<?>> columnTypes;

static
{

ImmutableList.Builder<ColumnSpec.DataType<?>> builder = ImmutableList.builder();
builder.add(ColumnSpec.int8Type,
ColumnSpec.int16Type,
ColumnSpec.int32Type,
ColumnSpec.int64Type,
// TODO re-enable boolean type; add it to ByteBufferUtil in Cassandra for that
// ColumnSpec.booleanType,
ColumnSpec.floatType,
ColumnSpec.doubleType,
ColumnSpec.asciiType);
columnTypes = builder.build();
builder = ImmutableList.builder();
builder.addAll(columnTypes);

ImmutableMap.Builder<String, ColumnSpec.DataType<?>> mapBuilder = ImmutableMap.builder();

for (ColumnSpec.DataType<?> columnType : columnTypes)
{
builder.add(ColumnSpec.ReversedType.getInstance(columnType));
ColumnSpec.DataType<?> reversedType = ColumnSpec.ReversedType.getInstance(columnType);
builder.add(reversedType);

mapBuilder.put(columnType.toString(), columnType);
mapBuilder.put(String.format("desc(%s)", columnType.toString()), columnType);
}

builder.add(ColumnSpec.floatType);
builder.add(ColumnSpec.doubleType);

clusteringKeyTypes = builder.build();
nameToTypeMap = mapBuilder.build();
}

@SuppressWarnings("unchecked")
@@ -281,15 +297,15 @@ public Surjections.Surjection<SchemaSpec> surjection()
public static Surjections.Surjection<SchemaSpec> defaultSchemaSpecGen(String ks, String table)
{
return new SchemaGenerators.Builder(ks, () -> table)
.partitionKeySpec(1, 4,
.partitionKeySpec(2, 4,
// ColumnSpec.int8Type,
// ColumnSpec.int16Type,
ColumnSpec.int32Type,
ColumnSpec.int64Type,
// ColumnSpec.floatType,
// ColumnSpec.doubleType,
ColumnSpec.asciiType(4, 10))
.clusteringKeySpec(1, 4,
.clusteringKeySpec(2, 4,
// ColumnSpec.int8Type,
// ColumnSpec.int16Type,
ColumnSpec.int32Type,
@@ -366,6 +382,7 @@ public static Surjections.Surjection<SchemaSpec> defaultSchemaSpecGen(String ks,
longAndStringSpecWithReversedBothBuilder,
withAllFeaturesEnabled
};

// Create schema generators that would produce tables starting with just a few features, progressing to use more
public static Supplier<SchemaSpec> progression(int switchAfter)
{
@@ -375,10 +392,11 @@ public static Supplier<SchemaSpec> progression(int switchAfter)

return new Supplier<SchemaSpec>()
{
private final AtomicInteger counter = new AtomicInteger();
private int counter = 0;
public SchemaSpec get()
{
int idx = (counter.getAndIncrement() / switchAfter) % generators.length;
int idx = (counter / switchAfter) % generators.length;
counter++;
SchemaSpec spec = generators[idx].get();
int tries = 100;
while ((spec.ckGenerator.byteSize() != Long.BYTES || spec.pkGenerator.byteSize() != Long.BYTES) && tries > 0)
@@ -388,6 +406,8 @@ public SchemaSpec get()
tries--;
}

spec.validate();

assert tries > 0 : String.format("Max number of tries exceeded on generator %d, can't generate a needed schema", idx);
return spec;
}
@@ -396,12 +416,34 @@ public SchemaSpec get()
};
}

public static int DEFAULT_SWITCH_AFTER = 5;
public static int GENERATORS_COUNT = PROGRESSIVE_GENERATORS.length;
public static int DEFAULT_RUNS = DEFAULT_SWITCH_AFTER * PROGRESSIVE_GENERATORS.length;
public static List<ColumnSpec<?>> toColumns(Map<String, String> config, ColumnSpec.Kind kind, boolean allowReverse)
{
List<ColumnSpec<?>> columns = new ArrayList<>(config.size());

for (Map.Entry<String, String> e : config.entrySet())
{
ColumnSpec.DataType<?> type = nameToTypeMap.get(e.getValue());
assert type != null : "Can't parse the type";
assert allowReverse || !type.isReversed() : String.format("%s columns aren't allowed to be reversed");
columns.add(new ColumnSpec<>(e.getKey(), type, kind));
}

public static Supplier<SchemaSpec> progression()
return columns;
}

public static SchemaSpec parse(String keyspace,
String table,
Map<String, String> pks,
Map<String, String> cks,
Map<String, String> regulars)
{
return progression(DEFAULT_SWITCH_AFTER); // would generate 30 tables before wrapping around
return new SchemaSpec(keyspace, table,
toColumns(pks, ColumnSpec.Kind.PARTITION_KEY, false),
toColumns(cks, ColumnSpec.Kind.CLUSTERING, false),
toColumns(regulars, ColumnSpec.Kind.REGULAR, false));
}

public static int DEFAULT_SWITCH_AFTER = Integer.getInteger("harry.test.progression.switch-after", 5);
public static int GENERATORS_COUNT = PROGRESSIVE_GENERATORS.length;
public static int DEFAULT_RUNS = DEFAULT_SWITCH_AFTER * GENERATORS_COUNT;
}
@@ -96,6 +96,12 @@ public SchemaSpec(String keyspace,
this.ALL_COLUMNS_BITSET = BitSet.allSet(regularColumns.size());
}

public void validate()
{
assert pkGenerator.byteSize() == Long.BYTES : partitionKeys.toString();
assert ckGenerator.byteSize() == Long.BYTES : clusteringKeys.toString();
}

public static interface AddRelationCallback
{
public void accept(ColumnSpec spec, Relation.RelationKind kind, Object value);
@@ -105,7 +105,7 @@ public static long distance(long curState, long newState, long stream)
curMult *= curMult;
}

return distance - 1;
return distance;
}

public static long shuffle(long state)
@@ -23,11 +23,7 @@

public class DoNothingModel implements Model
{
public void recordEvent(long lts, boolean quorumAchieved)
{
}

public void validatePartitionState(long verificationLts, Query query)
public void validate(Query query)
{
}

0 comments on commit 1c7fbc6

Please sign in to comment.