mirror of
https://github.com/nosqlbench/nosqlbench.git
synced 2025-02-25 18:55:28 -06:00
rename activitytype modules to driver modules
This commit is contained in:
32
driver-cqlverify/pom.xml
Normal file
32
driver-cqlverify/pom.xml
Normal file
@@ -0,0 +1,32 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>io.nosqlbench</groupId>
|
||||
<artifactId>mvn-defaults</artifactId>
|
||||
<version>3.12.100-SNAPSHOT</version>
|
||||
<relativePath>../mvn-defaults</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>driver-cqlverify</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>${project.artifactId}</name>
|
||||
|
||||
<description>
|
||||
A CQL content verifier ActivityType, based on the CQL ActivityType
|
||||
built on http://nosqlbench.io/
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- core dependencies -->
|
||||
|
||||
<dependency>
|
||||
<groupId>io.nosqlbench</groupId>
|
||||
<artifactId>driver-cql</artifactId>
|
||||
<version>3.12.100-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,20 @@
|
||||
package io.nosqlbench.activitytype.cqlverify;
|
||||
|
||||
import io.nosqlbench.activitytype.cql.core.CqlAction;
|
||||
import io.nosqlbench.activitytype.cql.core.CqlActivity;
|
||||
import io.nosqlbench.engine.api.activityapi.core.ActivityDefObserver;
|
||||
import io.nosqlbench.engine.api.activityimpl.ActivityDef;
|
||||
|
||||
public class CqlVerifyAction extends CqlAction implements ActivityDefObserver {
|
||||
|
||||
public CqlVerifyAction(ActivityDef activityDef, int slot, CqlActivity cqlActivity) {
|
||||
super(activityDef, slot, cqlActivity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityDefUpdate(ActivityDef activityDef) {
|
||||
super.onActivityDefUpdate(activityDef);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package io.nosqlbench.activitytype.cqlverify;
|
||||
|
||||
import io.nosqlbench.activitytype.cql.core.CqlActionDispenser;
|
||||
import io.nosqlbench.activitytype.cql.core.CqlActivity;
|
||||
import io.nosqlbench.engine.api.activityapi.core.Action;
|
||||
|
||||
public class CqlVerifyActionDispenser extends CqlActionDispenser {
|
||||
public CqlVerifyActionDispenser(CqlActivity cqlActivity) {
|
||||
super(cqlActivity);
|
||||
}
|
||||
|
||||
public Action getAction(int slot) {
|
||||
long async= getCqlActivity().getActivityDef().getParams().getOptionalLong("async").orElse(0L);
|
||||
if (async>0) {
|
||||
return new CqlVerifyAsyncAction(getCqlActivity().getActivityDef(), slot, getCqlActivity());
|
||||
} else {
|
||||
return new CqlVerifyAction(getCqlActivity().getActivityDef(), slot, getCqlActivity());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package io.nosqlbench.activitytype.cqlverify;
|
||||
|
||||
import io.nosqlbench.activitytype.cql.core.CqlActivity;
|
||||
import io.nosqlbench.activitytype.cql.statements.rsoperators.AssertSingleRowResultSet;
|
||||
import io.nosqlbench.engine.api.activityimpl.ActivityDef;
|
||||
import io.nosqlbench.virtdata.core.bindings.Bindings;
|
||||
import io.nosqlbench.virtdata.core.bindings.BindingsTemplate;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public class CqlVerifyActivity extends CqlActivity {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(CqlVerifyActivity.class);
|
||||
private BindingsTemplate expectedValuesTemplate;
|
||||
private VerificationMetrics verificationMetrics;
|
||||
|
||||
public CqlVerifyActivity(ActivityDef activityDef) {
|
||||
super(activityDef);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void initActivity() {
|
||||
this.verificationMetrics = new VerificationMetrics(getActivityDef());
|
||||
|
||||
super.initActivity();
|
||||
|
||||
if (this.stmts.size() > 1) {
|
||||
throw new RuntimeException("More than one statement was configured as active. "
|
||||
+ this.getActivityDef().getActivityType() + " requires exactly one.");
|
||||
}
|
||||
|
||||
Optional<String> randomMapper = stmts.stream()
|
||||
.flatMap(s -> s.getBindings().values().stream())
|
||||
.filter(t -> t.matches(".*Random.*") || t.matches(".*random.*"))
|
||||
.findAny();
|
||||
|
||||
|
||||
if (randomMapper.isPresent()) {
|
||||
throw new RuntimeException(
|
||||
"You should not try to verify data generated with random mapping " +
|
||||
"functions, like " + randomMapper.get() + " as it does not " +
|
||||
"produce stable results in different invocation order.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public synchronized BindingsTemplate getExpectedValuesTemplate() {
|
||||
if (expectedValuesTemplate==null) {
|
||||
expectedValuesTemplate = new BindingsTemplate();
|
||||
Map<String, String> bindings = stmts.get(0).getBindings();
|
||||
if (stmts.get(0).getParams().containsKey("verify-fields")) {
|
||||
List<String> fields = new ArrayList<>();
|
||||
String fieldsSpec = stmts.get(0).getParams().get("verify-fields");
|
||||
String[] vfields = fieldsSpec.split("\\s*,\\s*");
|
||||
for (String vfield : vfields) {
|
||||
if (vfield.equals("*")) {
|
||||
bindings.forEach((k,v)->fields.add(k));
|
||||
} else if (vfield.startsWith("+")) {
|
||||
fields.add(vfield.substring(1));
|
||||
} else if (vfield.startsWith("-")) {
|
||||
fields.remove(vfield.substring(1));
|
||||
} else if (vfield.matches("\\w+(\\w+->[\\w-]+)?")) {
|
||||
fields.add(vfield);
|
||||
} else {
|
||||
throw new RuntimeException("unknown verify-fields format: '" + vfield + "'");
|
||||
}
|
||||
}
|
||||
for (String vfield : fields) {
|
||||
String[] fieldNameAndBindingName = vfield.split("\\s*->\\s*", 2);
|
||||
String fieldName = fieldNameAndBindingName[0];
|
||||
String bindingName = fieldNameAndBindingName.length==1 ? fieldName : fieldNameAndBindingName[1];
|
||||
if (!bindings.containsKey(bindingName)) {
|
||||
throw new RuntimeException("binding name '" + bindingName +
|
||||
"' referenced in verify-fields, but it is not present in available bindings.");
|
||||
}
|
||||
expectedValuesTemplate.addFieldBinding(fieldName,bindings.get(bindingName));
|
||||
}
|
||||
} else {
|
||||
bindings.forEach((k,v)->expectedValuesTemplate.addFieldBinding(k,v));
|
||||
}
|
||||
}
|
||||
return expectedValuesTemplate;
|
||||
}
|
||||
|
||||
public synchronized VerificationMetrics getVerificationMetrics() {
|
||||
return verificationMetrics;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdownActivity() {
|
||||
super.shutdownActivity();
|
||||
VerificationMetrics metrics = getVerificationMetrics();
|
||||
long unverifiedValues = metrics.unverifiedValuesCounter.getCount();
|
||||
long unverifiedRows = metrics.unverifiedRowsCounter.getCount();
|
||||
|
||||
if (unverifiedRows > 0 || unverifiedValues > 0) {
|
||||
throw new RuntimeException(
|
||||
"There were " + unverifiedValues + " unverified values across " + unverifiedRows + " unverified rows."
|
||||
);
|
||||
}
|
||||
logger.info("verified " + metrics.verifiedValuesCounter.getCount() + " values across " + metrics.verifiedRowsCounter.getCount() + " verified rows");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityDefUpdate(ActivityDef activityDef) {
|
||||
super.onActivityDefUpdate(activityDef);
|
||||
addResultSetCycleOperator(new AssertSingleRowResultSet());
|
||||
|
||||
String verify = activityDef.getParams()
|
||||
.getOptionalString("compare").orElse("all");
|
||||
DiffType diffType = DiffType.valueOf(verify);
|
||||
Bindings verifyBindings = getExpectedValuesTemplate().resolveBindings();
|
||||
var differ = new RowDifferencer.ThreadLocalWrapper(
|
||||
getVerificationMetrics(),
|
||||
verifyBindings,
|
||||
diffType);
|
||||
addRowCycleOperator(differ);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package io.nosqlbench.activitytype.cqlverify;
|
||||
|
||||
import io.nosqlbench.engine.api.activityapi.core.ActionDispenser;
|
||||
import io.nosqlbench.engine.api.activityapi.core.ActivityType;
|
||||
import io.nosqlbench.engine.api.activityimpl.ActivityDef;
|
||||
import io.nosqlbench.nb.annotations.Service;
|
||||
|
||||
@Service(ActivityType.class)
|
||||
public class CqlVerifyActivityType implements ActivityType<CqlVerifyActivity> {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "cqlverify";
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionDispenser getActionDispenser(CqlVerifyActivity activity) {
|
||||
return new CqlVerifyActionDispenser(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CqlVerifyActivity getActivity(ActivityDef activityDef) {
|
||||
return new CqlVerifyActivity(activityDef);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package io.nosqlbench.activitytype.cqlverify;
|
||||
|
||||
import io.nosqlbench.activitytype.cql.core.CqlActivity;
|
||||
import io.nosqlbench.activitytype.cql.core.CqlAsyncAction;
|
||||
import io.nosqlbench.engine.api.activityapi.core.ActivityDefObserver;
|
||||
import io.nosqlbench.engine.api.activityimpl.ActivityDef;
|
||||
|
||||
public class CqlVerifyAsyncAction extends CqlAsyncAction implements ActivityDefObserver {
|
||||
|
||||
public CqlVerifyAsyncAction(ActivityDef activityDef, int slot, CqlActivity cqlActivity) {
|
||||
super(cqlActivity, slot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityDefUpdate(ActivityDef activityDef) {
|
||||
super.onActivityDefUpdate(activityDef);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package io.nosqlbench.activitytype.cqlverify;
|
||||
|
||||
public enum DiffType {
|
||||
|
||||
/// Verify that fields named in the row are present in the reference map.
|
||||
rowfields(0x1),
|
||||
|
||||
/// Verify that fields in the reference map are present in the row data.
|
||||
reffields(0x1<<1),
|
||||
|
||||
/// Verify that all fields present in either the row or the reference data
|
||||
/// are also present in the other.
|
||||
fields(0x1|0x1<<1),
|
||||
|
||||
/// Verify that all values of the same named field are equal, according to
|
||||
/// {@link Object#equals(Object)}}.
|
||||
values(0x1<<2),
|
||||
|
||||
/// Cross-verify all fields and field values between the reference data and
|
||||
/// the actual data.
|
||||
all(0x1|0x1<<1|0x1<<2);
|
||||
|
||||
public int bitmask;
|
||||
|
||||
DiffType(int bit) {
|
||||
this.bitmask = bit;
|
||||
}
|
||||
|
||||
public boolean is(DiffType option) {
|
||||
return (bitmask & option.bitmask) > 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
package io.nosqlbench.activitytype.cqlverify;
|
||||
|
||||
import com.datastax.driver.core.*;
|
||||
import io.nosqlbench.activitytype.cql.api.RowCycleOperator;
|
||||
import io.nosqlbench.activitytype.cql.errorhandling.exceptions.RowVerificationException;
|
||||
import io.nosqlbench.virtdata.core.bindings.Bindings;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.net.InetAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* <p>RowDifferencer uses the metadata associated with a row to access and compare
|
||||
* {@link Row} values in a type-specific way.
|
||||
* </p>
|
||||
*/
|
||||
public class RowDifferencer implements RowCycleOperator {
|
||||
|
||||
private final StringBuilder logbuffer = new StringBuilder();
|
||||
private final Map<String, Object> refMap = new HashMap<>();
|
||||
private final DiffType difftype;
|
||||
private final Bindings bindings;
|
||||
private final VerificationMetrics metrics;
|
||||
|
||||
|
||||
private RowDifferencer(VerificationMetrics metrics, Bindings bindings, DiffType diffType) {
|
||||
this.metrics = metrics;
|
||||
this.bindings = bindings;
|
||||
this.difftype = diffType;
|
||||
}
|
||||
|
||||
/**
|
||||
* see {@link DataType}
|
||||
*
|
||||
* @param typeName The DataType.Name of the field in question
|
||||
* @param row The row to read the field value from
|
||||
* @param fieldName The field name to read
|
||||
* @param genValue the generated value to compare against
|
||||
* @return true, if the value is equal
|
||||
*/
|
||||
private static boolean isEqual(DataType.Name typeName, Row row, String fieldName, Object genValue) {
|
||||
switch (typeName) {
|
||||
case ASCII: // ASCII(1, String.class)
|
||||
case VARCHAR: // VARCHAR(13, String.class)
|
||||
case TEXT: // TEXT(10, String.class)
|
||||
String textValue = row.getString(fieldName);
|
||||
return textValue.equals(genValue);
|
||||
case BIGINT: // BIGINT(2, Long.class)
|
||||
case COUNTER: // COUNTER(5, Long.class)
|
||||
long longValue = row.getLong(fieldName);
|
||||
return longValue == (long) genValue;
|
||||
case BLOB: // BLOB(3, ByteBuffer.class)
|
||||
// TODO: How do we test this one?
|
||||
case CUSTOM: // CUSTOM(0, ByteBuffer.class)
|
||||
ByteBuffer blobValue = row.getBytes(fieldName);
|
||||
return blobValue.equals(genValue);
|
||||
case BOOLEAN: // BOOLEAN(4, Boolean.class)
|
||||
boolean boolValue = row.getBool(fieldName);
|
||||
return boolValue == (boolean) genValue;
|
||||
case DECIMAL: // DECIMAL(6, BigDecimal.class)
|
||||
BigDecimal bigDecimalValue = row.getDecimal(fieldName);
|
||||
return bigDecimalValue.equals(genValue);
|
||||
case DOUBLE: // DOUBLE(7, Double.class)
|
||||
double doubleValue = row.getDouble(fieldName);
|
||||
return doubleValue == (double) genValue;
|
||||
case FLOAT: // FLOAT(8, Float.class)
|
||||
float floatValue = row.getFloat(fieldName);
|
||||
return floatValue == (float) genValue;
|
||||
case INET: // INET(16, InetAddress.class)
|
||||
InetAddress inetAddressValue = row.getInet(fieldName);
|
||||
return inetAddressValue.equals(genValue);
|
||||
case INT: // INT(9, Integer.class)
|
||||
int intValue = row.getInt(fieldName);
|
||||
return intValue == (int) genValue;
|
||||
case TIMESTAMP: // TIMESTAMP(11, Date.class)
|
||||
Date timestamp = row.getTimestamp(fieldName);
|
||||
return timestamp.equals(genValue);
|
||||
case UUID: // UUID(12, UUID.class)
|
||||
case TIMEUUID: // TIMEUUID(15, UUID.class)
|
||||
UUID uuidValue = row.getUUID(fieldName);
|
||||
return uuidValue.equals(genValue);
|
||||
case VARINT: // VARINT(14, BigInteger.class)
|
||||
BigInteger bigIntValue = row.getVarint(fieldName);
|
||||
return bigIntValue.equals(genValue);
|
||||
case LIST: // LIST(32, List.class)
|
||||
// TODO: How do we make getCollection methods work with non-String CQL types?
|
||||
List<?> list = row.getList(fieldName, String.class);
|
||||
return list.equals(genValue);
|
||||
case SET: // SET(34, Set.class)
|
||||
Set<?> set = row.getSet(fieldName, String.class);
|
||||
return set.equals(genValue);
|
||||
case MAP: // MAP(33, Map.class)
|
||||
Map<?, ?> map = row.getMap(fieldName, String.class, String.class);
|
||||
return map.equals(genValue);
|
||||
case UDT: // UDT(48, UDTValue.class)
|
||||
UDTValue udtValue = row.getUDTValue(fieldName);
|
||||
return udtValue.equals(genValue);
|
||||
case TUPLE: // TUPLE(49, TupleValue.class)
|
||||
TupleValue tupleValue = row.getTupleValue(fieldName);
|
||||
return tupleValue.equals(genValue);
|
||||
case SMALLINT:
|
||||
short shortVal = row.getShort(fieldName);
|
||||
return shortVal == (Short) genValue;
|
||||
case TINYINT:
|
||||
byte byteValue = row.getByte(fieldName);
|
||||
return byteValue == (byte) genValue;
|
||||
case DATE:
|
||||
LocalDate dateValue = row.getDate(fieldName);
|
||||
return dateValue.equals(genValue);
|
||||
case TIME:
|
||||
long timeValue = row.getTime(fieldName);
|
||||
return timeValue == (long) genValue;
|
||||
default:
|
||||
throw new RuntimeException("Unrecognized type:" + typeName);
|
||||
}
|
||||
}
|
||||
|
||||
private static String prettyPrint(DataType.Name typeName, Row row, String fieldName) {
|
||||
switch (typeName) {
|
||||
case ASCII: // ASCII(1, String.class)
|
||||
case VARCHAR: // VARCHAR(13, String.class)
|
||||
case TEXT: // TEXT(10, String.class)
|
||||
return row.getString(fieldName);
|
||||
case BIGINT: // BIGINT(2, Long.class)
|
||||
case COUNTER: // COUNTER(5, Long.class)
|
||||
long counterValue = row.getLong(fieldName);
|
||||
return String.valueOf(counterValue);
|
||||
case BLOB: // BLOB(3, ByteBuffer.class)
|
||||
case CUSTOM: // CUSTOM(0, ByteBuffer.class)
|
||||
ByteBuffer blobValue = row.getBytes(fieldName);
|
||||
return String.valueOf(blobValue);
|
||||
case BOOLEAN: // BOOLEAN(4, Boolean.class)
|
||||
boolean boolValue = row.getBool(fieldName);
|
||||
return String.valueOf(boolValue);
|
||||
case DECIMAL: // DECIMAL(6, BigDecimal.class)
|
||||
BigDecimal bigDecimalValue = row.getDecimal(fieldName);
|
||||
return String.valueOf(bigDecimalValue);
|
||||
case DOUBLE: // DOUBLE(7, Double.class)
|
||||
double doubleValue = row.getDouble(fieldName);
|
||||
return String.valueOf(doubleValue);
|
||||
case FLOAT: // FLOAT(8, Float.class)
|
||||
float floatValue = row.getFloat(fieldName);
|
||||
return String.valueOf(floatValue);
|
||||
case INET: // INET(16, InetAddress.class)
|
||||
InetAddress inetAddressValue = row.getInet(fieldName);
|
||||
return String.valueOf(inetAddressValue);
|
||||
case INT: // INT(9, Integer.class)
|
||||
int intValue = row.getInt(fieldName);
|
||||
return String.valueOf(intValue);
|
||||
case TIMESTAMP: // TIMESTAMP(11, Date.class)
|
||||
Date timestamp = row.getTimestamp(fieldName);
|
||||
return String.valueOf(timestamp);
|
||||
case UUID: // UUID(12, UUID.class)
|
||||
case TIMEUUID: // TIMEUUID(15, UUID.class)
|
||||
UUID uuidValue = row.getUUID(fieldName);
|
||||
return String.valueOf(uuidValue);
|
||||
case VARINT: // VARINT(14, BigInteger.class)
|
||||
BigInteger bigIntValue = row.getVarint(fieldName);
|
||||
return String.valueOf(bigIntValue);
|
||||
case LIST: // LIST(32, List.class)
|
||||
List<?> list = row.getList(fieldName, String.class);
|
||||
return String.valueOf(list);
|
||||
case SET: // SET(34, Set.class)
|
||||
Set<?> set = row.getSet(fieldName, String.class);
|
||||
return String.valueOf(set);
|
||||
case MAP: // MAP(33, Map.class)
|
||||
Map<?, ?> map = row.getMap(fieldName, String.class, String.class);
|
||||
return String.valueOf(map);
|
||||
case UDT: // UDT(48, UDTValue.class)
|
||||
UDTValue udtValue = row.getUDTValue(fieldName);
|
||||
return String.valueOf(udtValue);
|
||||
case TUPLE: // TUPLE(49, TupleValue.class)
|
||||
TupleValue tupleValue = row.getTupleValue(fieldName);
|
||||
return String.valueOf(tupleValue);
|
||||
case SMALLINT:
|
||||
short val = row.getShort(fieldName);
|
||||
return String.valueOf(val);
|
||||
case TINYINT:
|
||||
byte byteValue = row.getByte(fieldName);
|
||||
return String.valueOf(byteValue);
|
||||
case DATE:
|
||||
LocalDate dateValue = row.getDate(fieldName);
|
||||
return String.valueOf(dateValue);
|
||||
case TIME:
|
||||
long timeValue = row.getTime(fieldName);
|
||||
return String.valueOf(timeValue);
|
||||
default:
|
||||
throw new RuntimeException("Type not recognized:" + typeName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare the values of the row with the values generated.
|
||||
* <p>
|
||||
* Specifically,
|
||||
* <ol>
|
||||
* <li>Ensure the same number of fields.</li>
|
||||
* <li>Ensure the same pair-wise field names.</li>
|
||||
* <li>Ensure that each pair of same-named fields has the same data type.</li>
|
||||
* <li>Ensure that the value of each pair of fields is equal according to the equals
|
||||
* operator for the respective type.</li>
|
||||
* </ol>
|
||||
* *
|
||||
*
|
||||
* @param row A row of data
|
||||
* @param referenceMap a map of values
|
||||
* @return a count of differences between the row and the reference values
|
||||
*/
|
||||
private int compare(Row row, Map<String, Object> referenceMap) {
|
||||
int diff = 0;
|
||||
ColumnDefinitions cdefs = row.getColumnDefinitions();
|
||||
|
||||
logbuffer.setLength(0);
|
||||
|
||||
if (difftype.is(DiffType.reffields)) {
|
||||
List<String> missingRowFields = referenceMap.keySet().stream()
|
||||
.filter(gk -> !cdefs.contains(gk))
|
||||
.collect(Collectors.toList());
|
||||
if (missingRowFields.size() > 0) {
|
||||
diff += missingRowFields.size();
|
||||
|
||||
logbuffer.append("\nexpected fields '");
|
||||
logbuffer.append(String.join("','", missingRowFields));
|
||||
logbuffer.append("' not in row.");
|
||||
}
|
||||
}
|
||||
|
||||
if (difftype.is(DiffType.rowfields)) {
|
||||
List<String> missingRefFields = cdefs.asList().stream()
|
||||
.map(ColumnDefinitions.Definition::getName)
|
||||
.filter(k -> !referenceMap.containsKey(k))
|
||||
.collect(Collectors.toList());
|
||||
if (missingRefFields.size() > 0) {
|
||||
diff += missingRefFields.size();
|
||||
|
||||
logbuffer.append("\nexpected fields '");
|
||||
logbuffer.append(String.join("','", missingRefFields));
|
||||
logbuffer.append("' not in reference data: " + referenceMap);
|
||||
}
|
||||
}
|
||||
|
||||
if (difftype.is(DiffType.values)) {
|
||||
for (ColumnDefinitions.Definition definition : row.getColumnDefinitions()) {
|
||||
String name = definition.getName();
|
||||
if (referenceMap.containsKey(name)) {
|
||||
DataType type = definition.getType();
|
||||
if (!isEqual(type.getName(), row, name, referenceMap.get(name))) {
|
||||
logbuffer.append("\nvalue differs for '").append(name).append("' ");
|
||||
logbuffer.append("expected:'").append(referenceMap.get(name).toString()).append("'");
|
||||
logbuffer.append(" actual:'").append(prettyPrint(type.getName(), row, name)).append("'");
|
||||
diff++;
|
||||
metrics.unverifiedValuesCounter.inc();
|
||||
} else {
|
||||
metrics.verifiedValuesCounter.inc();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (diff == 0) {
|
||||
metrics.verifiedRowsCounter.inc();
|
||||
} else {
|
||||
metrics.unverifiedRowsCounter.inc();
|
||||
}
|
||||
return diff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the most recent detail log recorded by this thread.
|
||||
*
|
||||
* @return a logbuffer string, with one entry per line
|
||||
*/
|
||||
public String getDetail() {
|
||||
return this.logbuffer.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int apply(Row row, long cycle) {
|
||||
refMap.clear();
|
||||
bindings.setMap(refMap, cycle);
|
||||
int diffs = compare(row, refMap);
|
||||
if (diffs > 0) {
|
||||
HashMap<String, Object> mapcopy = new HashMap<>();
|
||||
mapcopy.putAll(refMap);
|
||||
throw new RowVerificationException(cycle, row, mapcopy, getDetail());
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ThreadLocalWrapper implements RowCycleOperator {
|
||||
|
||||
private final VerificationMetrics metrics;
|
||||
private final Bindings bindings;
|
||||
private final DiffType diffType;
|
||||
private ThreadLocal<RowDifferencer> tl;
|
||||
|
||||
public ThreadLocalWrapper(VerificationMetrics metrics, Bindings bindings, DiffType diffType) {
|
||||
this.metrics = metrics;
|
||||
this.bindings = bindings;
|
||||
this.diffType = diffType;
|
||||
tl = ThreadLocal.withInitial(() -> new RowDifferencer(metrics,bindings,diffType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int apply(Row row, long cycle) {
|
||||
return tl.get().apply(row,cycle);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package io.nosqlbench.activitytype.cqlverify;
|
||||
|
||||
import com.codahale.metrics.Counter;
|
||||
import io.nosqlbench.engine.api.activityimpl.ActivityDef;
|
||||
import io.nosqlbench.engine.api.metrics.ActivityMetrics;
|
||||
|
||||
public class VerificationMetrics {
|
||||
|
||||
public final Counter verifiedRowsCounter;
|
||||
public final Counter unverifiedRowsCounter;
|
||||
public final Counter verifiedValuesCounter;
|
||||
public final Counter unverifiedValuesCounter;
|
||||
|
||||
public VerificationMetrics(ActivityDef activityDef) {
|
||||
verifiedRowsCounter = ActivityMetrics.counter(activityDef,"verifiedrows");
|
||||
unverifiedRowsCounter= ActivityMetrics.counter(activityDef,"unverifiedrows");
|
||||
verifiedValuesCounter = ActivityMetrics.counter(activityDef,"verifiedvalues");
|
||||
unverifiedValuesCounter = ActivityMetrics.counter(activityDef,"unverifiedvalues");
|
||||
}
|
||||
|
||||
}
|
||||
166
driver-cqlverify/src/main/resources/cqlverify.md
Normal file
166
driver-cqlverify/src/main/resources/cqlverify.md
Normal file
@@ -0,0 +1,166 @@
|
||||
# cqlverify
|
||||
|
||||
This activity type allows you to read values from a database and compare them to
|
||||
the generated values that were expected to be written, row-by-row, producing a
|
||||
comparative result between the two.
|
||||
|
||||
The verification options include:
|
||||
|
||||
1. Each row contains the right fields, according to the reference data.
|
||||
2. Each row contains only the fields specified in the reference data.
|
||||
3. Each value of each row, by name, is equal to the referenced data,
|
||||
according to the Java equals implementation for the object type
|
||||
specified in that field's metadata.
|
||||
|
||||
The data bindings are used to generate the expected values that would be used
|
||||
for an upsert. Each row is verified according to these values, and any
|
||||
discrepancy is treated as an error that can be counted, logged, etc.
|
||||
|
||||
### Using cqlverify
|
||||
|
||||
The cqlverify activity type is built on top of the cql activity type. As such,
|
||||
it has all of the same capabilities and options, and then some. See the cql
|
||||
activity type documentation for the usual details. This doc page only covers how
|
||||
the cqlverify activity extends it.
|
||||
|
||||
The differences between the cql and cqlverify activity types are mostly in how
|
||||
how you configure for verifiable data and error handling.
|
||||
|
||||
##### Writing verifiable data
|
||||
|
||||
The cqlverify driver does not retain logged data for verification. Still, it is able to compare data as if it had a
|
||||
separate data set to compare to. This is possible only because the data generation facilities used by NoSQLBench provide
|
||||
realistic and voluminous synthetic data that can be recalled from a recipe and accessed dynamically.
|
||||
|
||||
That means, however, that you must avoid using the non-stable data mapping functions when writing data. The rule of
|
||||
thumb is to avoid using any data mapping functions containing the word "Random", as these are the ones that have
|
||||
historically used internal RNG state. Instead, swap in their replacements that start with "Hashed". There is a hashed
|
||||
equivalent to all of the original random functions. The rng-based functions will be deprecated in a future release.
|
||||
|
||||
In a typical cql activity, you are allowed to name the bindings however you like, so long as the binding names match the
|
||||
anchor names in your statement template. Because we need to match reference field data to actual row data pair-wise by
|
||||
field name, there is a more strict requirement for cqlverify activities. The binding names themselves are now required
|
||||
to match the field names that they are expected to be compared to.
|
||||
|
||||
The simplest way to do this is to follow this recipe:
|
||||
|
||||
1. Make the binding names the same as the field names that you use in
|
||||
in your write statements.
|
||||
2. When you configure your read statement for the cqlverify activity,
|
||||
simply include the same bindings as-is, using the partition and
|
||||
clustering fields in the appropriate where clauses.
|
||||
|
||||
*note*: It used to be an error to have bindings names in excess of what anchor
|
||||
names would match. Now, it is only an error if an anchor is not qualified with
|
||||
a matching binding name. This allows you to simply copy your bindings as-is
|
||||
directly from your write statement with no issues.
|
||||
|
||||
### Configuring the verification Reader
|
||||
|
||||
A cqlverify activity is almost exactly like any other cql activity. However, you
|
||||
configure a single read statement to access the row data you want to verify. The
|
||||
bindings for the read statement should include the data mappings that you have
|
||||
for the write statement. That's pretty much all you have to do.
|
||||
|
||||
The names of the bindings and the values they produce are considered, depending
|
||||
on the *compare* setting explained below. This means that you need to make sure
|
||||
that the bindings that are provided for the statement are exactly the same as
|
||||
you expect the row structure, irrespective of field order. For some statements
|
||||
which use the same value in more than one place, you must name these uniquely
|
||||
as well.
|
||||
|
||||
If more than one statement is active for a cqlverify activity, then an error is
|
||||
thrown. This may change in the future, but for now it is a requirement.
|
||||
|
||||
### Handling Verification Errors
|
||||
|
||||
The cqlverify activity extends on the error handling stack mechanism that is
|
||||
used by the cql activity type, by introducing a new error category:
|
||||
*unverified*. The default configuration for this error category is
|
||||
|
||||
unverified=stop
|
||||
|
||||
However, the usual options, including "stop", "warn", "retry", "histogram",
|
||||
"count", and "ignore" are also allowed.
|
||||
|
||||
Care should be taken to set the other error handling categories to be strict
|
||||
enough to avoid false negatives in testing. The verification on a row can only
|
||||
be done if the row is actually read first. If you set the error handler stack to
|
||||
only count real errors, for example, then you will be preempting the read
|
||||
verifier. Therefore, there is a default setting for the cqlverify activity for
|
||||
the catch-all error handler parameter *errors*.
|
||||
|
||||
This means that the default error handling behavior will cause an exception to
|
||||
be thrown and the client will exit by default. If you wish for something less
|
||||
dramatic, then set it to
|
||||
|
||||
errors=...,unverified->count
|
||||
|
||||
or
|
||||
|
||||
errors=...,unverified->warn
|
||||
|
||||
##### rows to verify
|
||||
|
||||
Currently, every read operation in a cqlverify activity must have a single row
|
||||
in the result set. If there is no row, then the row fails validation. The same
|
||||
happens if there is more than one row.
|
||||
|
||||
A future release may allow for paged reads for quicker verification.
|
||||
|
||||
### Example activity definitions
|
||||
|
||||
Write 100K cycles of telemetry data
|
||||
|
||||
... run driver=cql alias=writesome workload=cql-iot tags=group:write cycles=100000 host=...
|
||||
|
||||
Verify the the same 100K cycles of telemetry data
|
||||
|
||||
... run driver=cqlverify alias=verify workload=cql-iot tags=group:verify cycles=100000 host=...
|
||||
|
||||
To see how these examples work, consult the telemetry.yaml file in the nosqlbench.jar.
|
||||
|
||||
### CQLVerify ActivityType Parameters
|
||||
|
||||
(in addition to those provided by the cql activity type)
|
||||
|
||||
- **compare** - what to verify. Valid values are "reffields",
|
||||
"rowfields", "fields", "values", or "all"
|
||||
(default: all)
|
||||
- rowfields - Verify that fields in the row, by name, are
|
||||
not in excess of what is provided in the reference data.
|
||||
- reffields - Verify that fields in the row, by name, are
|
||||
present for all all of those provided in the reference data.
|
||||
- fields - A synonym for rowfields AND reffields
|
||||
(full set equivalence)
|
||||
- values - Verify that all the pair-wise fields have equal
|
||||
values, according to the type-specific equals method for
|
||||
the data type identified in the row metadata by field name.
|
||||
- all - A synonym for fields AND values
|
||||
|
||||
### CQLVerify Statement Parameters
|
||||
|
||||
- **verify-fields** - an optional modifier of fields to verify for a statement.
|
||||
If this parameter is not provided, then it is presumed to be '*' by default.
|
||||
This is a string which consists of comma-separate values. If the value
|
||||
is '*', then all the bindings that are visible for the statement will be
|
||||
used as expected values.
|
||||
If it is a word that starts with '-', like '-field2', then the name after the
|
||||
dash is removed from the list of fields to verify.
|
||||
If it is a word that starts with a '+', like '+field3', or a simple word,
|
||||
then the field is added to the list of fields to verify.
|
||||
This parameter is useful if you have a set of default bindings and want
|
||||
to specify which subset of them of them will be used just for this statement.
|
||||
|
||||
If any of the added fields is in the form "f->b", then it is taken as a mapping
|
||||
from the field name _f_ in the schema to a binding _b_.
|
||||
|
||||
### Metrics
|
||||
|
||||
The cqlverify activity type adds some verification-specific metrics:
|
||||
|
||||
- alias.verifiedrows - A counter for how many rows passed verification
|
||||
- alias.unverifiedrows - A counter for how many rows failed verification
|
||||
- alias.verifiedvalues - A counter for how many field values were verified
|
||||
- alias.unverifiedvalues - A counter for how many field values were unverified
|
||||
|
||||
2
driver-cqlverify/src/main/resources/topics.md
Normal file
2
driver-cqlverify/src/main/resources/topics.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# cqlverify help topics
|
||||
- cqlverify
|
||||
Reference in New Issue
Block a user