CEP-15: (C*) per-table transactional configuration

Patch by Blake Eggleston; Reviewed by Ariel Wesberg for CASSANDRA-19016
This commit is contained in:
Blake Eggleston 2024-03-26 16:39:41 -07:00 committed by David Capwell
parent 763bcf2de5
commit d3deef36d3
91 changed files with 2588 additions and 2250 deletions

View File

@ -18,6 +18,8 @@
package org.apache.cassandra.config;
import org.apache.cassandra.service.consensus.TransactionalMode;
public class AccordSpec
{
public volatile boolean enabled = false;
@ -49,4 +51,22 @@ public class AccordSpec
public volatile DurationSpec durability_txnid_lag = new DurationSpec.IntSecondsBound(5);
public volatile DurationSpec shard_durability_cycle = new DurationSpec.IntMinutesBound(2);
public volatile DurationSpec global_durability_cycle = new DurationSpec.IntMinutesBound(10);
public enum TransactionalRangeMigration
{
auto, explicit
}
/**
* Defines the behavior of range migration opt-in when changing transactional settings on a table. In auto,
* all ranges are marked as migrating and no additional user action is needed aside from running repairs. In
* explicit, no ranges are marked as migrating, and the user needs to explicitly mark ranges as migrating to
* the target transactional mode via nodetool.
*/
public volatile TransactionalRangeMigration range_migration = TransactionalRangeMigration.auto;
/**
* default transactional mode for tables created by this node when no transactional mode has been specified in the DDL
*/
public TransactionalMode default_transactional_mode = TransactionalMode.off;
}

View File

@ -44,7 +44,6 @@ import org.apache.cassandra.io.compress.BufferType;
import org.apache.cassandra.io.sstable.format.big.BigFormat;
import org.apache.cassandra.service.StartupChecks.StartupCheckType;
import org.apache.cassandra.utils.StorageCompatibilityMode;
import org.apache.cassandra.service.accord.IAccordService;
import static org.apache.cassandra.config.CassandraRelevantProperties.AUTOCOMPACTION_ON_STARTUP_ENABLED;
import static org.apache.cassandra.config.CassandraRelevantProperties.CASSANDRA_AVAILABLE_PROCESSORS;
@ -1167,8 +1166,6 @@ public class Config
public volatile boolean client_request_size_metrics_enabled = true;
public LWTStrategy lwt_strategy = LWTStrategy.migration;
public NonSerialWriteStrategy non_serial_write_strategy = NonSerialWriteStrategy.normal;
public volatile int max_top_size_partition_count = 10;
public volatile int max_top_tombstone_partition_count = 10;
@ -1387,118 +1384,6 @@ public class Config
cell
}
/**
* How to pick a consensus protocol for CAS
* and serial read operations. Transaction statements
* will always run on Accord. Legacy in this context includes PaxosV2.
*/
public enum LWTStrategy
{
/**
* Allow both Accord and PaxosV1/V2 to run on the same cluster
* Some keys and ranges might be running on Accord if they
* have been migrated and the rest will run on Paxos until
* they are migrated.
*/
migration,
/**
* Everything will be run on Accord. Useful for new deployments
* that don't want to accidentally start using legacy Paxos
* requiring migration to Accord.
*/
accord
}
/*
* Configure how non-serial writes should be executed. For Accord transactions to function correctly
* when mixed with non-SERIAL writes it's necessary for the writes to occur through Accord.
*
* Accord will also use this configuration to determine what consistency level to perform its reads
* at since it will need to be able to read data written at non-SERIAL consistency levels.
*
* BlockingReadRepair will also use this configuration to determine how BRR mutations are applied. For migration
* and accord the BRR mutations will be applied as Accord transactions so that BRR doesn't expose Accord to
* uncommitted Accord data that is being RRed. This can occur when Accord has applied a transaction at some, but not
* all replica since Accord defaults to asynchronous commit.
*
* By routing repairs through Accord it is guaranteed that the Accord derived contents of the repair have already been applied at any
* replica where Accord applies the transaction. This also prevents BRR from breaking atomicity of Accord writes.
*
* If they are not written through Accord then reads through Accord will be required to occur at
* consistency level compatible with the non-serial writes preventing single replica reads from being performed
* by Accord. It will also require Accord to perform read repair of non-serial writes.
*
* Even then there is the potential for Accord to inconsistently execute transactions at different replicas
* because different coordinators for an Accord transaction may encounter different non-SERIAL write state and
* race to commit different outcomes for the transaction.
*
* This is different from Paxos because Paxos performs consensus on the actual values to be applied so recovery
* coordinators will always produce a consistent state when applying a transaction. Accord performs consensus on
* the execution order of transaction and different coordinators witnessing different states not managed by Accord
* can produce multiple outcomes for a transaction.
*
* // TODO (maybe): To safely migrate you would have to route all writes through Accord with the current implementation
* // We could do it by range instead in the migration version, but then we need to know when all in flight writes
* // are done before marking a range as migrated. Would waiting out the timeout be enough (timeout bugs!)?
*/
public enum NonSerialWriteStrategy
{
/*
* Execute writes through Cassandra via StorageProxy's normal write path. This can lead Accord to compute
* multiple outcomes for a transaction that depends on data written by non-SERIAL writes.
*/
normal(false, false, false),
/*
* Allow mixing of non-SERIAL writes and Accord, but still force BRR through Accord
*/
mixed(false, false, true),
/*
* Execute writes through Accord skipping StorageProxy's normal write path, but commit
* writes at the provided consistency level so they can be read via non-SERIAL consistency levels.
*/
migration(false, true, true),
/*
* Execute writes through Accord skipping StorageProxy's normal write path. Ignores the provided consistency level
* which makes Accord commit writes at ANY similar to Paxos with commit consistency level ANY.
*/
accord(true, true, true);
public final boolean ignoresSuppliedConsistencyLevel;
public final boolean writesThroughAccord;
public final boolean blockingReadRepairThroughAccord;
NonSerialWriteStrategy(boolean ignoresSuppliedConsistencyLevel, boolean writesThroughAccord, boolean blockingReadRepairThroughAccord)
{
this.ignoresSuppliedConsistencyLevel = ignoresSuppliedConsistencyLevel;
this.writesThroughAccord = writesThroughAccord;
this.blockingReadRepairThroughAccord = blockingReadRepairThroughAccord;
}
public ConsistencyLevel commitCLForStrategy(ConsistencyLevel consistencyLevel)
{
if (ignoresSuppliedConsistencyLevel)
return null;
if (!IAccordService.SUPPORTED_COMMIT_CONSISTENCY_LEVELS.contains(consistencyLevel))
throw new UnsupportedOperationException("Consistency level " + consistencyLevel + " is unsupported with Accord for write/commit, supported are ANY, ONE, QUORUM, and ALL");
return consistencyLevel;
}
public ConsistencyLevel readCLForStrategy(ConsistencyLevel consistencyLevel)
{
if (ignoresSuppliedConsistencyLevel)
return null;
if (!IAccordService.SUPPORTED_READ_CONSISTENCY_LEVELS.contains(consistencyLevel))
throw new UnsupportedOperationException("Consistency level " + consistencyLevel + " is unsupported with Accord for read, supported are ONE, QUORUM, and SERIAL");
return consistencyLevel;
}
}
private static final Set<String> SENSITIVE_KEYS = new HashSet<String>() {{
add("client_encryption_options");
add("server_encryption_options");

View File

@ -78,8 +78,6 @@ import org.apache.cassandra.auth.INetworkAuthorizer;
import org.apache.cassandra.auth.IRoleManager;
import org.apache.cassandra.config.Config.CommitLogSync;
import org.apache.cassandra.config.Config.DiskAccessMode;
import org.apache.cassandra.config.Config.LWTStrategy;
import org.apache.cassandra.config.Config.NonSerialWriteStrategy;
import org.apache.cassandra.config.Config.PaxosOnLinearizabilityViolation;
import org.apache.cassandra.config.Config.PaxosStatePurging;
import org.apache.cassandra.db.ConsistencyLevel;
@ -120,6 +118,7 @@ import org.apache.cassandra.security.JREProvider;
import org.apache.cassandra.security.SSLFactory;
import org.apache.cassandra.service.CacheService.CacheType;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.service.consensus.TransactionalMode;
import org.apache.cassandra.service.paxos.Paxos;
import org.apache.cassandra.tcm.RegistrationStatus;
import org.apache.cassandra.utils.FBUtilities;
@ -168,8 +167,8 @@ import static org.apache.cassandra.utils.Clock.Global.logInitializationOutcome;
public class DatabaseDescriptor
{
public static final String NO_ACCORD_PAXOS_STRATEGY_WITH_ACCORD_DISABLED_MESSAGE =
"Cannot use lwt_strategy \"accord\" while Accord transactions are disabled.";
public static final String NO_ACCORD_PAXOS_STRATEGY_WITH_ACCORD_DISABLED_MESSAGE =
"Cannot use lwt_strategy \"accord\" while Accord transactions are disabled.";
static
{
@ -968,8 +967,8 @@ public class DatabaseDescriptor
{
// if consensusMigrationCacheSizeInMiB option was set to "auto" then size of the cache should be "min(1% of Heap (in MB), 50MB)
consensusMigrationCacheSizeInMiB = (conf.consensus_migration_cache_size == null)
? Math.min(Math.max(1, (int) (Runtime.getRuntime().totalMemory() * 0.01 / 1024 / 1024)), 50)
: conf.consensus_migration_cache_size.toMebibytes();
? Math.min(Math.max(1, (int) (Runtime.getRuntime().totalMemory() * 0.01 / 1024 / 1024)), 50)
: conf.consensus_migration_cache_size.toMebibytes();
if (consensusMigrationCacheSizeInMiB < 0)
throw new NumberFormatException(); // to escape duplicating error message
@ -1166,14 +1165,6 @@ public class DatabaseDescriptor
// run audit logging options through sanitation and validation
if (conf.audit_logging_options != null)
setAuditLoggingOptions(conf.audit_logging_options);
if (conf.lwt_strategy == LWTStrategy.accord)
{
if (!conf.accord.enabled)
throw new ConfigurationException(NO_ACCORD_PAXOS_STRATEGY_WITH_ACCORD_DISABLED_MESSAGE);
if (conf.non_serial_write_strategy == Config.NonSerialWriteStrategy.normal)
throw new ConfigurationException("If Accord is used for LWTs then regular writes needs to be routed through Accord for interoperability by setting non_serial_write_strategy to \"accord\" or \"migration\"");
}
}
@VisibleForTesting
@ -3653,25 +3644,14 @@ public class DatabaseDescriptor
return conf.paxos_topology_repair_strict_each_quorum;
}
// TODO (desired): This configuration should come out of TrM to force the cluster to agree on it
public static LWTStrategy getLWTStrategy()
public static AccordSpec.TransactionalRangeMigration getTransactionalRangeMigration()
{
return conf.lwt_strategy;
return conf.accord.range_migration;
}
public static void setLWTStrategy(LWTStrategy lwtStrategy)
public static void setTransactionalRangeMigration(AccordSpec.TransactionalRangeMigration val)
{
conf.lwt_strategy = lwtStrategy;
}
public static Config.NonSerialWriteStrategy getNonSerialWriteStrategy()
{
return conf.non_serial_write_strategy;
}
public static void setNonSerialWriteStrategy(NonSerialWriteStrategy nonSerialWriteStrategy)
{
conf.non_serial_write_strategy = nonSerialWriteStrategy;
conf.accord.range_migration = Preconditions.checkNotNull(val);
}
public static int getAccordBarrierRetryAttempts()
@ -3694,6 +3674,11 @@ public class DatabaseDescriptor
return conf.accord.range_barrier_timeout.to(TimeUnit.NANOSECONDS);
}
public static TransactionalMode defaultTransactionalMode()
{
return conf.accord.default_transactional_mode;
}
public static void setNativeTransportMaxRequestDataInFlightPerIpInBytes(long maxRequestDataInFlightInBytes)
{
if (maxRequestDataInFlightInBytes == -1)

View File

@ -36,7 +36,6 @@ import org.slf4j.LoggerFactory;
import accord.api.Update;
import accord.primitives.Txn;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.QueryOptions;
import org.apache.cassandra.cql3.UpdateParameters;
import org.apache.cassandra.cql3.conditions.ColumnCondition;
@ -502,7 +501,7 @@ public class CQL3CasRequest implements CASRequest
Update update = createUpdate(clientState, commitConsistencyLevel);
// If the write strategy is sending all writes through Accord there is no need to use the supplied consistency
// level since Accord will manage reading safely
consistencyLevel = DatabaseDescriptor.getNonSerialWriteStrategy().readCLForStrategy(consistencyLevel);
consistencyLevel = metadata.params.transactionalMode.readCLForStrategy(consistencyLevel);
TxnRead read = TxnRead.createCasRead(readCommand, consistencyLevel);
// In a CAS requesting only one key is supported and writes
// can't be dependent on any data that is read (only conditions)
@ -514,7 +513,7 @@ public class CQL3CasRequest implements CASRequest
{
// Potentially ignore commit consistency level if non-SERIAL write strategy is Accord
// since it is safe to match what non-SERIAL writes do
commitConsistencyLevel = DatabaseDescriptor.getNonSerialWriteStrategy().commitCLForStrategy(commitConsistencyLevel);
commitConsistencyLevel = metadata.params.transactionalMode.commitCLForStrategy(commitConsistencyLevel);
return new TxnUpdate(createWriteFragments(clientState), createCondition(), commitConsistencyLevel);
}

View File

@ -63,9 +63,11 @@ import org.apache.cassandra.db.SinglePartitionReadQuery;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.partitions.FilteredPartition;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.QueryState;
import org.apache.cassandra.service.accord.AccordService;
import org.apache.cassandra.service.accord.api.AccordRoutableKey;
import org.apache.cassandra.service.accord.txn.AccordUpdate;
import org.apache.cassandra.service.accord.txn.TxnCondition;
import org.apache.cassandra.service.accord.txn.TxnData;
@ -78,13 +80,12 @@ import org.apache.cassandra.service.accord.txn.TxnResult;
import org.apache.cassandra.service.accord.txn.TxnUpdate;
import org.apache.cassandra.service.accord.txn.TxnWrite;
import org.apache.cassandra.transport.Dispatcher;
import org.apache.cassandra.service.consensus.TransactionalMode;
import org.apache.cassandra.transport.messages.ResultMessage;
import org.apache.cassandra.utils.FBUtilities;
import static accord.primitives.Txn.Kind.EphemeralRead;
import static accord.primitives.Txn.Kind.Read;
import static org.apache.cassandra.config.Config.NonSerialWriteStrategy.accord;
import static org.apache.cassandra.config.DatabaseDescriptor.getNonSerialWriteStrategy;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkNotNull;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkTrue;
@ -314,6 +315,11 @@ public class TransactionStatement implements CQLStatement.CompositeCQLStatement,
return new Keys(keySet);
}
private static TransactionalMode transactionalModeForSingleKey(Keys keys)
{
return Schema.instance.getTableMetadata(((AccordRoutableKey) keys.get(0)).table()).params.transactionalMode;
}
@VisibleForTesting
public Txn createTxn(ClientState state, QueryOptions options)
{
@ -326,7 +332,8 @@ public class TransactionStatement implements CQLStatement.CompositeCQLStatement,
List<TxnNamedRead> reads = createNamedReads(options, state, ImmutableMap.of(), keySet::add);
Keys txnKeys = toKeys(keySet);
TxnRead read = createTxnRead(reads, txnKeys, null);
Txn.Kind kind = txnKeys.size() == 1 && getNonSerialWriteStrategy() == accord ? EphemeralRead : Read;
Txn.Kind kind = txnKeys.size() == 1 && transactionalModeForSingleKey(txnKeys) == TransactionalMode.full
? EphemeralRead : Read;
return new Txn.InMemory(kind, txnKeys, read, TxnQuery.ALL, null);
}
else
@ -376,8 +383,6 @@ public class TransactionStatement implements CQLStatement.CompositeCQLStatement,
Txn txn = createTxn(state.getClientState(), options);
AccordService.instance().maybeConvertTablesToAccord(txn);
TxnResult txnResult = AccordService.instance().coordinate(txn, options.getConsistency(), requestTime);
if (txnResult.kind() == retry_new_protocol)
throw new IllegalStateException("Transaction statement should never be required to switch consensus protocols");

View File

@ -40,6 +40,7 @@ import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.ClientWarn;
import org.apache.cassandra.service.QueryState;
import org.apache.cassandra.service.accord.AccordTopology;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Dispatcher;
import org.apache.cassandra.transport.Event.SchemaChange;
@ -199,6 +200,9 @@ abstract public class AlterSchemaStatement implements CQLStatement.SingleKeyspac
if (null != user && !user.isAnonymous())
createdResources(diff).forEach(r -> grantPermissionsOnResource(r, user));
// if the changes affected accord, wait for accord to apply them
AccordTopology.awaitTopologyReadiness(diff, result.epoch);
return new ResultMessage.SchemaChange(schemaChangeEvent(diff));
}

View File

@ -59,12 +59,14 @@ import org.apache.cassandra.schema.KeyspaceMetadata;
import org.apache.cassandra.schema.Keyspaces;
import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
import org.apache.cassandra.schema.MemtableParams;
import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.schema.TableParams;
import org.apache.cassandra.schema.UserFunctions;
import org.apache.cassandra.schema.ViewMetadata;
import org.apache.cassandra.schema.Views;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.consensus.migration.TransactionalMigrationFromMode;
import org.apache.cassandra.service.reads.repair.ReadRepairStrategy;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.Epoch;
@ -83,6 +85,8 @@ import static org.apache.cassandra.schema.TableMetadata.Flag;
public abstract class AlterTableStatement extends AlterSchemaStatement
{
private static final Logger logger = LoggerFactory.getLogger(AlterTableStatement.class);
protected final String tableName;
private final boolean ifExists;
protected ClientState state;
@ -583,6 +587,42 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
validateDefaultTimeToLive(attrs.asNewTableParams());
}
private TableParams validateAndUpdateTransactionalMigration(TableParams prev, TableParams next)
{
if (next.transactionalMode.accordIsEnabled && SchemaConstants.isSystemKeyspace(keyspaceName))
throw ire("Cannot enable accord on system tables (%s.%s)", keyspaceName, tableName);
boolean modeChange = prev.transactionalMode != next.transactionalMode;
boolean wasMigrating = prev.transactionalMigrationFrom.isMigrating();
boolean forceMigrationChange = prev.transactionalMigrationFrom != next.transactionalMigrationFrom;
if (modeChange && next.transactionalMode.accordIsEnabled && !DatabaseDescriptor.getAccordTransactionsEnabled())
throw ire(format("Cannot change transactional mode to %s for %s.%s with accord_transactions_enabled set to false",
next.transactionalMode, keyspaceName, tableName));
// user is manually updating migration mode, don't interfere
if (forceMigrationChange)
{
logger.warn("Forcing unsafe migration change from {} to {} with transaction mode {}", prev.transactionalMigrationFrom, next.transactionalMigrationFrom, next.transactionalMode);
return next;
}
if (!modeChange)
return next;
// if the user is trying to revert to the mode being migrated from, allow it. The migration states will be inverted when
// the transformation is applied. Otherwise throw
if (wasMigrating && next.transactionalMode != prev.transactionalMigrationFrom.from)
throw ire(format("Cannot change transactional mode from %s to %s for %s.%s before transactional migration has completed",
prev.transactionalMode, next.transactionalMode,
keyspaceName, tableName));
// set table to migrating
TransactionalMigrationFromMode migrateFrom = TransactionalMigrationFromMode.fromMode(prev.transactionalMode, next.transactionalMode);
return next.unbuild().transactionalMigrationFrom(migrateFrom).build();
}
public KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetadata table, ClusterMetadata metadata)
{
attrs.validate();
@ -610,6 +650,8 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
if (!params.compression.isEnabled())
Guardrails.uncompressedTablesEnabled.ensureEnabled(state);
params = validateAndUpdateTransactionalMigration(table.params, params);
return keyspace.withSwapped(keyspace.tables.withSwapped(table.withSwapped(params)));
}
}

View File

@ -31,6 +31,7 @@ import org.apache.cassandra.audit.AuditLogEntryType;
import org.apache.cassandra.auth.DataResource;
import org.apache.cassandra.auth.IResource;
import org.apache.cassandra.auth.Permission;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.*;
import org.apache.cassandra.cql3.constraints.ColumnConstraints;
import org.apache.cassandra.cql3.functions.masking.ColumnMask;
@ -46,6 +47,7 @@ import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
import org.apache.cassandra.transport.Event.SchemaChange.Target;
import static java.lang.String.format;
import static java.util.Comparator.comparing;
import static com.google.common.collect.Iterables.concat;
@ -145,6 +147,16 @@ public final class CreateTableStatement extends AlterSchemaStatement
if (!table.params.compression.isEnabled())
Guardrails.uncompressedTablesEnabled.ensureEnabled(state);
if (table.params.transactionalMode.accordIsEnabled && SchemaConstants.isSystemKeyspace(keyspaceName))
throw ire("Cannot enable accord on system tables (%s.%s)", keyspaceName, tableName);
if (table.params.transactionalMode.accordIsEnabled && !DatabaseDescriptor.getAccordTransactionsEnabled())
throw ire(format("Cannot create table %s.%s with transactional mode %s with accord.enabled set to false",
keyspaceName, tableName, table.params.transactionalMode));
if (table.params.transactionalMigrationFrom.isMigrating())
throw ire("Cannot set transactional migration on new tables (%s.%s), %s", keyspaceName, tableName, table.params.transactionalMigrationFrom);
return schema.withAddedOrUpdated(keyspace.withSwapped(keyspace.tables.with(table)));
}

View File

@ -23,6 +23,7 @@ import java.util.Set;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.statements.PropertyDefinitions;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.exceptions.SyntaxException;
@ -34,6 +35,8 @@ import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.schema.TableParams;
import org.apache.cassandra.schema.TableParams.Option;
import org.apache.cassandra.service.accord.fastpath.FastPathStrategy;
import org.apache.cassandra.service.consensus.TransactionalMode;
import org.apache.cassandra.service.consensus.migration.TransactionalMigrationFromMode;
import org.apache.cassandra.service.reads.SpeculativeRetryPolicy;
import org.apache.cassandra.service.reads.repair.ReadRepairStrategy;
@ -64,7 +67,10 @@ public final class TableAttributes extends PropertyDefinitions
TableParams asNewTableParams()
{
return build(TableParams.builder());
TableParams.Builder builder = TableParams.builder();
if (!hasOption(TRANSACTIONAL_MODE))
builder.transactionalMode(DatabaseDescriptor.defaultTransactionalMode());
return build(builder);
}
TableParams asAlteredTableParams(TableParams previous)
@ -165,6 +171,12 @@ public final class TableAttributes extends PropertyDefinitions
}
}
if (hasOption(Option.TRANSACTIONAL_MODE))
builder.transactionalMode(TransactionalMode.fromString(getString(Option.TRANSACTIONAL_MODE)));
if (hasOption(Option.TRANSACTIONAL_MIGRATION_FROM))
builder.transactionalMigrationFrom(TransactionalMigrationFromMode.fromString(getString(Option.TRANSACTIONAL_MIGRATION_FROM)));
return builder.build();
}

View File

@ -136,8 +136,6 @@ import static org.apache.cassandra.config.DatabaseDescriptor.paxosStatePurging;
import static org.apache.cassandra.cql3.QueryProcessor.executeInternal;
import static org.apache.cassandra.cql3.QueryProcessor.executeInternalWithNowInSec;
import static org.apache.cassandra.cql3.QueryProcessor.executeOnceInternal;
import static org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState.ConsensusMigratedAt;
import static org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState.ConsensusMigrationTarget;
import static org.apache.cassandra.gms.ApplicationState.DC;
import static org.apache.cassandra.gms.ApplicationState.HOST_ID;
import static org.apache.cassandra.gms.ApplicationState.INTERNAL_ADDRESS_AND_PORT;
@ -146,6 +144,8 @@ import static org.apache.cassandra.gms.ApplicationState.RACK;
import static org.apache.cassandra.gms.ApplicationState.RELEASE_VERSION;
import static org.apache.cassandra.gms.ApplicationState.STATUS_WITH_PORT;
import static org.apache.cassandra.gms.ApplicationState.TOKENS;
import org.apache.cassandra.service.consensus.migration.ConsensusMigratedAt;
import org.apache.cassandra.service.consensus.migration.ConsensusMigrationTarget;
import static org.apache.cassandra.service.paxos.Commit.latest;
import static org.apache.cassandra.service.snapshot.SnapshotOptions.systemSnapshot;
import static org.apache.cassandra.utils.CassandraVersion.NULL_VERSION;

View File

@ -246,7 +246,7 @@ public class CassandraStreamReceiver implements StreamReceiver
checkNotNull(minVersion, "Unable to determine minimum cluster version");
IAccordService accordService = AccordService.instance();
if (session.streamOperation().requiresBarrierTransaction()
&& accordService.isAccordManagedTable(cfs.getTableId())
&& cfs.metadata().isAccordEnabled()
&& CassandraVersion.CASSANDRA_5_0.compareTo(minVersion) >= 0)
accordService.postStreamReceivingBarrier(cfs, ranges);

View File

@ -33,7 +33,7 @@ import org.apache.cassandra.dht.AccordSplitter;
import org.apache.cassandra.dht.IPartitioner;
import org.apache.cassandra.service.accord.AccordService;
import org.apache.cassandra.service.accord.TokenRange;
import org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState.ConsensusMigrationRepairResult;
import org.apache.cassandra.service.consensus.migration.ConsensusMigrationRepairResult;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.Epoch;

View File

@ -50,7 +50,7 @@ import org.apache.cassandra.repair.asymmetric.ReduceHelper;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.SystemDistributedKeyspace;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState.ConsensusMigrationRepairResult;
import org.apache.cassandra.service.consensus.migration.ConsensusMigrationRepairResult;
import org.apache.cassandra.service.paxos.cleanup.PaxosCleanup;
import org.apache.cassandra.streaming.PreviewKind;
import org.apache.cassandra.tcm.ClusterMetadata;

View File

@ -19,7 +19,7 @@ package org.apache.cassandra.repair;
import java.util.List;
import org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState.ConsensusMigrationRepairResult;
import org.apache.cassandra.service.consensus.migration.ConsensusMigrationRepairResult;
/**
* RepairJob's result

View File

@ -59,7 +59,7 @@ import org.apache.cassandra.repair.messages.ValidationResponse;
import org.apache.cassandra.repair.state.SessionState;
import org.apache.cassandra.schema.SystemDistributedKeyspace;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState;
import org.apache.cassandra.service.consensus.migration.ConsensusTableMigration;
import org.apache.cassandra.streaming.PreviewKind;
import org.apache.cassandra.tracing.Tracing;
import org.apache.cassandra.utils.FBUtilities;
@ -353,7 +353,7 @@ public class RepairSession extends AsyncFuture<RepairSessionResult> implements I
new AccordRepairJob(this, cfname) :
new CassandraRepairJob(this, cfname);
// Repairs can drive forward progress for consensus migration so always check
job.addCallback(ConsensusTableMigrationState.completedRepairJobHandler);
job.addCallback(ConsensusTableMigration.completedRepairJobHandler);
state.register(job.state);
executor.execute(job);
jobs.add(job);

View File

@ -30,6 +30,7 @@ import java.util.Set;
import java.util.UUID;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import org.apache.cassandra.auth.AuthKeyspace;
import org.apache.cassandra.config.DatabaseDescriptor;
@ -79,10 +80,18 @@ public class DistributedSchema implements MetadataValue<DistributedSchema>
return new DistributedSchema(Keyspaces.of(DistributedMetadataLogKeyspace.initialMetadata(knownDatacenters)), Epoch.FIRST);
}
private static ImmutableMap<TableId, TableMetadata> keyspacesToTableMap(Keyspaces keyspaces)
{
ImmutableMap.Builder<TableId, TableMetadata> builder = ImmutableMap.builder();
keyspaces.forEach(ksm -> ksm.tablesAndViews().forEach(tbl -> builder.put(tbl.id, tbl)));
return builder.build();
}
private final Keyspaces keyspaces;
private final Epoch epoch;
private final UUID version;
private final Map<String, Keyspace> keyspaceInstances = new HashMap<>();
private final transient ImmutableMap<TableId, TableMetadata> tables;
public DistributedSchema(Keyspaces keyspaces)
{
@ -95,6 +104,7 @@ public class DistributedSchema implements MetadataValue<DistributedSchema>
this.keyspaces = keyspaces;
this.epoch = epoch;
this.version = new UUID(0, epoch.getEpoch());
this.tables = keyspacesToTableMap(keyspaces);
validate();
}
@ -120,6 +130,11 @@ public class DistributedSchema implements MetadataValue<DistributedSchema>
return keyspaces.get(keyspace).get();
}
public TableMetadata getTableMetadata(TableId id)
{
return tables.get(id);
}
public static DistributedSchema fromSystemTables(Keyspaces keyspaces, Set<String> knownDatacenters)
{
if (!keyspaces.containsKeyspace(SchemaConstants.METADATA_KEYSPACE_NAME))

View File

@ -361,6 +361,21 @@ public class TableMetadata implements SchemaElement
return false;
}
public boolean isAccordEnabled()
{
return params.transactionalMode.accordIsEnabled;
}
public boolean migratingFromAccord()
{
return params.transactionalMigrationFrom.migratingFromAccord();
}
public boolean requiresAccordSupport()
{
return isAccordEnabled() || migratingFromAccord();
}
public ImmutableCollection<ColumnMetadata> columns()
{
return columns.values();

View File

@ -33,6 +33,8 @@ import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.service.accord.fastpath.FastPathStrategy;
import org.apache.cassandra.service.consensus.TransactionalMode;
import org.apache.cassandra.service.consensus.migration.TransactionalMigrationFromMode;
import org.apache.cassandra.tcm.serialization.MetadataSerializer;
import org.apache.cassandra.tcm.serialization.Version;
import org.apache.cassandra.service.reads.PercentileSpeculativeRetryPolicy;
@ -71,7 +73,9 @@ public final class TableParams
CRC_CHECK_CHANCE,
CDC,
READ_REPAIR,
FAST_PATH;
FAST_PATH,
TRANSACTIONAL_MODE,
TRANSACTIONAL_MIGRATION_FROM;
@Override
public String toString()
@ -100,6 +104,8 @@ public final class TableParams
public final boolean cdc;
public final ReadRepairStrategy readRepair;
public final FastPathStrategy fastPath;
public final TransactionalMode transactionalMode;
public final TransactionalMigrationFromMode transactionalMigrationFrom;
private TableParams(Builder builder)
{
@ -125,6 +131,8 @@ public final class TableParams
cdc = builder.cdc;
readRepair = builder.readRepair;
fastPath = builder.fastPath;
transactionalMode = builder.transactionalMode != null ? builder.transactionalMode : TransactionalMode.off;
transactionalMigrationFrom = builder.transactionalMigrationFrom;
}
public static Builder builder()
@ -153,7 +161,9 @@ public final class TableParams
.extensions(params.extensions)
.cdc(params.cdc)
.readRepair(params.readRepair)
.fastPath(params.fastPath);
.fastPath(params.fastPath)
.transactionalMode(params.transactionalMode)
.transactionalMigrationFrom(params.transactionalMigrationFrom);
}
public Builder unbuild()
@ -245,7 +255,9 @@ public final class TableParams
&& extensions.equals(p.extensions)
&& cdc == p.cdc
&& readRepair == p.readRepair
&& fastPath.equals(fastPath);
&& fastPath.equals(fastPath)
&& transactionalMode == p.transactionalMode
&& transactionalMigrationFrom == p.transactionalMigrationFrom;
}
@Override
@ -270,7 +282,9 @@ public final class TableParams
extensions,
cdc,
readRepair,
fastPath);
fastPath,
transactionalMode,
transactionalMigrationFrom);
}
@Override
@ -298,6 +312,8 @@ public final class TableParams
.add(CDC.toString(), cdc)
.add(READ_REPAIR.toString(), readRepair)
.add(Option.FAST_PATH.toString(), fastPath)
.add(Option.TRANSACTIONAL_MODE.toString(), transactionalMode)
.add(Option.TRANSACTIONAL_MIGRATION_FROM.toString(), transactionalMigrationFrom)
.toString();
}
@ -348,8 +364,17 @@ public final class TableParams
.append("AND min_index_interval = ").append(minIndexInterval)
.newLine()
.append("AND read_repair = ").appendWithSingleQuotes(readRepair.toString())
.newLine()
.append("AND speculative_retry = ").appendWithSingleQuotes(speculativeRetry.toString());
.newLine();
if (!isView)
{
builder.append("AND transactional_mode = ").appendWithSingleQuotes(transactionalMode.toString())
.newLine()
.append("AND transactional_migration_from = ").appendWithSingleQuotes(transactionalMigrationFrom.toString())
.newLine();
}
builder.append("AND speculative_retry = ").appendWithSingleQuotes(speculativeRetry.toString());
}
public static final class Builder
@ -374,6 +399,8 @@ public final class TableParams
private boolean cdc;
private ReadRepairStrategy readRepair = ReadRepairStrategy.BLOCKING;
private FastPathStrategy fastPath = FastPathStrategy.inheritKeyspace();
private TransactionalMode transactionalMode = TransactionalMode.off;
public TransactionalMigrationFromMode transactionalMigrationFrom = TransactionalMigrationFromMode.none;
public Builder()
{
@ -498,6 +525,18 @@ public final class TableParams
return this;
}
public Builder transactionalMode(TransactionalMode val)
{
transactionalMode = val;
return this;
}
public Builder transactionalMigrationFrom(TransactionalMigrationFromMode val)
{
transactionalMigrationFrom = val;
return this;
}
public Builder extensions(Map<String, ByteBuffer> val)
{
extensions = ImmutableMap.copyOf(val);
@ -534,6 +573,8 @@ public final class TableParams
{
out.writeBoolean(t.allowAutoSnapshot);
out.writeBoolean(t.incrementalBackups);
out.writeInt(t.transactionalMode.ordinal());
out.writeInt(t.transactionalMigrationFrom.ordinal());
}
}
@ -559,7 +600,9 @@ public final class TableParams
.cdc(in.readBoolean())
.readRepair(ReadRepairStrategy.fromString(in.readUTF()))
.allowAutoSnapshot(!version.isAtLeast(Version.V4) || in.readBoolean())
.incrementalBackups(!version.isAtLeast(Version.V4) || in.readBoolean());
.incrementalBackups(!version.isAtLeast(Version.V4) || in.readBoolean())
.transactionalMode(version.isAtLeast(Version.V4) ? TransactionalMode.fromOrdinal(in.readInt()) : TransactionalMode.off)
.transactionalMigrationFrom(version.isAtLeast(Version.V4) ? TransactionalMigrationFromMode.fromOrdinal(in.readInt()) : TransactionalMigrationFromMode.off);
return builder.build();
}
@ -584,7 +627,9 @@ public final class TableParams
sizeof(t.cdc) +
sizeof(t.readRepair.name()) +
(version.isAtLeast(Version.V4) ? sizeof(t.allowAutoSnapshot) : 0) +
(version.isAtLeast(Version.V4) ? sizeof(t.incrementalBackups) : 0);
(version.isAtLeast(Version.V4) ? sizeof(t.incrementalBackups) : 0) +
(version.isAtLeast(Version.V4) ? sizeof(t.transactionalMode.ordinal()) : 0) +
(version.isAtLeast(Version.V4) ? sizeof(t.transactionalMigrationFrom.ordinal()) : 0);
}
private void serializeMap(Map<String, String> map, DataOutputPlus out) throws IOException

View File

@ -42,6 +42,7 @@ import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import com.google.common.base.Preconditions;
import com.google.common.cache.CacheLoader;
import com.google.common.collect.Iterables;
import com.google.common.util.concurrent.Uninterruptibles;
@ -57,7 +58,7 @@ import org.apache.cassandra.concurrent.DebuggableTask.RunnableDebuggableTask;
import org.apache.cassandra.concurrent.Stage;
import org.apache.cassandra.config.CassandraRelevantProperties;
import org.apache.cassandra.config.Config;
import org.apache.cassandra.config.Config.NonSerialWriteStrategy;
import org.apache.cassandra.service.consensus.TransactionalMode;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.ConsistencyLevel;
@ -131,7 +132,6 @@ import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.accord.AccordService;
import org.apache.cassandra.service.accord.IAccordService;
import org.apache.cassandra.service.accord.api.PartitionKey;
import org.apache.cassandra.service.accord.txn.AccordUpdate;
import org.apache.cassandra.service.accord.txn.TxnCondition;
import org.apache.cassandra.service.accord.txn.TxnData;
import org.apache.cassandra.service.accord.txn.TxnQuery;
@ -175,8 +175,6 @@ import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Iterables.concat;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static org.apache.cassandra.config.Config.NonSerialWriteStrategy.accord;
import static org.apache.cassandra.config.DatabaseDescriptor.getNonSerialWriteStrategy;
import static org.apache.cassandra.db.ConsistencyLevel.SERIAL;
import static org.apache.cassandra.metrics.ClientRequestsMetricsHolder.casReadMetrics;
import static org.apache.cassandra.metrics.ClientRequestsMetricsHolder.casWriteMetrics;
@ -383,7 +381,6 @@ public class StorageProxy implements StorageProxyMBean
clientState,
nowInSeconds);
IAccordService accordService = AccordService.instance();
accordService.maybeConvertTablesToAccord(txn);
TxnResult txnResult = accordService.coordinate(txn,
consistencyForPaxos,
requestTime);
@ -1187,6 +1184,54 @@ public class StorageProxy implements StorageProxyMBean
}
}
private static ConsistencyLevel consistencyLevelForCommit(Collection<? extends IMutation> mutations, ConsistencyLevel consistencyLevel)
{
ConsistencyLevel result = null;
for (IMutation mutation : mutations)
{
for (TableId tableId : mutation.getTableIds())
{
TransactionalMode mode = Schema.instance.getTableMetadata(tableId).params.transactionalMode;
ConsistencyLevel commitCL = mode.commitCLForStrategy(consistencyLevel);
if (result == null || commitCL.compareTo(result) > 0)
result = commitCL;
}
}
return result;
}
private static boolean writesThroughAccord(List<? extends IMutation> mutations, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime)
{
boolean accordWrite = false;
boolean normalWrite = false;
for (int i=0,mi=mutations.size(); i<mi; i++)
{
IMutation mutation = mutations.get(i);
for (TableId tableId : mutation.getTableIds())
{
if (ConsensusRequestRouter.instance.shouldWriteThroughAccordAndMaybeMigrate(mutation.key(),
tableId,
consistencyLevel,
requestTime,
DatabaseDescriptor.getWriteRpcTimeout(NANOSECONDS),
true))
{
accordWrite = true;
}
else
{
normalWrite = true;
}
}
}
if (!accordWrite && !normalWrite)
throw new IllegalStateException("Mutation is neither a normal write nor an accord write");
if (accordWrite && normalWrite)
throw new InvalidRequestException("Cannot mix writes to accord and normal tables");
return accordWrite;
}
@SuppressWarnings("unchecked")
public static void mutateWithTriggers(List<? extends IMutation> mutations,
ConsistencyLevel consistencyLevel,
@ -1220,12 +1265,15 @@ public class StorageProxy implements StorageProxyMBean
.viewManager
.updatesAffectView(mutations, true);
long size = IMutation.dataSize(mutations);
writeMetrics.mutationSize.update(size);
writeMetricsForLevel(consistencyLevel).mutationSize.update(size);
NonSerialWriteStrategy nonSerialWriteStrategy = getNonSerialWriteStrategy();
if (nonSerialWriteStrategy.writesThroughAccord && !SchemaConstants.getSystemKeyspaces().contains(keyspaceName))
mutateWithAccord(augmented != null ? augmented : mutations, consistencyLevel, requestTime, nonSerialWriteStrategy);
if (writesThroughAccord(mutations, consistencyLevel, requestTime))
{
Preconditions.checkState(!SchemaConstants.getSystemKeyspaces().contains(keyspaceName));
mutateWithAccord(augmented != null ? augmented : mutations, consistencyLevel, requestTime);
}
else if (augmented != null)
mutateAtomically(augmented, consistencyLevel, updatesView, requestTime);
else
@ -1237,12 +1285,12 @@ public class StorageProxy implements StorageProxyMBean
}
}
private static void mutateWithAccord(Collection<? extends IMutation> iMutations, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime, Config.NonSerialWriteStrategy nonSerialWriteStrategy)
private static void mutateWithAccord(Collection<? extends IMutation> mutations, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime)
{
int fragmentIndex = 0;
List<TxnWrite.Fragment> fragments = new ArrayList<>(iMutations.size());
List<PartitionKey> partitionKeys = new ArrayList<>(iMutations.size());
for (IMutation mutation : iMutations)
List<TxnWrite.Fragment> fragments = new ArrayList<>(mutations.size());
List<PartitionKey> partitionKeys = new ArrayList<>(mutations.size());
for (IMutation mutation : mutations)
{
for (PartitionUpdate update : mutation.getPartitionUpdates())
{
@ -1252,11 +1300,10 @@ public class StorageProxy implements StorageProxyMBean
}
}
// Potentially ignore commit consistency level if the strategy specifies accord and not migration
ConsistencyLevel clForCommit = nonSerialWriteStrategy.commitCLForStrategy(consistencyLevel);
AccordUpdate update = new TxnUpdate(fragments, TxnCondition.none(), clForCommit);
ConsistencyLevel clForCommit = consistencyLevelForCommit(mutations, consistencyLevel);
TxnUpdate update = new TxnUpdate(fragments, TxnCondition.none(), clForCommit);
Txn.InMemory txn = new Txn.InMemory(Keys.of(partitionKeys), TxnRead.EMPTY, TxnQuery.EMPTY, update);
IAccordService accordService = AccordService.instance();
accordService.maybeConvertTablesToAccord(txn);
accordService.coordinate(txn, consistencyLevel, requestTime);
}
@ -2002,13 +2049,12 @@ public class StorageProxy implements StorageProxyMBean
SinglePartitionReadCommand readCommand = group.queries.get(0);
// If the non-SERIAL write strategy is sending all writes through Accord there is no need to use the supplied consistency
// level since Accord will manage reading safely
NonSerialWriteStrategy nonSerialWriteStrategy = getNonSerialWriteStrategy();
consistencyLevel = nonSerialWriteStrategy.readCLForStrategy(consistencyLevel);
TransactionalMode transactionalMode = group.metadata().params.transactionalMode;
consistencyLevel = transactionalMode.readCLForStrategy(consistencyLevel);
TxnRead read = TxnRead.createSerialRead(readCommand, consistencyLevel);
Invariants.checkState(read.keys().size() == 1, "Ephemeral reads are only strict-serializable for single partition reads");
Txn txn = new Txn.InMemory(nonSerialWriteStrategy == accord ? EphemeralRead : Read, read.keys(), read, TxnQuery.ALL, null);
Txn txn = new Txn.InMemory(transactionalMode == TransactionalMode.full ? EphemeralRead : Read, read.keys(), read, TxnQuery.ALL, null);
IAccordService accordService = AccordService.instance();
accordService.maybeConvertTablesToAccord(txn);
TxnResult txnResult = accordService.coordinate(txn, consistencyLevel, requestTime);
if (txnResult.kind() == retry_new_protocol)
return RETRY_NEW_PROTOCOL;

View File

@ -168,9 +168,7 @@ import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.schema.TableMetadataRef;
import org.apache.cassandra.schema.ViewMetadata;
import org.apache.cassandra.service.accord.AccordService;
import org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState;
import org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState.ConsensusMigrationState;
import org.apache.cassandra.service.consensus.migration.ConsensusMigrationState;
import org.apache.cassandra.service.disk.usage.DiskUsageBroadcaster;
import org.apache.cassandra.service.paxos.Paxos;
import org.apache.cassandra.service.paxos.PaxosCommit;
@ -259,8 +257,8 @@ import static org.apache.cassandra.service.StorageService.Mode.JOINING_FAILED;
import static org.apache.cassandra.service.StorageService.Mode.LEAVING;
import static org.apache.cassandra.service.StorageService.Mode.MOVE_FAILED;
import static org.apache.cassandra.service.StorageService.Mode.NORMAL;
import static org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState.finishMigrationToConsensusProtocol;
import static org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState.startMigrationToConsensusProtocol;
import static org.apache.cassandra.service.consensus.migration.ConsensusTableMigration.finishMigrationToConsensusProtocol;
import static org.apache.cassandra.service.consensus.migration.ConsensusTableMigration.startMigrationToConsensusProtocol;
import static org.apache.cassandra.tcm.membership.NodeState.BOOTSTRAPPING;
import static org.apache.cassandra.tcm.membership.NodeState.BOOT_REPLACING;
import static org.apache.cassandra.tcm.membership.NodeState.JOINED;
@ -1696,18 +1694,6 @@ public class StorageService extends NotificationBroadcasterSupport implements IE
return finishMigrationToConsensusProtocol(keyspace, Optional.ofNullable(maybeTableNames), Optional.ofNullable(maybeRangesStr));
}
@Override
public void setConsensusMigrationTargetProtocol(@Nonnull String targetProtocol,
@Nullable List<String> keyspaceNames,
@Nullable List<String> maybeTableNames)
{
checkNotNull(targetProtocol, "targetProtocol is null");
checkNotNull(keyspaceNames, "keyspaceNames is null");
checkArgument(!keyspaceNames.contains(SchemaConstants.METADATA_KEYSPACE_NAME));
ConsensusTableMigrationState.setConsensusMigrationTargetProtocol(targetProtocol, keyspaceNames, Optional.ofNullable(maybeTableNames));
}
@Override
public String listConsensusMigrations(@Nullable Set<String> keyspaceNames,
@Nullable Set<String> tableNames,
@ -4235,7 +4221,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE
{
Keyspaces keyspaces = Schema.instance.getNonLocalStrategyKeyspaces();
return keyspaces.stream().flatMap(ks -> ks.tables.stream())
.filter(tbm -> AccordService.instance().isAccordManagedTable(tbm.id))
.filter(TableMetadata::requiresAccordSupport)
.map(tbm -> tbm.keyspace)
.distinct()
.sorted()
@ -4245,10 +4231,9 @@ public class StorageService extends NotificationBroadcasterSupport implements IE
@Override
public List<String> getAccordManagedTables()
{
// TODO (review) These are really just the ones Accord is aware of not necessarily managed
Keyspaces keyspaces = Schema.instance.getNonLocalStrategyKeyspaces();
return keyspaces.stream().flatMap(ks -> ks.tables.stream())
.filter(tbm -> AccordService.instance().isAccordManagedTable(tbm.id))
.filter(TableMetadata::requiresAccordSupport)
.map(tbm -> tbm.keyspace + '.' + tbm.name)
.collect(toList());
}

View File

@ -1151,10 +1151,6 @@ public interface StorageServiceMBean extends NotificationEmitter
@Nullable List<String> maybeTableNames,
@Nullable String maybeRangesStr);
void setConsensusMigrationTargetProtocol(@Nonnull String targetProtocol,
@Nullable List<String> keyspaceNames,
@Nullable List<String> maybeTableNames);
String listConsensusMigrations(@Nullable Set<String> keyspaceNames, @Nullable Set<String> tableNames, @Nonnull String format);
List<String> getAccordManagedKeyspaces();

View File

@ -37,7 +37,7 @@ import accord.impl.CoordinateDurabilityScheduling;
import org.apache.cassandra.cql3.statements.RequestValidations;
import org.apache.cassandra.service.accord.interop.AccordInteropAdapter.AccordInteropFactory;
import org.apache.cassandra.tcm.ClusterMetadataService;
import org.apache.cassandra.tcm.transformations.AddAccordTable;
import org.apache.cassandra.service.accord.api.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -74,7 +74,6 @@ import org.apache.cassandra.concurrent.Shutdownable;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.db.WriteType;
import org.apache.cassandra.exceptions.ExceptionCode;
import org.apache.cassandra.exceptions.ReadTimeoutException;
import org.apache.cassandra.exceptions.RequestTimeoutException;
import org.apache.cassandra.exceptions.WriteTimeoutException;
@ -83,23 +82,16 @@ import org.apache.cassandra.net.IVerbHandler;
import org.apache.cassandra.net.Message;
import org.apache.cassandra.net.MessageDelivery;
import org.apache.cassandra.net.MessagingService;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.service.accord.AccordSyncPropagator.Notification;
import org.apache.cassandra.service.accord.api.AccordAgent;
import org.apache.cassandra.service.accord.api.AccordRoutingKey.KeyspaceSplitter;
import org.apache.cassandra.service.accord.api.AccordScheduler;
import org.apache.cassandra.service.accord.api.AccordTopologySorter;
import org.apache.cassandra.service.accord.api.CompositeTopologySorter;
import org.apache.cassandra.service.accord.exceptions.ReadPreemptedException;
import org.apache.cassandra.service.accord.exceptions.WritePreemptedException;
import org.apache.cassandra.service.accord.txn.TxnResult;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.tcm.membership.NodeId;
import org.apache.cassandra.transport.Dispatcher;
import org.apache.cassandra.utils.Clock;
import org.apache.cassandra.utils.ExecutorUtils;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.Pair;
import org.apache.cassandra.utils.Throwables;
import org.apache.cassandra.utils.concurrent.AsyncPromise;
@ -206,20 +198,11 @@ public class AccordService implements IAccordService, Shutdownable
@Override
public void receive(Message<List<AccordSyncPropagator.Notification>> message) {}
@Override
public boolean isAccordManagedTable(TableId keyspace)
{
return false;
}
@Override
public Pair<Int2ObjectHashMap<RedundantBefore>, DurableBefore> getRedundantBeforesAndDurableBefore()
{
return Pair.create(new Int2ObjectHashMap<>(), DurableBefore.EMPTY);
}
@Override
public void ensureTableIsAccordManaged(TableId tableId) {}
};
private static volatile IAccordService instance = null;
@ -676,28 +659,6 @@ public class AccordService implements IAccordService, Shutdownable
return configService;
}
@Override
public boolean isAccordManagedTable(TableId tableId)
{
return ClusterMetadata.current().accordTables.contains(tableId);
}
@Override
public void ensureTableIsAccordManaged(TableId tableId)
{
if (isAccordManagedTable(tableId))
return;
ClusterMetadataService.instance().commit(new AddAccordTable(tableId),
metadata -> null,
(code, message) -> {
Invariants.checkState(code == ExceptionCode.ALREADY_EXISTS,
"Expected %s, got %s", ExceptionCode.ALREADY_EXISTS, code);
return null;
});
// we need to avoid creating a txnId in an epoch when no one has any ranges
FBUtilities.waitOnFuture(AccordService.instance().epochReady(ClusterMetadata.current().epoch));
}
@Override
public Pair<Int2ObjectHashMap<RedundantBefore>, DurableBefore> getRedundantBeforesAndDurableBefore()
{

View File

@ -19,17 +19,20 @@
package org.apache.cassandra.service.accord;
import java.util.*;
import java.util.function.Predicate;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import accord.primitives.Ranges;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import accord.local.Node;
import accord.topology.Shard;
import accord.topology.Topology;
import accord.utils.Invariants;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token;
@ -45,6 +48,9 @@ import org.apache.cassandra.tcm.membership.NodeId;
import org.apache.cassandra.tcm.ownership.DataPlacement;
import org.apache.cassandra.tcm.ownership.DataPlacements;
import org.apache.cassandra.tcm.ownership.VersionedEndpoints;
import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
/**
* Deterministically computes accord topology from a ClusterMetadata instance
@ -212,7 +218,7 @@ public class AccordTopology
return builder.build();
}
public static Topology createAccordTopology(Epoch epoch, DistributedSchema schema, DataPlacements placements, Directory directory, AccordFastPath accordFastPath, Predicate<TableId> tablePredicate, ShardLookup lookup)
public static Topology createAccordTopology(Epoch epoch, DistributedSchema schema, DataPlacements placements, Directory directory, AccordFastPath accordFastPath, ShardLookup lookup)
{
List<Shard> shards = new ArrayList<>();
Set<Node.Id> unavailable = accordFastPath.unavailableIds();
@ -220,7 +226,7 @@ public class AccordTopology
for (KeyspaceMetadata keyspace : schema.getKeyspaces())
{
List<TableMetadata> tables = keyspace.tables.stream().filter(tbl -> tablePredicate.test(tbl.id)).collect(Collectors.toList());
List<TableMetadata> tables = keyspace.tables.stream().filter(TableMetadata::requiresAccordSupport).collect(Collectors.toList());
if (tables.isEmpty())
continue;
List<KeyspaceShard> ksShards = KeyspaceShard.forKeyspace(keyspace, placements, directory, lookup);
@ -231,19 +237,14 @@ public class AccordTopology
return new Topology(epoch.getEpoch(), shards.toArray(new Shard[0]));
}
public static Topology createAccordTopology(ClusterMetadata metadata, Predicate<TableId> tablePredicate, ShardLookup lookup)
public static Topology createAccordTopology(ClusterMetadata metadata, ShardLookup lookup)
{
return createAccordTopology(metadata.epoch, metadata.schema, metadata.placements, metadata.directory, metadata.accordFastPath, tablePredicate, lookup);
}
public static Topology createAccordTopology(ClusterMetadata metadata, Predicate<TableId> tablePredicate)
{
return createAccordTopology(metadata, tablePredicate, new ShardLookup());
return createAccordTopology(metadata.epoch, metadata.schema, metadata.placements, metadata.directory, metadata.accordFastPath, lookup);
}
public static Topology createAccordTopology(ClusterMetadata metadata, Topology current)
{
return createAccordTopology(metadata, metadata.accordTables::contains, createShardLookup(current));
return createAccordTopology(metadata, createShardLookup(current));
}
public static Topology createAccordTopology(ClusterMetadata metadata)
@ -274,4 +275,59 @@ public class AccordTopology
topology.forEach(shard -> map.put(shard.range, shard));
return map;
}
private static boolean hasAccordSchemaChange(TableMetadata before, TableMetadata after)
{
return after.requiresAccordSupport() && (before == null || !before.requiresAccordSupport());
}
private static boolean hasAccordSchemaChange(TableMetadata created)
{
return hasAccordSchemaChange(null, created);
}
private static boolean hasAccordSchemaChange(Diff.Altered<TableMetadata> diff)
{
return hasAccordSchemaChange(diff.before, diff.after);
}
private static boolean hasAccordSchemaChange(Keyspaces.KeyspacesDiff keyspacesDiff)
{
for (KeyspaceMetadata.KeyspaceDiff keyspaceDiff : keyspacesDiff.altered)
{
if (Iterables.any(keyspaceDiff.tables.created, AccordTopology::hasAccordSchemaChange))
return true;
if (Iterables.any(keyspaceDiff.tables.altered, AccordTopology::hasAccordSchemaChange))
return true;
}
return false;
}
/**
* If an accord related schema change occurs, we need to wait until accord has processed them
* before unblocking the change
*/
public static void awaitTopologyReadiness(Keyspaces.KeyspacesDiff keyspacesDiff, Epoch epoch)
{
if (!AccordService.isSetup())
return;
if (!hasAccordSchemaChange(keyspacesDiff))
return;
try
{
AccordService.instance().epochReady(epoch).get(DatabaseDescriptor.getTransactionTimeout(MILLISECONDS), MILLISECONDS);
}
catch (InterruptedException e)
{
throw new UncheckedInterruptedException(e);
}
catch (ExecutionException | TimeoutException e)
{
throw new RuntimeException(e);
}
}
}

View File

@ -18,16 +18,6 @@
package org.apache.cassandra.service.accord;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import com.google.common.collect.ImmutableSet;
import accord.api.BarrierType;
import accord.local.DurableBefore;
import accord.local.Node.Id;
@ -37,30 +27,32 @@ import accord.primitives.Ranges;
import accord.primitives.Seekables;
import accord.primitives.Txn;
import accord.topology.TopologyManager;
import com.google.common.collect.ImmutableSet;
import org.agrona.collections.Int2ObjectHashMap;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.net.IVerbHandler;
import org.apache.cassandra.net.Message;
import org.apache.cassandra.service.accord.api.AccordRoutableKey;
import org.apache.cassandra.service.accord.api.AccordRoutingKey.TokenKey;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.service.accord.api.AccordScheduler;
import org.apache.cassandra.service.accord.txn.TxnResult;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.transport.Dispatcher;
import org.apache.cassandra.tcm.transformations.AddAccordTable;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.Pair;
import org.apache.cassandra.utils.concurrent.Future;
import javax.annotation.Nonnull;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
public interface IAccordService
{
Set<ConsistencyLevel> SUPPORTED_COMMIT_CONSISTENCY_LEVELS = ImmutableSet.of(ConsistencyLevel.ANY, ConsistencyLevel.ONE, ConsistencyLevel.QUORUM, ConsistencyLevel.SERIAL, ConsistencyLevel.ALL);
Set<ConsistencyLevel> SUPPORTED_COMMIT_CONSISTENCY_LEVELS = ImmutableSet.of(ConsistencyLevel.ANY, ConsistencyLevel.ONE, ConsistencyLevel.LOCAL_ONE, ConsistencyLevel.QUORUM, ConsistencyLevel.SERIAL, ConsistencyLevel.ALL);
Set<ConsistencyLevel> SUPPORTED_READ_CONSISTENCY_LEVELS = ImmutableSet.of(ConsistencyLevel.ONE, ConsistencyLevel.QUORUM, ConsistencyLevel.SERIAL);
IVerbHandler<? extends Request> verbHandler();
@ -112,52 +104,10 @@ public interface IAccordService
void receive(Message<List<AccordSyncPropagator.Notification>> message);
/**
* Temporary method to avoid double-streaming keyspaces
* @param tableId
* @return
*/
boolean isAccordManagedTable(TableId tableId);
/**
* Fetch the redundnant befores for every command store
*/
Pair<Int2ObjectHashMap<RedundantBefore>, DurableBefore> getRedundantBeforesAndDurableBefore();
default Id nodeId() { throw new UnsupportedOperationException(); }
default void maybeConvertTablesToAccord(Txn txn)
{
Set<TableId> allTables = new HashSet<>();
Set<TableId> newTables = new HashSet<>();
txn.keys().forEach(key -> {
TableId table = key instanceof AccordRoutableKey ? ((AccordRoutableKey) key).table() : ((TokenRange) key).table();
if (allTables.add(table) && !isAccordManagedTable(table))
newTables.add(table);
});
if (newTables.isEmpty())
return;
for (TableId table : newTables)
AddAccordTable.addTable(table);
// we need to avoid creating a txnId in an epoch when no one has any ranges
FBUtilities.waitOnFuture(epochReady(ClusterMetadata.current().epoch));
for (TableId table : allTables)
{
if (!isAccordManagedTable(table))
throw new IllegalStateException(table + " is not an accord managed table");
}
}
void ensureTableIsAccordManaged(TableId tableId);
default void ensureKeyspaceIsAccordManaged(String keyspace)
{
// TODO: remove when accord enabled is handled via schema
Keyspace ks = Keyspace.open(keyspace);
ks.getMetadata().tables.forEach(metadata -> ensureTableIsAccordManaged(metadata.id));
}
}

View File

@ -86,8 +86,8 @@ import org.apache.cassandra.service.accord.txn.TxnData;
import org.apache.cassandra.service.accord.txn.TxnRead;
import org.apache.cassandra.service.accord.txn.UnrecoverableRepairUpdate;
import org.apache.cassandra.service.consensus.migration.ConsensusRequestRouter;
import org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState;
import org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState.TableMigrationState;
import org.apache.cassandra.service.consensus.migration.ConsensusTableMigration;
import org.apache.cassandra.service.consensus.migration.TableMigrationState;
import org.apache.cassandra.service.reads.ReadCoordinator;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Dispatcher;
@ -262,9 +262,9 @@ public class AccordInteropExecution implements ReadCoordinator, MaximalCommitSen
// because they haven't yet updated their cluster metadata.
// It would be harmless to do the read, but we can respond faster skipping it
// and getting the transaction on the correct protocol
TableMigrationState tms = ConsensusTableMigrationState.getTableMigrationState(command.metadata().id);
TableMigrationState tms = ConsensusTableMigration.getTableMigrationState(command.metadata().id);
AccordClientRequestMetrics metrics = txn.kind().isWrite() ? accordWriteMetrics : accordReadMetrics;
if (ConsensusRequestRouter.instance.isKeyInMigratingOrMigratedRangeFromAccord(tms, command.partitionKey()))
if (ConsensusRequestRouter.instance.isKeyInMigratingOrMigratedRangeFromAccord(command.metadata(), tms, command.partitionKey()))
{
metrics.migrationSkippedReads.mark();
results.add(AsyncChains.success(TxnData.emptyPartition(fragment.txnDataName(), command)));

View File

@ -32,8 +32,6 @@ import accord.api.Update;
import accord.primitives.Seekables;
import accord.primitives.Timestamp;
import accord.primitives.TxnId;
import org.apache.cassandra.config.Config.LWTStrategy;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.EmptyIterators;
import org.apache.cassandra.db.SinglePartitionReadCommand;
import org.apache.cassandra.db.TypeSizes;
@ -154,10 +152,6 @@ public abstract class TxnQuery implements Query
Epoch epoch = Epoch.create(executeAt.epoch());
if (transactionIsInMigratingOrMigratedRange(epoch, keys))
{
// Fail fast because we can't be sure where this request should really run or what was intended
if (DatabaseDescriptor.getLWTStrategy() == LWTStrategy.accord)
throw new IllegalStateException("Mixing a hard coded strategy with migration is unsupported");
if (txnId.isWrite())
ClientRequestsMetricsHolder.accordWriteMetrics.accordMigrationRejects.mark();
else

View File

@ -0,0 +1,141 @@
/*
* 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.service.consensus;
import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.service.accord.IAccordService;
import org.apache.cassandra.utils.LocalizeString;
/*
* Configure the transactional behavior of a table. Enables accord on a table and defines how it mixes with non-serial writes
*
* For Accord transactions to function correctly when mixed with non-SERIAL writes it's necessary for the writes to occur through Accord.
*
* Accord will also use this configuration to determine what consistency level to perform its reads
* at since it will need to be able to read data written at non-SERIAL consistency levels.
*
* BlockingReadRepair will also use this configuration to determine how BRR mutations are applied. For migration
* and accord the BRR mutations will be applied as Accord transactions so that BRR doesn't expose Accord to
* uncommitted Accord data that is being RRed. This can occur when Accord has applied a transaction at some, but not
* all replica since Accord defaults to asynchronous commit.
*
* By routing repairs through Accord it is guaranteed that the Accord derived contents of the repair have already been applied at any
* replica where Accord applies the transaction. This also prevents BRR from breaking atomicity of Accord writes.
*
* If they are not written through Accord then reads through Accord will be required to occur at
* consistency level compatible with the non-serial writes preventing single replica reads from being performed
* by Accord. It will also require Accord to perform read repair of non-serial writes.
*
* Even then there is the potential for Accord to inconsistently execute transactions at different replicas
* because different coordinators for an Accord transaction may encounter different non-SERIAL write state and
* race to commit different outcomes for the transaction.
*
* This is different from Paxos because Paxos performs consensus on the actual values to be applied so recovery
* coordinators will always produce a consistent state when applying a transaction. Accord performs consensus on
* the execution order of transaction and different coordinators witnessing different states not managed by Accord
* can produce multiple outcomes for a transaction.
*
* // TODO to safely migrate you would have to route all writes through Accord with the current implementation
* // We could do it by range instead in the migration version, but then we need to know when all in flight writes
* // are done before marking a range as migrated. Would waiting out the timeout be enough (timeout bugs!)?
*/
public enum TransactionalMode
{
// Running on Paxos V1 or V2 with Accord disabled
off(false, false, false, false),
/*
* Execute writes through Cassandra via StorageProxy's normal write path. This can lead Accord to compute
* multiple outcomes for a transaction that depends on data written by non-SERIAL writes.
*/
unsafe(true, false, false, false),
/*
* Allow mixing of non-SERIAL writes and Accord, but still force BRR through Accord.
* This mode makes it safe to perform non-SERIAL or SERIAL reads of Accord data, but unsafe
* to write data that Accord may attempt to read.
*/
unsafe_writes(true, false, false, true),
/*
* Execute writes through Accord skipping StorageProxy's normal write path, but commit
* writes at the provided consistency level so they can be read via non-SERIAL consistency levels.
* This mode makes it safe to read/write data that Accord will read/write.
*/
mixed_reads(true, false, true, true),
/*
* Execute writes through Accord skipping StorageProxy's normal write path. Ignores the provided consistency level
* which makes Accord commit writes at ANY similar to Paxos with commit consistency level ANY.
*/
full(true, true, true, true);
public final boolean accordIsEnabled;
public final boolean ignoresSuppliedConsistencyLevel;
public final boolean writesThroughAccord;
public final boolean blockingReadRepairThroughAccord;
private final String cqlParam;
TransactionalMode(boolean accordIsEnabled, boolean ignoresSuppliedConsistencyLevel, boolean writesThroughAccord, boolean blockingReadRepairThroughAccord)
{
this.accordIsEnabled = accordIsEnabled;
this.ignoresSuppliedConsistencyLevel = ignoresSuppliedConsistencyLevel;
this.writesThroughAccord = writesThroughAccord;
this.blockingReadRepairThroughAccord = blockingReadRepairThroughAccord;
this.cqlParam = String.format("transactional_mode = '%s'", LocalizeString.toLowerCaseLocalized(this.name()));
}
public ConsistencyLevel commitCLForStrategy(ConsistencyLevel consistencyLevel)
{
if (ignoresSuppliedConsistencyLevel)
return null;
if (!IAccordService.SUPPORTED_COMMIT_CONSISTENCY_LEVELS.contains(consistencyLevel))
throw new UnsupportedOperationException("Consistency level " + consistencyLevel + " is unsupported with Accord for write/commit, supported are ANY, ONE, QUORUM, and ALL");
return consistencyLevel;
}
public ConsistencyLevel readCLForStrategy(ConsistencyLevel consistencyLevel)
{
if (ignoresSuppliedConsistencyLevel)
return null;
if (!IAccordService.SUPPORTED_READ_CONSISTENCY_LEVELS.contains(consistencyLevel))
throw new UnsupportedOperationException("Consistency level " + consistencyLevel + " is unsupported with Accord for read, supported are ONE, QUORUM, and SERIAL");
return consistencyLevel;
}
public String asCqlParam()
{
return cqlParam;
}
public static TransactionalMode fromOrdinal(int ordinal)
{
return values()[ordinal];
}
public static TransactionalMode fromString(String name)
{
return valueOf(LocalizeString.toLowerCaseLocalized(name));
}
}

View File

@ -57,7 +57,6 @@ import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.accord.AccordService;
import org.apache.cassandra.service.accord.api.PartitionKey;
import org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState.ConsensusMigratedAt;
import org.apache.cassandra.service.paxos.AbstractPaxosRepair.Failure;
import org.apache.cassandra.service.paxos.AbstractPaxosRepair.Result;
import org.apache.cassandra.service.paxos.PaxosRepair;
@ -70,9 +69,9 @@ import org.apache.cassandra.utils.Pair;
import org.apache.cassandra.utils.UUIDSerializer;
import static org.apache.cassandra.net.Verb.CONSENSUS_KEY_MIGRATION;
import static org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState.ConsensusMigrationTarget;
import static org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState.ConsensusMigrationTarget.paxos;
import static org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState.TableMigrationState;
import static org.apache.cassandra.service.consensus.migration.ConsensusMigrationTarget.paxos;
import static org.apache.cassandra.utils.Clock.Global.nanoTime;
/**

View File

@ -0,0 +1,70 @@
/*
* 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.service.consensus.migration;
import java.io.IOException;
import javax.annotation.Nullable;
import org.apache.cassandra.db.TypeSizes;
import org.apache.cassandra.io.IVersionedSerializer;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.utils.NullableSerializer;
public class ConsensusMigratedAt
{
public static final IVersionedSerializer<ConsensusMigratedAt> serializer = NullableSerializer.wrap(new IVersionedSerializer<ConsensusMigratedAt>()
{
@Override
public void serialize(ConsensusMigratedAt t, DataOutputPlus out, int version) throws IOException
{
Epoch.messageSerializer.serialize(t.migratedAtEpoch, out, version);
out.writeByte(t.migratedAtTarget.value);
}
@Override
public ConsensusMigratedAt deserialize(DataInputPlus in, int version) throws IOException
{
Epoch migratedAtEpoch = Epoch.messageSerializer.deserialize(in, version);
ConsensusMigrationTarget target = ConsensusMigrationTarget.fromValue(in.readByte());
return new ConsensusMigratedAt(migratedAtEpoch, target);
}
@Override
public long serializedSize(ConsensusMigratedAt t, int version)
{
return TypeSizes.sizeof(ConsensusMigrationTarget.accord.value)
+ Epoch.messageSerializer.serializedSize(t.migratedAtEpoch, version);
}
});
// Fields are not nullable when used for messaging
@Nullable
public final Epoch migratedAtEpoch;
@Nullable
public final ConsensusMigrationTarget migratedAtTarget;
public ConsensusMigratedAt(Epoch migratedAtEpoch, ConsensusMigrationTarget migratedAtTarget)
{
this.migratedAtEpoch = migratedAtEpoch;
this.migratedAtTarget = migratedAtTarget;
}
}

View File

@ -0,0 +1,50 @@
/*
* 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.service.consensus.migration;
import org.apache.cassandra.tcm.Epoch;
import static com.google.common.base.Preconditions.checkArgument;
public class ConsensusMigrationRepairResult
{
public final ConsensusMigrationRepairType type;
public final Epoch minEpoch;
private ConsensusMigrationRepairResult(ConsensusMigrationRepairType type, Epoch minEpoch)
{
this.type = type;
this.minEpoch = minEpoch;
}
public static ConsensusMigrationRepairResult fromCassandraRepair(Epoch minEpoch, boolean migrationEligibleRepair)
{
checkArgument(!migrationEligibleRepair || minEpoch.isAfter(Epoch.EMPTY), "Epoch should not be empty if Paxos and regular repairs were performed");
if (migrationEligibleRepair)
return new ConsensusMigrationRepairResult(ConsensusMigrationRepairType.paxos, minEpoch);
else
return new ConsensusMigrationRepairResult(ConsensusMigrationRepairType.ineligible, Epoch.EMPTY);
}
public static ConsensusMigrationRepairResult fromAccordRepair(Epoch minEpoch)
{
checkArgument(minEpoch.isAfter(Epoch.EMPTY), "Accord repairs should always occur at an Epoch");
return new ConsensusMigrationRepairResult(ConsensusMigrationRepairType.accord, minEpoch);
}
}

View File

@ -0,0 +1,57 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.service.consensus.migration;
import com.google.common.primitives.SignedBytes;
import org.apache.cassandra.utils.LocalizeString;
public enum ConsensusMigrationRepairType
{
ineligible(0),
paxos(1),
accord(2);
public final byte value;
ConsensusMigrationRepairType(int value)
{
this.value = SignedBytes.checkedCast(value);
}
public static ConsensusMigrationRepairType fromString(String repairType)
{
return ConsensusMigrationRepairType.valueOf(LocalizeString.toLowerCaseLocalized(repairType));
}
public static ConsensusMigrationRepairType fromValue(byte value)
{
switch (value)
{
default:
throw new IllegalArgumentException(value + " is not recognized");
case 0:
return ConsensusMigrationRepairType.ineligible;
case 1:
return ConsensusMigrationRepairType.paxos;
case 2:
return ConsensusMigrationRepairType.accord;
}
}
}

View File

@ -0,0 +1,256 @@
/*
* 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.service.consensus.migration;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.schema.DistributedSchema;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.tcm.MetadataValue;
import org.apache.cassandra.tcm.serialization.MetadataSerializer;
import org.apache.cassandra.tcm.serialization.Version;
import org.apache.cassandra.utils.PojoToString;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.apache.cassandra.utils.CollectionSerializers.deserializeMap;
import static org.apache.cassandra.utils.CollectionSerializers.newHashMap;
import static org.apache.cassandra.utils.CollectionSerializers.serializeMap;
import static org.apache.cassandra.utils.CollectionSerializers.serializedMapSize;
// TODO this will mostly go away once we can move TableMigrationState into the table schema
public class ConsensusMigrationState implements MetadataValue<ConsensusMigrationState>
{
public static ConsensusMigrationState EMPTY = new ConsensusMigrationState(Epoch.EMPTY, ImmutableMap.of());
@Nonnull
public final Map<TableId, TableMigrationState> tableStates;
public final Epoch lastModified;
public ConsensusMigrationState(@Nonnull Epoch lastModified, @Nonnull Map<TableId, TableMigrationState> tableStates)
{
checkNotNull(tableStates, "tableStates is null");
checkNotNull(lastModified, "lastModified is null");
this.lastModified = lastModified;
this.tableStates = ImmutableMap.copyOf(tableStates);
}
public Map<String, Object> toMap(@Nullable Set<String> keyspaceNames, @Nullable Set<String> tableNames)
{
return ImmutableMap.of("lastModifiedEpoch", lastModified.getEpoch(),
"tableStates", tableStatesAsMaps(keyspaceNames, tableNames),
"version", PojoToString.CURRENT_VERSION);
}
private List<Map<String, Object>> tableStatesAsMaps(@Nullable Set<String> keyspaceNames,
@Nullable Set<String> tableNames)
{
ImmutableList.Builder<Map<String, Object>> builder = ImmutableList.builder();
for (TableMigrationState tms : tableStates.values())
{
if (keyspaceNames != null && !keyspaceNames.contains(tms.keyspaceName))
continue;
if (tableNames != null && !tableNames.contains(tms.tableName))
continue;
builder.add(tms.toMap());
}
return builder.build();
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ConsensusMigrationState that = (ConsensusMigrationState) o;
return tableStates.equals(that.tableStates);
}
public ConsensusMigrationState withReversedMigrations(Map<TableId, TableMetadata> tables, Epoch epoch)
{
if (tables.isEmpty())
return this;
ImmutableMap.Builder<TableId, TableMigrationState> updated = ImmutableMap.builder();
tableStates.forEach((id, state) -> {
if (!tables.containsKey(id))
updated.put(id, state);
});
tables.values().forEach(metadata -> {
TableMigrationState state = tableStates.get(metadata.id);
if (state != null)
updated.put(metadata.id, state.reverseMigration(ConsensusMigrationTarget.fromTransactionalMode(metadata.params.transactionalMode), epoch));
});
return new ConsensusMigrationState(lastModified, updated.build());
}
private static void withRangesMigrating(Map<TableId, TableMigrationState> current, ImmutableMap.Builder<TableId, TableMigrationState> next, TableMetadata metadata, List<Range<Token>> ranges, boolean overwrite)
{
TableMigrationState tableState;
ConsensusMigrationTarget target = ConsensusMigrationTarget.fromTransactionalMode(metadata.params.transactionalMode);
if (!overwrite && current.containsKey(metadata.id))
{
tableState = current.get(metadata.id).withRangesMigrating(ranges, target);
}
else
{
tableState = new TableMigrationState(metadata.keyspace, metadata.name, metadata.id, target, ImmutableSet.of(), ImmutableMap.of(Epoch.EMPTY, ranges));
}
next.put(metadata.id, tableState);
}
private static void putUnchanged(Map<TableId, TableMigrationState> current, ImmutableMap.Builder<TableId, TableMigrationState> next, Set<TableId> changed)
{
current.forEach((id, migrationState) -> {
if (!changed.contains(id))
next.put(id, migrationState);
});
}
private static void putUnchanged(Map<TableId, TableMigrationState> current, ImmutableMap.Builder<TableId, TableMigrationState> next, Collection<TableMetadata> changed)
{
Set<TableId> changedIds = changed.stream().map(TableMetadata::id).collect(Collectors.toSet());
putUnchanged(current, next, changedIds);
}
public ConsensusMigrationState withRangesMigrating(Collection<TableMetadata> tables, List<Range<Token>> ranges, boolean overwrite)
{
ImmutableMap.Builder<TableId, TableMigrationState> updated = ImmutableMap.builder();
putUnchanged(tableStates, updated, tables);
tables.forEach(metadata -> withRangesMigrating(tableStates, updated, metadata, ranges, overwrite));
return new ConsensusMigrationState(lastModified, updated.build());
}
public ConsensusMigrationState withMigrationsCompletedFor(Collection<TableId> completed)
{
ImmutableMap.Builder<TableId, TableMigrationState> updated = ImmutableMap.builder();
putUnchanged(tableStates, updated, new HashSet<>(completed));
for (Map.Entry<TableId, TableMigrationState> entry : tableStates.entrySet())
{
if (completed.contains(entry.getKey()))
continue;
updated.put(entry);
}
return new ConsensusMigrationState(lastModified, updated.build());
}
public ConsensusMigrationState withRangesRepairedAtEpoch(TableMetadata metadata, List<Range<Token>> ranges, Epoch minEpoch)
{
TableMigrationState state = Preconditions.checkNotNull(tableStates.get(metadata.id));
state = state.withRangesRepairedAtEpoch(ranges, minEpoch);
if (state.hasMigratedFullTokenRange(metadata.partitioner))
{
return withMigrationsCompletedFor(Collections.singleton(metadata.id));
}
else
{
ImmutableMap.Builder<TableId, TableMigrationState> updated = ImmutableMap.builder();
putUnchanged(tableStates, updated, Collections.singleton(metadata.id));
updated.put(metadata.id, state);
return new ConsensusMigrationState(lastModified, updated.build());
}
}
public ConsensusMigrationState withMigrationsRemovedFor(Set<TableId> removed)
{
ImmutableMap.Builder<TableId, TableMigrationState> updated = ImmutableMap.builder();
putUnchanged(tableStates, updated, removed);
return new ConsensusMigrationState(lastModified, updated.build());
}
@Override
public int hashCode()
{
return Objects.hash(tableStates);
}
@Override
public ConsensusMigrationState withLastModified(Epoch epoch)
{
ImmutableMap.Builder<TableId, TableMigrationState> newMap = ImmutableMap.builderWithExpectedSize(tableStates.size());
tableStates.forEach((tableId, tableState) -> {
newMap.put(tableId, tableState.withReplacementForEmptyEpoch(epoch));
});
return new ConsensusMigrationState(epoch, newMap.build());
}
@Override
public Epoch lastModified()
{
return lastModified;
}
public void validateAgainstSchema(DistributedSchema schema)
{
tableStates.forEach((id, migrationState) -> {
TableMetadata metadata = schema.getTableMetadata(id);
Preconditions.checkState(ConsensusMigrationTarget.fromTransactionalMode(metadata.params.transactionalMode).equals(migrationState.targetProtocol));
});
}
public static final MetadataSerializer<ConsensusMigrationState> serializer = new MetadataSerializer<ConsensusMigrationState>()
{
@Override
public void serialize(ConsensusMigrationState consensusMigrationState, DataOutputPlus out, Version version) throws IOException
{
Epoch.serializer.serialize(consensusMigrationState.lastModified, out, version);
serializeMap(consensusMigrationState.tableStates, out, version, TableId.metadataSerializer, TableMigrationState.serializer);
}
@Override
public ConsensusMigrationState deserialize(DataInputPlus in, Version version) throws IOException
{
Epoch lastModified = Epoch.serializer.deserialize(in, version);
Map<TableId, TableMigrationState> tableMigrationStates = deserializeMap(in, version, TableId.metadataSerializer, TableMigrationState.serializer, newHashMap());
return new ConsensusMigrationState(lastModified, tableMigrationStates);
}
@Override
public long serializedSize(ConsensusMigrationState t, Version version)
{
return Epoch.serializer.serializedSize(t.lastModified, version)
+ serializedMapSize(t.tableStates, version, TableId.metadataSerializer, TableMigrationState.serializer);
}
};
}

View File

@ -0,0 +1,60 @@
/*
* 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.service.consensus.migration;
import com.google.common.primitives.SignedBytes;
import org.apache.cassandra.service.consensus.TransactionalMode;
import org.apache.cassandra.utils.LocalizeString;
public enum ConsensusMigrationTarget
{
paxos(0),
accord(1);
public final byte value;
ConsensusMigrationTarget(int value)
{
this.value = SignedBytes.checkedCast(value);
}
public static ConsensusMigrationTarget fromString(String targetProtocol)
{
return ConsensusMigrationTarget.valueOf(LocalizeString.toLowerCaseLocalized(targetProtocol));
}
public static ConsensusMigrationTarget fromValue(byte value)
{
switch (value)
{
default:
throw new IllegalArgumentException(value + " is not recognized");
case 0:
return paxos;
case 1:
return accord;
}
}
public static ConsensusMigrationTarget fromTransactionalMode(TransactionalMode mode)
{
return mode.accordIsEnabled ? accord : paxos;
}
}

View File

@ -22,16 +22,16 @@ import javax.annotation.Nonnull;
import com.google.common.annotations.VisibleForTesting;
import org.apache.cassandra.config.Config.LWTStrategy;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.locator.EndpointsForToken;
import org.apache.cassandra.locator.ReplicaLayout;
import org.apache.cassandra.schema.KeyspaceMetadata;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState.ConsensusMigratedAt;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.consensus.TransactionalMode;
import org.apache.cassandra.service.paxos.Paxos;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.ClusterMetadataService;
@ -44,9 +44,8 @@ import static org.apache.cassandra.service.consensus.migration.ConsensusKeyMigra
import static org.apache.cassandra.service.consensus.migration.ConsensusRequestRouter.ConsensusRoutingDecision.accord;
import static org.apache.cassandra.service.consensus.migration.ConsensusRequestRouter.ConsensusRoutingDecision.paxosV1;
import static org.apache.cassandra.service.consensus.migration.ConsensusRequestRouter.ConsensusRoutingDecision.paxosV2;
import static org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState.ConsensusMigrationTarget;
import static org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState.ConsensusMigrationTarget.paxos;
import static org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState.TableMigrationState;
import static org.apache.cassandra.service.consensus.migration.ConsensusMigrationTarget.paxos;
/**
* Helper class to decide where to route a request that requires consensus, migrating a key if necessary
@ -77,32 +76,85 @@ public class ConsensusRequestRouter
protected ConsensusRequestRouter() {}
public ConsensusRoutingDecision routeAndMaybeMigrate(@Nonnull DecoratedKey key, @Nonnull TableId tableId, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime, long timeoutNanos, boolean isForWrite)
ConsensusRoutingDecision decisionFor(TransactionalMode transactionalMode)
{
// In accord mode there might be migration state in CM (unless cleanup gets added), but it doesn't
// matter. All other consensus protocols are not used.
if (DatabaseDescriptor.getLWTStrategy() == LWTStrategy.accord)
if (transactionalMode.accordIsEnabled)
return accord;
ColumnFamilyStore cfs = ColumnFamilyStore.getIfExists(tableId);
if (cfs == null)
throw new IllegalStateException("Can't route consensus request for nonexistent table %s".format(tableId.toString()));
return routeAndMaybeMigrate(key, cfs, consistencyLevel, requestTime, timeoutNanos, isForWrite);
return pickPaxos();
}
protected ConsensusRoutingDecision routeAndMaybeMigrate(@Nonnull DecoratedKey key, @Nonnull ColumnFamilyStore cfs, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime, long timeoutNanos, boolean isForWrite)
private static TableMetadata metadata(ClusterMetadata cm, String keyspace, String table)
{
KeyspaceMetadata ksm = cm.schema.getKeyspaceMetadata(keyspace);
TableMetadata tbm = ksm != null ? ksm.getTableOrViewNullable(table) : null;
if (tbm == null)
throw new IllegalStateException("Can't route consensus request to nonexistent CFS %s.%s".format(keyspace, table));
return tbm;
}
public ConsensusRoutingDecision routeAndMaybeMigrate(@Nonnull DecoratedKey key, @Nonnull String keyspace, @Nonnull String table, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime, long timeoutNanos, boolean isForWrite)
{
ClusterMetadata cm = ClusterMetadata.current();
TableMetadata metadata = metadata(cm, keyspace, table);
return routeAndMaybeMigrate(cm, metadata, key, consistencyLevel, requestTime, timeoutNanos, isForWrite);
}
TableMigrationState tms = cm.consensusMigrationState.tableStates.get(cfs.getTableId());
public ConsensusRoutingDecision routeAndMaybeMigrate(@Nonnull DecoratedKey key, @Nonnull TableId tableId, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime, long timeoutNanos, boolean isForWrite)
{
ClusterMetadata cm = ClusterMetadata.current();
TableMetadata metadata = cm.schema.getTableMetadata(tableId);
if (metadata == null)
throw new IllegalStateException("Can't route consensus request for nonexistent table %s".format(tableId.toString()));
return routeAndMaybeMigrate(cm, metadata, key, consistencyLevel, requestTime, timeoutNanos, isForWrite);
}
protected static boolean mayWriteThroughAccord(TableMetadata metadata)
{
return metadata.params.transactionalMode.writesThroughAccord || metadata.params.transactionalMigrationFrom.writesThroughAccord();
}
public boolean shouldWriteThroughAccordAndMaybeMigrate(@Nonnull DecoratedKey key, @Nonnull TableId tableId, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime, long timeoutNanos, boolean isForWrite)
{
ClusterMetadata cm = ClusterMetadata.current();
TableMetadata metadata = cm.schema.getTableMetadata(tableId);
if (metadata == null)
throw new IllegalStateException("Can't route consensus request for nonexistent table %s".format(tableId.toString()));
if (!mayWriteThroughAccord(metadata))
return false;
consistencyLevel = consistencyLevel.isDatacenterLocal() ? ConsistencyLevel.LOCAL_SERIAL : ConsistencyLevel.SERIAL;
ConsensusRoutingDecision decision = routeAndMaybeMigrate(cm, metadata, key, consistencyLevel, requestTime, timeoutNanos, isForWrite);
switch (decision)
{
case paxosV1:
case paxosV2:
return false;
case accord:
return true;
default:
throw new IllegalStateException("Unsupported consensus " + decision);
}
}
protected ConsensusRoutingDecision routeAndMaybeMigrate(ClusterMetadata cm, @Nonnull TableMetadata tmd, @Nonnull DecoratedKey key, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime, long timeoutNanos, boolean isForWrite)
{
if (!tmd.params.transactionalMigrationFrom.isMigrating())
return decisionFor(tmd.params.transactionalMode);
TableMigrationState tms = cm.consensusMigrationState.tableStates.get(tmd.id);
if (tms == null)
return pickPaxos();
return decisionFor(tmd.params.transactionalMigrationFrom.from);
if (Range.isInNormalizedRanges(key.getToken(), tms.migratedRanges))
return pickMigrated(tms.targetProtocol);
if (Range.isInNormalizedRanges(key.getToken(), tms.migratingRanges))
return pickBasedOnKeyMigrationStatus(cm, tms, key, cfs, consistencyLevel, requestTime, timeoutNanos, isForWrite);
return pickBasedOnKeyMigrationStatus(cm, tmd, tms, key, consistencyLevel, requestTime, timeoutNanos, isForWrite);
// It's not migrated so infer the protocol from the target
return pickNotMigrated(tms.targetProtocol);
@ -112,10 +164,13 @@ public class ConsensusRequestRouter
* If the key was already migrated then we can pick the target protocol otherwise
* we have to run a repair operation on the key to migrate it.
*/
private static ConsensusRoutingDecision pickBasedOnKeyMigrationStatus(ClusterMetadata cm, TableMigrationState tms, DecoratedKey key, ColumnFamilyStore cfs, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime, long timeoutNanos, boolean isForWrite)
private static ConsensusRoutingDecision pickBasedOnKeyMigrationStatus(ClusterMetadata cm, TableMetadata tmd, TableMigrationState tms, DecoratedKey key, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime, long timeoutNanos, boolean isForWrite)
{
checkState(pickPaxos() != paxosV1, "Can't migrate from PaxosV1 to anything");
ColumnFamilyStore cfs = ColumnFamilyStore.getIfExists(tmd.id);
if (cfs == null)
throw new IllegalStateException("Can't route consensus request to nonexistent CFS %s.%s".format(tmd.keyspace, tmd.name));
// If it is locally replicated we can check our local migration state to see if it was already migrated
EndpointsForToken naturalReplicas = ReplicaLayout.forNonLocalStrategyTokenRead(cm, cfs.keyspace.getMetadata(), key.getToken());
boolean isLocallyReplicated = naturalReplicas.lookup(FBUtilities.getBroadcastAddressAndPort()) != null;
@ -192,15 +247,18 @@ public class ConsensusRequestRouter
{
ClusterMetadata cm = ClusterMetadataService.instance().fetchLogFromCMS(epoch);
TableMigrationState tms = cm.consensusMigrationState.tableStates.get(tableId);
return isKeyInMigratingOrMigratedRangeFromAccord(tms, key);
return isKeyInMigratingOrMigratedRangeFromAccord(cm.schema.getTableMetadata(tableId), tms, key);
}
/*
* A lightweight check against cluster metadata that doesn't check if the key has already been migrated
* using local system table state.
*/
public boolean isKeyInMigratingOrMigratedRangeFromAccord(TableMigrationState tms, DecoratedKey key)
public boolean isKeyInMigratingOrMigratedRangeFromAccord(TableMetadata metadata, TableMigrationState tms, DecoratedKey key)
{
if (!metadata.params.transactionalMigrationFrom.isMigrating())
return false;
// No state means no migration for this table
if (tms == null)
return false;

View File

@ -0,0 +1,337 @@
/*
* 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.service.consensus.migration;
import java.util.*;
import java.util.function.Predicate;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.FutureCallback;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.dht.IPartitioner;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.repair.RepairJobDesc;
import org.apache.cassandra.repair.RepairParallelism;
import org.apache.cassandra.repair.RepairResult;
import org.apache.cassandra.repair.messages.RepairOption;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.service.consensus.TransactionalMode;
import org.apache.cassandra.service.paxos.Paxos;
import org.apache.cassandra.streaming.PreviewKind;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.ClusterMetadataService;
import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.tcm.serialization.MetadataSerializer;
import org.apache.cassandra.tcm.transformations.BeginConsensusMigrationForTableAndRange;
import org.apache.cassandra.tcm.transformations.MaybeFinishConsensusMigrationForTableAndRange;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static java.util.Collections.emptyList;
import static org.apache.cassandra.dht.Range.normalize;
import static org.apache.cassandra.utils.CollectionSerializers.newListSerializer;
/**
* Track and update the migration state of individual table and ranges within those tables
*/
public abstract class ConsensusTableMigration
{
private static final Logger logger = LoggerFactory.getLogger(ConsensusTableMigration.class);
public static final MetadataSerializer<List<Range<Token>>> rangesSerializer = newListSerializer(Range.serializer);
public static final FutureCallback<RepairResult> completedRepairJobHandler = new FutureCallback<RepairResult>()
{
@Override
public void onSuccess(@Nullable RepairResult repairResult)
{
checkNotNull(repairResult, "repairResult should not be null");
ConsensusMigrationRepairResult migrationResult = repairResult.consensusMigrationRepairResult;
// Need to repair both Paxos and base table state
// Could track them separately, but doesn't seem worth the effort
if (migrationResult.type == ConsensusMigrationRepairType.ineligible)
return;
RepairJobDesc desc = repairResult.desc;
TableMetadata tm = Schema.instance.getTableMetadata(desc.keyspace, desc.columnFamily);
if (tm == null)
return;
TableMigrationState tms = ClusterMetadata.current().consensusMigrationState.tableStates.get(tm.id);
if (tms == null || !Range.intersects(tms.migratingRanges, desc.ranges))
return;
if (tms.targetProtocol == ConsensusMigrationTarget.paxos && repairResult.consensusMigrationRepairResult.type != ConsensusMigrationRepairType.accord)
return;
if (tms.targetProtocol == ConsensusMigrationTarget.accord && repairResult.consensusMigrationRepairResult.type != ConsensusMigrationRepairType.paxos)
return;
ClusterMetadataService.instance().commit(
new MaybeFinishConsensusMigrationForTableAndRange(
desc.keyspace, desc.columnFamily, ImmutableList.copyOf(desc.ranges),
migrationResult.minEpoch, migrationResult.type));
}
@Override
public void onFailure(Throwable throwable)
{
// Only successes drive forward progress
}
};
private ConsensusTableMigration() {}
public static @Nullable TableMigrationState getTableMigrationState(TableId tableId)
{
ClusterMetadata cm = ClusterMetadata.current();
return cm.consensusMigrationState.tableStates.get(tableId);
}
// Used by callers to avoid looking up the TMS multiple times
public static @Nullable TableMigrationState getTableMigrationState(long epoch, TableId tableId)
{
ClusterMetadata cm = ClusterMetadataService.instance().fetchLogFromCMS(Epoch.create(epoch));
return cm.consensusMigrationState.tableStates.get(tableId);
}
public static void startMigrationToConsensusProtocol(@Nonnull String targetProtocolName,
@Nullable List<String> keyspaceNames,
@Nonnull Optional<List<String>> maybeTables,
@Nonnull Optional<String> maybeRangesStr)
{
checkArgument(!maybeTables.isPresent() || !maybeTables.get().isEmpty(), "Must provide at least 1 table if Optional is not empty");
ConsensusMigrationTarget targetProtocol = ConsensusMigrationTarget.fromString(targetProtocolName);
if (keyspaceNames == null || keyspaceNames.isEmpty())
{
keyspaceNames = ImmutableList.copyOf(StorageService.instance.getNonLocalStrategyKeyspaces());
}
checkState(keyspaceNames.size() == 1 || !maybeTables.isPresent(), "Can't specify tables with multiple keyspaces");
List<TableId> ids = keyspacesAndTablesToTableIds(keyspaceNames, maybeTables);
// TODO (review): should this perform the schema change to make these tables accord tables?
List<TableId> tableIds = new ArrayList<>();
for (TableId tableId : ids)
{
TableMetadata metadata = Schema.instance.getTableMetadata(tableId);
if (metadata == null || !metadata.params.transactionalMigrationFrom.isMigrating())
continue;
TransactionalMode transactionalMode = metadata.params.transactionalMode;
if (!transactionalMode.writesThroughAccord && transactionalMode != TransactionalMode.unsafe_writes)
throw new IllegalStateException("non-SERIAL writes need to be routed through Accord before attempting migration, or enable mixed mode");
tableIds.add(tableId);
}
if (!Paxos.useV2())
throw new IllegalStateException("Can't do any consensus migrations to/from PaxosV1, switch to V2 first");
IPartitioner partitioner = DatabaseDescriptor.getPartitioner();
Optional<List<Range<Token>>> maybeParsedRanges = maybeRangesStr.map(rangesStr -> ImmutableList.copyOf(RepairOption.parseRanges(rangesStr, partitioner)));
Token minToken = partitioner.getMinimumToken();
List<Range<Token>> ranges = maybeParsedRanges.orElse(ImmutableList.of(new Range(minToken, minToken)));
ClusterMetadataService.instance().commit(new BeginConsensusMigrationForTableAndRange(targetProtocol, ranges, tableIds));
}
public static List<Integer> finishMigrationToConsensusProtocol(@Nonnull String keyspace,
@Nonnull Optional<List<String>> maybeTables,
@Nonnull Optional<String> maybeRangesStr)
{
checkArgument(!maybeTables.isPresent() || !maybeTables.get().isEmpty(), "Must provide at least 1 table if Optional is not empty");
Optional<List<Range<Token>>> localKeyspaceRanges = Optional.of(ImmutableList.copyOf(StorageService.instance.getLocalReplicas(keyspace).onlyFull().ranges()));
List<Range<Token>> ranges = maybeRangesToRanges(maybeRangesStr, localKeyspaceRanges);
Map<TableId, TableMigrationState> allTableMigrationStates = ClusterMetadata.current().consensusMigrationState.tableStates;
List<TableId> tableIds = keyspacesAndTablesToTableIds(ImmutableList.of(keyspace), maybeTables, Optional.of(allTableMigrationStates::containsKey));
checkState(tableIds.stream().allMatch(allTableMigrationStates::containsKey), "All tables need to be migrating");
List<TableMigrationState> tableMigrationStates = new ArrayList<>();
tableIds.forEach(table -> {
ColumnFamilyStore cfs = ColumnFamilyStore.getIfExists(table);
if (cfs == null)
{
logger.warn("Table {} does not exist or was dropped", cfs);
return;
}
TableMigrationState tms = allTableMigrationStates.get(table);
if (tms == null)
{
logger.warn("Table {} does not have any migration state", cfs.name);
return;
}
if(!Range.intersects(ranges, tms.migratingRanges))
{
logger.warn("Table {} with migrating ranges {} does not intersect with any requested ranges {}", cfs.name, tms.migratingRanges, ranges);
return;
}
tableMigrationStates.add(tms);
});
List<TableMigrationState> migratingToAccord = tableMigrationStates.stream().filter(tms -> tms.targetProtocol == ConsensusMigrationTarget.accord).collect(toImmutableList());
List<TableMigrationState> migratingToPaxos = tableMigrationStates.stream().filter(tms -> tms.targetProtocol == ConsensusMigrationTarget.paxos).collect(toImmutableList());;
Integer accordRepairCmd = finishMigrationToAccord(keyspace, migratingToAccord, ranges);
Integer paxosRepairCmd = finishMigrationToPaxos(keyspace, migratingToPaxos, ranges);
List<Integer> result = new ArrayList<>();
if (accordRepairCmd != null)
result.add(accordRepairCmd);
if (paxosRepairCmd != null)
result.add(paxosRepairCmd);
return result;
}
private interface MigrationFinisher
{
Integer finish(Collection<TableMigrationState> tables, List<Range<Token>> ranges);
}
private static Integer finishMigrationTo(String name, List<TableMigrationState> tableMigrationStates, List<Range<Token>> requestedRanges, MigrationFinisher migrationFinisher)
{
logger.info("Begin finish migration to {} for ranges {} and tables {}", name, requestedRanges, tableMigrationStates);
List<Range<Token>> intersectingRanges = new ArrayList<>();
tableMigrationStates.stream().map(TableMigrationState::migratingRanges).forEach(intersectingRanges::addAll);
intersectingRanges = Range.normalize(intersectingRanges);
intersectingRanges = Range.intersectionOfNormalizedRanges(intersectingRanges, requestedRanges);
if (intersectingRanges.isEmpty())
{
logger.warn("No requested ranges {} intersect any migrating ranges in any table in keyspace {}");
return null;
}
// Repair requires that the ranges once again be grouped by the ranges provided originally which all
// fall within local range boundaries. This was already checked in maybeRangesToRanges.
List<Range<Token>> intersectingRangesGrouped = new ArrayList<>();
for (Range<Token> r : requestedRanges)
{
List<Range<Token>> intersectionsForGroup = new ArrayList<>();
for (Range<Token> intersectedRange : intersectingRanges)
intersectionsForGroup.addAll(r.intersectionWith(intersectedRange));
intersectingRangesGrouped.addAll(normalize(intersectionsForGroup));
}
return migrationFinisher.finish(tableMigrationStates, intersectingRangesGrouped);
}
/*
* This is basically just invoking classic Cassandra repair and is pretty redundant with invoking repair
* directly which would also work without issue. It's include so the same interface works for both migrating to/from
* Accord, but it's not great in that repair has a lot of options that might need to be forwarded.
*
* Still maybe more valuable to put this layer of abstraction in so we can change how it works later and it's less
* tightly coupled with the Repair interface which is pretty orthogonal to consensus migration.
*/
private static Integer finishMigrationToAccord(String keyspace, List<TableMigrationState> migratingToAccord, List<Range<Token>> requestedRanges)
{
return finishMigrationTo("Accord", migratingToAccord, requestedRanges, (tables, intersectingRanges) -> {
RepairOption repairOption = getRepairOption(tables, intersectingRanges, false);
return StorageService.instance.repair(keyspace, repairOption, emptyList()).left;
});
}
private static Integer finishMigrationToPaxos(String keyspace, List<TableMigrationState> migratingToPaxos, List<Range<Token>> requestedRanges)
{
return finishMigrationTo("Paxos", migratingToPaxos, requestedRanges, (tables, intersectingRanges) -> {
RepairOption repairOption = getRepairOption(tables, intersectingRanges, true);
return StorageService.instance.repair(keyspace, repairOption, emptyList()).left;
});
}
private static List<TableId> keyspacesAndTablesToTableIds(@Nonnull List<String> keyspaceNames, @Nonnull Optional<List<String>> maybeTables)
{
return keyspacesAndTablesToTableIds(keyspaceNames, maybeTables, Optional.empty());
}
private static List<TableId> keyspacesAndTablesToTableIds(@Nonnull List<String> keyspaceNames, @Nonnull Optional<List<String>> maybeTables, @Nonnull Optional<Predicate<TableId>> includeTable)
{
List<TableId> tableIds = new ArrayList<>();
for (String keyspaceName : keyspaceNames)
{
Optional<Collection<TableId>> maybeTableIds = maybeTables.map(tableNames ->
tableNames
.stream()
.map(tableName -> {
TableMetadata tm = Schema.instance.getTableMetadata(keyspaceName, tableName);
if (tm == null)
throw new IllegalArgumentException("Unknown table %s.%s".format(keyspaceName, tableName));
return tm.id;
})
.collect(toImmutableList()));
tableIds.addAll(
maybeTableIds.orElseGet(() ->
Schema.instance.getKeyspaceInstance(keyspaceName).getColumnFamilyStores()
.stream()
.map(ColumnFamilyStore::getTableId)
.filter(includeTable.orElse(Predicates.alwaysTrue())) // Filter out non-migrating so they don't generate an error
.collect(toImmutableList())));
}
return tableIds;
}
@Nonnull
private static RepairOption getRepairOption(Collection<TableMigrationState> tables, List<Range<Token>> intersectingRanges, boolean accordRepair)
{
boolean primaryRange = false;
// TODO (review): Should disabling incremental repair be exposed for the Paxos repair in case someone explicitly does not do incremental repair?
boolean incremental = !accordRepair;
boolean trace = false;
int numJobThreads = 1;
boolean pullRepair = false;
boolean forceRepair = false;
boolean optimiseStreams = false;
boolean ignoreUnreplicatedKeyspaces = true;
boolean repairPaxos = !accordRepair;
boolean paxosOnly = false;
boolean dontPurgeTombstones = false;
RepairOption repairOption = new RepairOption(RepairParallelism.PARALLEL, primaryRange, incremental, trace, numJobThreads, intersectingRanges, pullRepair, forceRepair, PreviewKind.NONE, optimiseStreams, ignoreUnreplicatedKeyspaces, repairPaxos, paxosOnly, dontPurgeTombstones, accordRepair);
tables.forEach(table -> repairOption.getColumnFamilies().add(table.tableName));
return repairOption;
}
// Repair is restricted to local ranges, but manipulating CMS migration state doesn't need to be restricted
private static @Nonnull List<Range<Token>> maybeRangesToRanges(@Nonnull Optional<String> maybeRangesStr)
{
return maybeRangesToRanges(maybeRangesStr, Optional.empty());
}
private static @Nonnull List<Range<Token>> maybeRangesToRanges(@Nonnull Optional<String> maybeRangesStr, Optional<List<Range<Token>>> restrictToRanges)
{
IPartitioner partitioner = DatabaseDescriptor.getPartitioner();
Optional<List<Range<Token>>> maybeParsedRanges = maybeRangesStr.map(rangesStr -> ImmutableList.copyOf(RepairOption.parseRanges(rangesStr, partitioner)));
Token minToken = partitioner.getMinimumToken();
List<Range<Token>> defaultRanges = restrictToRanges.orElse(ImmutableList.of(new Range(minToken, minToken)));
List<Range<Token>> ranges = maybeParsedRanges.orElse(defaultRanges);
checkArgument(ranges.stream().allMatch(range -> defaultRanges.stream().anyMatch(defaultRange -> defaultRange.contains(range))),
"If ranges are specified each range must be contained within a local range (" + defaultRanges + ") for this node to allow for precise repairs. Specified " + ranges);
return ranges;
}
}

View File

@ -1,909 +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.service.consensus.migration;
import java.io.IOException;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.primitives.SignedBytes;
import com.google.common.util.concurrent.FutureCallback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.config.Config.LWTStrategy;
import org.apache.cassandra.config.Config.NonSerialWriteStrategy;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.TypeSizes;
import org.apache.cassandra.dht.IPartitioner;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.io.IVersionedSerializer;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.repair.RepairJobDesc;
import org.apache.cassandra.repair.RepairParallelism;
import org.apache.cassandra.repair.RepairResult;
import org.apache.cassandra.repair.messages.RepairOption;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.service.paxos.Paxos;
import org.apache.cassandra.streaming.PreviewKind;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.ClusterMetadataService;
import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.tcm.MetadataValue;
import org.apache.cassandra.tcm.serialization.MetadataSerializer;
import org.apache.cassandra.tcm.serialization.Version;
import org.apache.cassandra.tcm.transformations.BeginConsensusMigrationForTableAndRange;
import org.apache.cassandra.tcm.transformations.MaybeFinishConsensusMigrationForTableAndRange;
import org.apache.cassandra.tcm.transformations.SetConsensusMigrationTargetProtocol;
import org.apache.cassandra.utils.LocalizeString;
import org.apache.cassandra.utils.NullableSerializer;
import org.apache.cassandra.utils.PojoToString;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static java.util.Collections.emptyList;
import static org.apache.cassandra.db.TypeSizes.sizeof;
import static org.apache.cassandra.dht.Range.intersectionOfNormalizedRanges;
import static org.apache.cassandra.dht.Range.normalize;
import static org.apache.cassandra.dht.Range.subtract;
import static org.apache.cassandra.dht.Range.subtractNormalizedRanges;
import static org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState.ConsensusMigrationTarget.reset;
import static org.apache.cassandra.utils.CollectionSerializers.deserializeMap;
import static org.apache.cassandra.utils.CollectionSerializers.deserializeSet;
import static org.apache.cassandra.utils.CollectionSerializers.newHashMap;
import static org.apache.cassandra.utils.CollectionSerializers.newListSerializer;
import static org.apache.cassandra.utils.CollectionSerializers.serializeCollection;
import static org.apache.cassandra.utils.CollectionSerializers.serializeMap;
import static org.apache.cassandra.utils.CollectionSerializers.serializedCollectionSize;
import static org.apache.cassandra.utils.CollectionSerializers.serializedMapSize;
/**
* Track and update the migration state of individual table and ranges within those tables
*/
public abstract class ConsensusTableMigrationState
{
private static final Logger logger = LoggerFactory.getLogger(ConsensusTableMigrationState.class);
public static final MetadataSerializer<List<Range<Token>>> rangesSerializer = newListSerializer(Range.serializer);
public static final FutureCallback<RepairResult> completedRepairJobHandler = new FutureCallback<RepairResult>()
{
@Override
public void onSuccess(@Nullable RepairResult repairResult)
{
checkNotNull(repairResult, "repairResult should not be null");
ConsensusMigrationRepairResult migrationResult = repairResult.consensusMigrationRepairResult;
// Need to repair both Paxos and base table state
// Could track them separately, but doesn't seem worth the effort
if (migrationResult.type == ConsensusMigrationRepairType.ineligible)
return;
RepairJobDesc desc = repairResult.desc;
TableMetadata tm = Schema.instance.getTableMetadata(desc.keyspace, desc.columnFamily);
if (tm == null)
return;
TableMigrationState tms = ClusterMetadata.current().consensusMigrationState.tableStates.get(tm.id);
if (tms == null || !Range.intersects(tms.migratingRanges, desc.ranges))
return;
if (tms.targetProtocol == ConsensusMigrationTarget.paxos && repairResult.consensusMigrationRepairResult.type != ConsensusMigrationRepairType.accord)
return;
if (tms.targetProtocol == ConsensusMigrationTarget.accord && repairResult.consensusMigrationRepairResult.type != ConsensusMigrationRepairType.paxos)
return;
logger.info("Repair {} is going to trigger migration completion for ranges {} and epoch {}", desc.sessionId, desc.ranges, migrationResult.minEpoch);
ClusterMetadataService.instance().commit(
new MaybeFinishConsensusMigrationForTableAndRange(
desc.keyspace, desc.columnFamily, ImmutableList.copyOf(desc.ranges),
migrationResult.minEpoch, migrationResult.type));
}
@Override
public void onFailure(Throwable throwable)
{
// Only successes drive forward progress
}
};
public static void reset()
{
ClusterMetadata cm = ClusterMetadata.current();
for (TableMigrationState tms : cm.consensusMigrationState.tableStates.values())
setConsensusMigrationTargetProtocol("reset",
ImmutableList.of(tms.keyspaceName),
Optional.of(ImmutableList.of(tms.tableName)));
}
public enum ConsensusMigrationRepairType
{
ineligible(0),
paxos(1),
accord(2);
public final byte value;
ConsensusMigrationRepairType(int value)
{
this.value = SignedBytes.checkedCast(value);
}
public static ConsensusMigrationRepairType fromString(String repairType)
{
return ConsensusMigrationRepairType.valueOf(LocalizeString.toLowerCaseLocalized(repairType));
}
public static ConsensusMigrationRepairType fromValue(byte value)
{
switch (value)
{
default:
throw new IllegalArgumentException(value + " is not recognized");
case 0:
return ConsensusMigrationRepairType.ineligible;
case 1:
return ConsensusMigrationRepairType.paxos;
case 2:
return ConsensusMigrationRepairType.accord;
}
}
}
public enum ConsensusMigrationTarget
{
paxos(0),
accord(1),
reset(2);
public final byte value;
ConsensusMigrationTarget(int value)
{
this.value = SignedBytes.checkedCast(value);
}
public static ConsensusMigrationTarget fromString(String targetProtocol)
{
return ConsensusMigrationTarget.valueOf(LocalizeString.toLowerCaseLocalized(targetProtocol));
}
public static ConsensusMigrationTarget fromValue(byte value)
{
switch (value)
{
default:
throw new IllegalArgumentException(value + " is not recognized");
case 0:
return paxos;
case 1:
return accord;
case 2:
return reset;
}
}
}
public static class ConsensusMigrationRepairResult
{
private final ConsensusMigrationRepairType type;
private final Epoch minEpoch;
private ConsensusMigrationRepairResult(ConsensusMigrationRepairType type, Epoch minEpoch)
{
this.type = type;
this.minEpoch = minEpoch;
}
public static ConsensusMigrationRepairResult fromCassandraRepair(Epoch minEpoch, boolean migrationEligibleRepair)
{
checkArgument(!migrationEligibleRepair || minEpoch.isAfter(Epoch.EMPTY), "Epoch should not be empty if Paxos and regular repairs were performed");
if (migrationEligibleRepair)
return new ConsensusMigrationRepairResult(ConsensusMigrationRepairType.paxos, minEpoch);
else
return new ConsensusMigrationRepairResult(ConsensusMigrationRepairType.ineligible, Epoch.EMPTY);
}
public static ConsensusMigrationRepairResult fromAccordRepair(Epoch minEpoch)
{
checkArgument(minEpoch.isAfter(Epoch.EMPTY), "Accord repairs should always occur at an Epoch");
return new ConsensusMigrationRepairResult(ConsensusMigrationRepairType.accord, minEpoch);
}
}
public static class ConsensusMigratedAt
{
public static final IVersionedSerializer<ConsensusMigratedAt> serializer = NullableSerializer.wrap(new IVersionedSerializer<ConsensusMigratedAt>()
{
@Override
public void serialize(ConsensusMigratedAt t, DataOutputPlus out, int version) throws IOException
{
Epoch.messageSerializer.serialize(t.migratedAtEpoch, out, version);
out.writeByte(t.migratedAtTarget.value);
}
@Override
public ConsensusMigratedAt deserialize(DataInputPlus in, int version) throws IOException
{
Epoch migratedAtEpoch = Epoch.messageSerializer.deserialize(in, version);
ConsensusMigrationTarget target = ConsensusMigrationTarget.fromValue(in.readByte());
return new ConsensusMigratedAt(migratedAtEpoch, target);
}
@Override
public long serializedSize(ConsensusMigratedAt t, int version)
{
return TypeSizes.sizeof(ConsensusMigrationTarget.accord.value)
+ Epoch.messageSerializer.serializedSize(t.migratedAtEpoch, version);
}
});
// Fields are not nullable when used for messaging
@Nullable
public final Epoch migratedAtEpoch;
@Nullable
public final ConsensusMigrationTarget migratedAtTarget;
public ConsensusMigratedAt(Epoch migratedAtEpoch, ConsensusMigrationTarget migratedAtTarget)
{
this.migratedAtEpoch = migratedAtEpoch;
this.migratedAtTarget = migratedAtTarget;
}
}
// TODO (desired): Move this into the schema for the table once this is based off of TrM
public static class TableMigrationState
{
@Nonnull
public final String keyspaceName;
@Nonnull
public final String tableName;
@Nonnull
public final TableId tableId;
@Nonnull
public final ConsensusMigrationTarget targetProtocol;
@Nonnull
public final List<Range<Token>> migratedRanges;
/*
* Necessary to track which ranges started migrating at which epoch
* in order to know whether a repair qualifies in terms of finishing
* migration of the range.
*/
@Nonnull
public final NavigableMap<Epoch, List<Range<Token>>> migratingRangesByEpoch;
public static final MetadataSerializer<TableMigrationState> serializer = new MetadataSerializer<TableMigrationState>()
{
@Override
public void serialize(TableMigrationState t, DataOutputPlus out, Version version) throws IOException
{
out.write(t.targetProtocol.value);
out.writeUTF(t.keyspaceName);
out.writeUTF(t.tableName);
t.tableId.serialize(out);
serializeCollection(t.migratedRanges, out, version, Range.serializer);
serializeMap(t.migratingRangesByEpoch, out, version, Epoch.serializer, rangesSerializer);
}
@Override
public TableMigrationState deserialize(DataInputPlus in, Version version) throws IOException
{
ConsensusMigrationTarget targetProtocol = ConsensusMigrationTarget.fromValue(in.readByte());
String keyspaceName = in.readUTF();
String tableName = in.readUTF();
TableId tableId = TableId.deserialize(in);
Set<Range<Token>> migratedRanges = deserializeSet(in, version, Range.serializer);
Map<Epoch, List<Range<Token>>> migratingRangesByEpoch = deserializeMap(in, version, Epoch.serializer, rangesSerializer, newHashMap());
return new TableMigrationState(keyspaceName, tableName, tableId, targetProtocol, migratedRanges, migratingRangesByEpoch);
}
@Override
public long serializedSize(TableMigrationState t, Version version)
{
return sizeof(t.targetProtocol.value)
+ sizeof(t.keyspaceName)
+ sizeof(t.tableName)
+ t.tableId.serializedSize()
+ serializedCollectionSize(t.migratedRanges, version, Range.serializer)
+ serializedMapSize(t.migratingRangesByEpoch, version, Epoch.serializer, rangesSerializer);
}
};
@Nonnull
public final List<Range<Token>> migratingRanges;
@Nonnull
public final List<Range<Token>> migratingAndMigratedRanges;
public TableMigrationState(@Nonnull String keyspaceName,
@Nonnull String tableName,
@Nonnull TableId tableId,
@Nonnull ConsensusMigrationTarget targetProtocol,
@Nonnull Collection<Range<Token>> migratedRanges,
@Nonnull Map<Epoch, List<Range<Token>>> migratingRangesByEpoch)
{
this.keyspaceName = keyspaceName;
this.tableName = tableName;
this.tableId = tableId;
this.targetProtocol = targetProtocol;
this.migratedRanges = ImmutableList.copyOf(normalize(migratedRanges));
this.migratingRangesByEpoch = ImmutableSortedMap.copyOf(
migratingRangesByEpoch.entrySet()
.stream()
.map( entry -> new SimpleEntry<>(entry.getKey(), ImmutableList.copyOf(normalize(entry.getValue()))))
.collect(Collectors.toList()));
this.migratingRanges = ImmutableList.copyOf(normalize(migratingRangesByEpoch.values().stream().flatMap(Collection::stream).collect(Collectors.toList())));
this.migratingAndMigratedRanges = ImmutableList.copyOf(normalize(ImmutableList.<Range<Token>>builder().addAll(migratedRanges).addAll(migratingRanges).build()));
}
public TableMigrationState withRangesMigrating(@Nonnull Collection<Range<Token>> ranges,
@Nonnull ConsensusMigrationTarget target)
{
checkState(!migratingRangesByEpoch.containsKey(Epoch.EMPTY), "Shouldn't already have an entry for the empty epoch");
// Doesn't matter which epoch the range started migrating in for this context so merge them all
Collection<Range<Token>> migratingRanges = normalize(migratingRangesByEpoch.values().stream().flatMap(Collection::stream).collect(Collectors.toList()));
checkArgument(target == targetProtocol, "Requested migration to target protocol " + target + " conflicts with in progress migration to protocol " + targetProtocol);
List<Range<Token>> normalizedRanges = normalize(ranges);
if (subtract(normalizedRanges, migratingRanges).isEmpty())
logger.warn("Range " + ranges + " is already being migrated");
Set<Range<Token>> withoutAlreadyMigrated = subtract(normalizedRanges, migratedRanges);
if (withoutAlreadyMigrated.isEmpty())
logger.warn("Range " + ranges + " is already migrated");
Set<Range<Token>> withoutBoth = subtract(withoutAlreadyMigrated, migratingRanges);
if (withoutBoth.isEmpty())
logger.warn("Range " + ranges + " is already migrating/migrated");
if (!Range.equals(normalizedRanges, withoutBoth))
logger.warn("Ranges " + normalizedRanges + " to start migrating is already partially migrating/migrated " + withoutBoth);
Map<Epoch, List<Range<Token>>> newMigratingRanges = new HashMap<>(migratingRangesByEpoch.size() + 1);
newMigratingRanges.putAll(migratingRangesByEpoch);
newMigratingRanges.put(Epoch.EMPTY, normalizedRanges);
return new TableMigrationState(keyspaceName, tableName, tableId, targetProtocol, migratedRanges, newMigratingRanges);
}
public TableMigrationState withReplacementForEmptyEpoch(@Nonnull Epoch replacementEpoch)
{
if (!migratingRangesByEpoch.containsKey(Epoch.EMPTY))
return this;
Map<Epoch, List<Range<Token>>> newMigratingRangesByEpoch = new HashMap<>(migratingRangesByEpoch.size());
migratingRangesByEpoch.forEach((epoch, ranges) -> {
if (epoch.equals(Epoch.EMPTY))
newMigratingRangesByEpoch.put(replacementEpoch, ranges);
else
newMigratingRangesByEpoch.put(epoch, ranges);
});
if (newMigratingRangesByEpoch != null)
return new TableMigrationState(keyspaceName, tableName, tableId, targetProtocol, migratedRanges, newMigratingRangesByEpoch);
else
return this;
}
public TableMigrationState withRangesRepairedAtEpoch(@Nonnull Collection<Range<Token>> ranges,
@Nonnull Epoch epoch)
{
checkState(!migratingRangesByEpoch.containsKey(Epoch.EMPTY), "Shouldn't have an entry for the empty epoch");
checkArgument(epoch.isAfter(Epoch.EMPTY), "Epoch shouldn't be empty");
List<Range<Token>> normalizedRepairedRanges = normalize(ranges);
// This should be inclusive because the epoch we store in the map is the epoch in which the range has been marked migrating
// in startMigrationToConsensusProtocol
NavigableMap<Epoch, List<Range<Token>>> coveredEpochs = migratingRangesByEpoch.headMap(epoch, true);
List<Range<Token>> normalizedMigratingRanges = normalize(coveredEpochs.values().stream().flatMap(Collection::stream).collect(Collectors.toList()));
List<Range<Token>> normalizedRepairedIntersection = intersectionOfNormalizedRanges(normalizedRepairedRanges, normalizedMigratingRanges);
checkState(!normalizedRepairedIntersection.isEmpty(), "None of Ranges " + ranges + " were being migrated");
Map<Epoch, List<Range<Token>>> newMigratingRangesByEpoch = new HashMap<>();
// Everything in this epoch or later can't have been migrated so re-add all of them
newMigratingRangesByEpoch.putAll(migratingRangesByEpoch.tailMap(epoch, false));
// Include anything still remaining to be migrated after subtracting what was repaired
for (Map.Entry<Epoch, List<Range<Token>>> e : coveredEpochs.entrySet())
{
// Epoch when these ranges started migrating
Epoch rangesEpoch = e.getKey();
List<Range<Token>> epochMigratingRanges = e.getValue();
List<Range<Token>> remainingRanges = subtractNormalizedRanges(epochMigratingRanges, normalizedRepairedIntersection);
if (!remainingRanges.isEmpty())
newMigratingRangesByEpoch.put(rangesEpoch, remainingRanges);
}
List<Range<Token>> newMigratedRanges = new ArrayList<>(normalizedMigratingRanges.size() + ranges.size());
newMigratedRanges.addAll(migratedRanges);
newMigratedRanges.addAll(normalizedRepairedIntersection);
return new TableMigrationState(keyspaceName, tableName, tableId, targetProtocol, newMigratedRanges, newMigratingRangesByEpoch);
}
public boolean paxosReadSatisfiedByKeyMigrationAtEpoch(DecoratedKey key, ConsensusMigratedAt consensusMigratedAt)
{
// This check is being done from a Paxos read attempt which needs to
// check if Accord needs to resolve any in flight accord transactions
// if the migration target is Accord then nothing needs to be done
if (targetProtocol != ConsensusMigrationTarget.paxos)
return true;
return satisfiedByKeyMigrationAtEpoch(key, consensusMigratedAt);
}
public boolean satisfiedByKeyMigrationAtEpoch(@Nonnull DecoratedKey key, @Nullable ConsensusMigratedAt consensusMigratedAt)
{
if (consensusMigratedAt == null)
{
// It hasn't been migrated and needs migration if it is in a migrating range
return Range.isInNormalizedRanges(key.getToken(), migratingRanges);
}
else
{
// It has been migrated and might be from a late enough epoch to satisfy this migration
return consensusMigratedAt.migratedAtTarget == targetProtocol
&& migratingRangesByEpoch.headMap(consensusMigratedAt.migratedAtEpoch, true).values()
.stream()
.flatMap(List::stream)
.anyMatch(range -> range.contains(key.getToken()));
}
}
public Epoch minMigrationEpoch(Token token)
{
for (Map.Entry<Epoch, List<Range<Token>>> e : migratingRangesByEpoch.entrySet())
{
if (Range.isInNormalizedRanges(token, e.getValue()))
return e.getKey();
}
return Epoch.EMPTY;
}
public @Nonnull TableId getTableId()
{
return tableId;
}
public TableMigrationState withMigrationTarget(ConsensusMigrationTarget newTargetProtocol)
{
checkState(!migratingRangesByEpoch.containsKey(Epoch.EMPTY), "Shouldn't have an entry for the empty epoch");
if (this.targetProtocol == newTargetProtocol)
return this;
// Migrating ranges remain migrating because individual keys may have already been migrated
// So for correctness we need to perform key migration
// We do need to update the epoch so that a new repair is required to drive the migration
Map<Epoch, List<Range<Token>>> migratingRangesByEpoch = ImmutableMap.of(Epoch.EMPTY, migratingRanges);
Token minToken = ColumnFamilyStore.getIfExists(tableId).getPartitioner().getMinimumToken();
Range<Token> fullRange = new Range(minToken, minToken);
// What is migrated already is anything that was never migrated/migrating before (untouched)
List<Range<Token>> migratedRanges = ImmutableList.copyOf(normalize(fullRange.subtractAll(migratingAndMigratedRanges)));
return new TableMigrationState(keyspaceName, tableName, tableId, newTargetProtocol, migratedRanges, migratingRangesByEpoch);
}
public Map<String, Object> toMap()
{
Builder<String, Object> builder = ImmutableMap.builder();
builder.put("keyspace", keyspaceName);
builder.put("table", tableName);
builder.put("tableId", tableId.toString());
builder.put("targetProtocol", targetProtocol.toString());
builder.put("migratedRanges", migratedRanges.stream().map(Objects::toString).collect(toImmutableList()));
Map<Long, List<String>> rangesByEpoch = new LinkedHashMap<>();
for (Map.Entry<Epoch, List<Range<Token>>> entry : migratingRangesByEpoch.entrySet())
{
rangesByEpoch.put(entry.getKey().getEpoch(), entry.getValue().stream().map(Objects::toString).collect(toImmutableList()));
}
builder.put("migratingRangesByEpoch", rangesByEpoch);
return builder.build();
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TableMigrationState that = (TableMigrationState) o;
return keyspaceName.equals(that.keyspaceName) && tableName.equals(that.tableName) && tableId.equals(that.tableId) && targetProtocol == that.targetProtocol && migratedRanges.equals(that.migratedRanges) && migratingRangesByEpoch.equals(that.migratingRangesByEpoch) && migratingRanges.equals(that.migratingRanges) && migratingAndMigratedRanges.equals(that.migratingAndMigratedRanges);
}
@Override
public int hashCode()
{
return Objects.hash(keyspaceName, tableName, tableId, targetProtocol, migratedRanges, migratingRangesByEpoch, migratingRanges, migratingAndMigratedRanges);
}
public List<Range<Token>> migratingRanges()
{
return migratingRanges;
}
}
public static class ConsensusMigrationState implements MetadataValue<ConsensusMigrationState>
{
public static ConsensusMigrationState EMPTY = new ConsensusMigrationState(Epoch.EMPTY, ImmutableMap.of());
@Nonnull
public final Map<TableId, TableMigrationState> tableStates;
public final Epoch lastModified;
public ConsensusMigrationState(@Nonnull Epoch lastModified, @Nonnull Map<TableId, TableMigrationState> tableStates)
{
checkNotNull(tableStates, "tableStates is null");
checkNotNull(lastModified, "lastModified is null");
this.lastModified = lastModified;
this.tableStates = ImmutableMap.copyOf(tableStates);
}
public Map<String, Object> toMap(@Nullable Set<String> keyspaceNames, @Nullable Set<String> tableNames)
{
return ImmutableMap.of("lastModifiedEpoch", lastModified.getEpoch(),
"tableStates", tableStatesAsMaps(keyspaceNames, tableNames),
"version", PojoToString.CURRENT_VERSION);
}
private List<Map<String, Object>> tableStatesAsMaps(@Nullable Set<String> keyspaceNames,
@Nullable Set<String> tableNames)
{
ImmutableList.Builder<Map<String, Object>> builder = ImmutableList.builder();
for (TableMigrationState tms : tableStates.values())
{
if (keyspaceNames != null && !keyspaceNames.contains(tms.keyspaceName))
continue;
if (tableNames != null && !tableNames.contains(tms.tableName))
continue;
builder.add(tms.toMap());
}
return builder.build();
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ConsensusMigrationState that = (ConsensusMigrationState) o;
return tableStates.equals(that.tableStates);
}
@Override
public int hashCode()
{
return Objects.hash(tableStates);
}
public static final MetadataSerializer<ConsensusMigrationState> serializer = new MetadataSerializer<ConsensusMigrationState>()
{
@Override
public void serialize(ConsensusMigrationState consensusMigrationState, DataOutputPlus out, Version version) throws IOException
{
Epoch.serializer.serialize(consensusMigrationState.lastModified, out, version);
serializeMap(consensusMigrationState.tableStates, out, version, TableId.metadataSerializer, TableMigrationState.serializer);
}
@Override
public ConsensusMigrationState deserialize(DataInputPlus in, Version version) throws IOException
{
Epoch lastModified = Epoch.serializer.deserialize(in, version);
Map<TableId, TableMigrationState> tableMigrationStates = deserializeMap(in, version, TableId.metadataSerializer, TableMigrationState.serializer, newHashMap());
return new ConsensusMigrationState(lastModified, tableMigrationStates);
}
@Override
public long serializedSize(ConsensusMigrationState t, Version version)
{
return Epoch.serializer.serializedSize(t.lastModified, version)
+ serializedMapSize(t.tableStates, version, TableId.metadataSerializer, TableMigrationState.serializer);
}
};
@Override
public ConsensusMigrationState withLastModified(Epoch epoch)
{
ImmutableMap.Builder<TableId, TableMigrationState> newMap = ImmutableMap.builderWithExpectedSize(tableStates.size());
tableStates.forEach((tableId, tableState) -> {
newMap.put(tableId, tableState.withReplacementForEmptyEpoch(epoch));
});
return new ConsensusMigrationState(epoch, newMap.build());
}
@Override
public Epoch lastModified()
{
return lastModified;
}
}
private ConsensusTableMigrationState() {}
// Used by callers to avoid looking up the TMS multiple times
public static @Nullable TableMigrationState getTableMigrationState(TableId tableId)
{
TableMigrationState tms = ClusterMetadata.current().consensusMigrationState.tableStates.get(tableId);
return tms;
}
/*
* Set or change the migration target for the keyspaces and tables. Can be used to reverse the direction of a migration
* or instantly migrate a table to a new protocol.
*/
public static void setConsensusMigrationTargetProtocol(@Nonnull String targetProtocolName,
@Nullable List<String> keyspaceNames,
@Nonnull Optional<List<String>> maybeTables)
{
checkArgument(!maybeTables.isPresent() || (keyspaceNames != null && keyspaceNames.size() == 1), "Must specify one keyspace along with tables");
checkArgument(!maybeTables.isPresent() || !maybeTables.get().isEmpty(), "Must provide at least 1 table if Optional is not empty");
keyspaceNames = maybeDefaultKeyspaceNames(keyspaceNames);
ConsensusMigrationTarget targetProtocol = ConsensusMigrationTarget.fromString(targetProtocolName);
if (DatabaseDescriptor.getLWTStrategy() == LWTStrategy.accord)
throw new IllegalStateException("Mixing a hard coded strategy with migration is unsupported");
if (!Paxos.useV2())
throw new IllegalStateException("Can't do any consensus migrations from/to PaxosV1, switch to V2 first");
List<TableId> tableIds = keyspacesAndTablesToTableIds(keyspaceNames, maybeTables);
ClusterMetadataService.instance().commit(new SetConsensusMigrationTargetProtocol(targetProtocol, tableIds));
}
public static void startMigrationToConsensusProtocol(@Nonnull String targetProtocolName,
@Nullable List<String> keyspaceNames,
@Nonnull Optional<List<String>> maybeTables,
@Nonnull Optional<String> maybeRangesStr)
{
checkState(keyspaceNames.size() == 1 || !maybeTables.isPresent(), "Must specify one keyspace along with tables");
checkArgument(!maybeTables.isPresent() || !maybeTables.get().isEmpty(), "Must provide at least 1 table if Optional is not empty");
ConsensusMigrationTarget targetProtocol = ConsensusMigrationTarget.fromString(targetProtocolName);
checkArgument(targetProtocol != reset, "Can't start migration to reset");
if (DatabaseDescriptor.getLWTStrategy() == LWTStrategy.accord)
throw new IllegalStateException("Mixing a hard coded strategy with migration is unsupported");
NonSerialWriteStrategy nonSerialWriteStrategy = DatabaseDescriptor.getNonSerialWriteStrategy();
if (!nonSerialWriteStrategy.writesThroughAccord && nonSerialWriteStrategy != NonSerialWriteStrategy.mixed)
throw new IllegalStateException("non-SERIAL writes need to be routed through Accord before attempting migration, or enable mixed mode");
if (!Paxos.useV2())
throw new IllegalStateException("Can't do any consensus migrations to/from PaxosV1, switch to V2 first");
keyspaceNames = maybeDefaultKeyspaceNames(keyspaceNames);
List<Range<Token>> ranges = maybeRangesToRanges(maybeRangesStr);
List<TableId> tableIds = keyspacesAndTablesToTableIds(keyspaceNames, maybeTables);
ClusterMetadataService.instance().commit(new BeginConsensusMigrationForTableAndRange(targetProtocol, ranges, tableIds));
}
public static List<Integer> finishMigrationToConsensusProtocol(@Nonnull String keyspace,
@Nonnull Optional<List<String>> maybeTables,
@Nonnull Optional<String> maybeRangesStr)
{
checkArgument(!maybeTables.isPresent() || !maybeTables.get().isEmpty(), "Must provide at least 1 table if Optional is not empty");
Optional<List<Range<Token>>> localKeyspaceRanges = Optional.of(ImmutableList.copyOf(StorageService.instance.getLocalReplicas(keyspace).onlyFull().ranges()));
List<Range<Token>> ranges = maybeRangesToRanges(maybeRangesStr, localKeyspaceRanges);
Map<TableId, TableMigrationState> allTableMigrationStates = ClusterMetadata.current().consensusMigrationState.tableStates;
List<TableId> tableIds = keyspacesAndTablesToTableIds(ImmutableList.of(keyspace), maybeTables, Optional.of(allTableMigrationStates::containsKey));
checkState(tableIds.stream().allMatch(allTableMigrationStates::containsKey), "All tables need to be migrating");
List<TableMigrationState> tableMigrationStates = new ArrayList<>();
tableIds.forEach(table -> {
ColumnFamilyStore cfs = ColumnFamilyStore.getIfExists(table);
if (cfs == null)
{
logger.warn("Table {} does not exist or was dropped", cfs);
return;
}
TableMigrationState tms = allTableMigrationStates.get(table);
if (tms == null)
{
logger.warn("Table {} does not have any migration state", cfs.name);
return;
}
if(!Range.intersects(ranges, tms.migratingRanges))
{
logger.warn("Table {} with migrating ranges {} does not intersect with any requested ranges {}", cfs.name, tms.migratingRanges, ranges);
return;
}
tableMigrationStates.add(tms);
});
List<TableMigrationState> migratingToAccord = tableMigrationStates.stream().filter(tms -> tms.targetProtocol == ConsensusMigrationTarget.accord).collect(toImmutableList());
List<TableMigrationState> migratingToPaxos = tableMigrationStates.stream().filter(tms -> tms.targetProtocol == ConsensusMigrationTarget.paxos).collect(toImmutableList());;
Integer accordRepairCmd = finishMigrationToAccord(keyspace, migratingToAccord, ranges);
Integer paxosRepairCmd = finishMigrationToPaxos(keyspace, migratingToPaxos, ranges);
List<Integer> result = new ArrayList<>();
if (accordRepairCmd != null)
result.add(accordRepairCmd);
if (paxosRepairCmd != null)
result.add(paxosRepairCmd);
return result;
}
private interface MigrationFinisher
{
Integer finish(Collection<TableMigrationState> tables, List<Range<Token>> ranges);
}
private static Integer finishMigrationTo(String name, List<TableMigrationState> tableMigrationStates, List<Range<Token>> requestedRanges, MigrationFinisher migrationFinisher)
{
logger.info("Begin finish migration to {} for ranges {} and tables {}", name, requestedRanges, tableMigrationStates);
List<Range<Token>> intersectingRanges = new ArrayList<>();
tableMigrationStates.stream().map(TableMigrationState::migratingRanges).forEach(intersectingRanges::addAll);
intersectingRanges = Range.normalize(intersectingRanges);
intersectingRanges = Range.intersectionOfNormalizedRanges(intersectingRanges, requestedRanges);
if (intersectingRanges.isEmpty())
{
logger.warn("No requested ranges {} intersect any migrating ranges in any table in keyspace {}");
return null;
}
// Repair requires that the ranges once again be grouped by the ranges provided originally which all
// fall within local range boundaries. This was already checked in maybeRangesToRanges.
List<Range<Token>> intersectingRangesGrouped = new ArrayList<>();
for (Range<Token> r : requestedRanges)
{
List<Range<Token>> intersectionsForGroup = new ArrayList<>();
for (Range<Token> intersectedRange : intersectingRanges)
intersectionsForGroup.addAll(r.intersectionWith(intersectedRange));
intersectingRangesGrouped.addAll(normalize(intersectionsForGroup));
}
return migrationFinisher.finish(tableMigrationStates, intersectingRangesGrouped);
}
/*
* This is basically just invoking classic Cassandra repair and is pretty redundant with invoking repair
* directly which would also work without issue. It's include so the same interface works for both migrating to/from
* Accord, but it's not great in that repair has a lot of options that might need to be forwarded.
*
* Still maybe more valuable to put this layer of abstraction in so we can change how it works later and it's less
* tightly coupled with the Repair interface which is pretty orthogonal to consensus migration.
*/
private static Integer finishMigrationToAccord(String keyspace, List<TableMigrationState> migratingToAccord, List<Range<Token>> requestedRanges)
{
return finishMigrationTo("Accord", migratingToAccord, requestedRanges, (tables, intersectingRanges) -> {
RepairOption repairOption = getRepairOption(tables, intersectingRanges, false);
return StorageService.instance.repair(keyspace, repairOption, emptyList()).left;
});
}
private static Integer finishMigrationToPaxos(String keyspace, List<TableMigrationState> migratingToPaxos, List<Range<Token>> requestedRanges)
{
return finishMigrationTo("Paxos", migratingToPaxos, requestedRanges, (tables, intersectingRanges) -> {
RepairOption repairOption = getRepairOption(tables, intersectingRanges, true);
return StorageService.instance.repair(keyspace, repairOption, emptyList()).left;
});
}
@Nonnull
private static RepairOption getRepairOption(Collection<TableMigrationState> tables, List<Range<Token>> intersectingRanges, boolean accordRepair)
{
boolean primaryRange = false;
// TODO (review): Should disabling incremental repair be exposed for the Paxos repair in case someone explicitly does not do incremental repair?
boolean incremental = !accordRepair;
boolean trace = false;
int numJobThreads = 1;
boolean pullRepair = false;
boolean forceRepair = false;
boolean optimiseStreams = false;
boolean ignoreUnreplicatedKeyspaces = true;
boolean repairPaxos = !accordRepair;
boolean paxosOnly = false;
boolean dontPurgeTombstones = false;
RepairOption repairOption = new RepairOption(RepairParallelism.PARALLEL, primaryRange, incremental, trace, numJobThreads, intersectingRanges, pullRepair, forceRepair, PreviewKind.NONE, optimiseStreams, ignoreUnreplicatedKeyspaces, repairPaxos, paxosOnly, dontPurgeTombstones, accordRepair);
tables.forEach(table -> repairOption.getColumnFamilies().add(table.tableName));
return repairOption;
}
// Repair is restricted to local ranges, but manipulating CMS migration state doesn't need to be restricted
private static @Nonnull List<Range<Token>> maybeRangesToRanges(@Nonnull Optional<String> maybeRangesStr)
{
return maybeRangesToRanges(maybeRangesStr, Optional.empty());
}
private static @Nonnull List<Range<Token>> maybeRangesToRanges(@Nonnull Optional<String> maybeRangesStr, Optional<List<Range<Token>>> restrictToRanges)
{
IPartitioner partitioner = DatabaseDescriptor.getPartitioner();
Optional<List<Range<Token>>> maybeParsedRanges = maybeRangesStr.map(rangesStr -> ImmutableList.copyOf(RepairOption.parseRanges(rangesStr, partitioner)));
Token minToken = partitioner.getMinimumToken();
List<Range<Token>> defaultRanges = restrictToRanges.orElse(ImmutableList.of(new Range(minToken, minToken)));
List<Range<Token>> ranges = maybeParsedRanges.orElse(defaultRanges);
checkArgument(ranges.stream().allMatch(range -> defaultRanges.stream().anyMatch(defaultRange -> defaultRange.contains(range))),
"If ranges are specified each range must be contained within a local range (" + defaultRanges + ") for this node to allow for precise repairs. Specified " + ranges);
return ranges;
}
private static List<String> maybeDefaultKeyspaceNames(@Nullable List<String> keyspaceNames)
{
if (keyspaceNames == null || keyspaceNames.isEmpty())
{
keyspaceNames = ImmutableList.copyOf(StorageService.instance.getNonSystemKeyspaces());
}
checkState(keyspaceNames.stream().noneMatch(SchemaConstants::isSystemKeyspace), "Migrating system keyspaces is not supported");
return keyspaceNames;
}
private static List<TableId> keyspacesAndTablesToTableIds(@Nonnull List<String> keyspaceNames, @Nonnull Optional<List<String>> maybeTables)
{
return keyspacesAndTablesToTableIds(keyspaceNames, maybeTables, Optional.empty());
}
private static List<TableId> keyspacesAndTablesToTableIds(@Nonnull List<String> keyspaceNames, @Nonnull Optional<List<String>> maybeTables, @Nonnull Optional<Predicate<TableId>> includeTable)
{
List<TableId> tableIds = new ArrayList<>();
for (String keyspaceName : keyspaceNames)
{
Optional<Collection<TableId>> maybeTableIds = maybeTables.map(tableNames ->
tableNames
.stream()
.map(tableName -> {
TableMetadata tm = Schema.instance.getTableMetadata(keyspaceName, tableName);
if (tm == null)
throw new IllegalArgumentException("Unknown table %s.%s".format(keyspaceName, tableName));
return tm.id;
})
.collect(toImmutableList()));
tableIds.addAll(
maybeTableIds.orElseGet(() ->
Schema.instance.getKeyspaceInstance(keyspaceName).getColumnFamilyStores()
.stream()
.map(ColumnFamilyStore::getTableId)
.filter(includeTable.orElse(Predicates.alwaysTrue())) // Filter out non-migrating so they don't generate an error
.collect(toImmutableList())));
}
return tableIds;
}
}

View File

@ -0,0 +1,360 @@
/*
* 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.service.consensus.migration;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedMap;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.dht.IPartitioner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.tcm.serialization.MetadataSerializer;
import org.apache.cassandra.tcm.serialization.Version;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static org.apache.cassandra.db.TypeSizes.sizeof;
import static org.apache.cassandra.dht.Range.intersectionOfNormalizedRanges;
import static org.apache.cassandra.dht.Range.normalize;
import static org.apache.cassandra.dht.Range.subtract;
import static org.apache.cassandra.dht.Range.subtractNormalizedRanges;
import static org.apache.cassandra.utils.CollectionSerializers.deserializeMap;
import static org.apache.cassandra.utils.CollectionSerializers.deserializeSet;
import static org.apache.cassandra.utils.CollectionSerializers.newHashMap;
import static org.apache.cassandra.utils.CollectionSerializers.serializeCollection;
import static org.apache.cassandra.utils.CollectionSerializers.serializeMap;
import static org.apache.cassandra.utils.CollectionSerializers.serializedCollectionSize;
import static org.apache.cassandra.utils.CollectionSerializers.serializedMapSize;
// TODO Move this into the schema for the table once this is based off of TrM
public class TableMigrationState
{
private static final Logger logger = LoggerFactory.getLogger(TableMigrationState.class);
@Nonnull
public final String keyspaceName;
@Nonnull
public final String tableName;
@Nonnull
public final TableId tableId;
@Nonnull
public final ConsensusMigrationTarget targetProtocol;
@Nonnull
public final List<Range<Token>> migratedRanges;
/*
* Necessary to track which ranges started migrating at which epoch
* in order to know whether a repair qualifies in terms of finishing
* migration of the range.
*/
@Nonnull
public final NavigableMap<Epoch, List<Range<Token>>> migratingRangesByEpoch;
@Nonnull
public final List<Range<Token>> migratingRanges;
@Nonnull
public final List<Range<Token>> migratingAndMigratedRanges;
public TableMigrationState(@Nonnull String keyspaceName,
@Nonnull String tableName,
@Nonnull TableId tableId,
@Nonnull ConsensusMigrationTarget targetProtocol,
@Nonnull Collection<Range<Token>> migratedRanges,
@Nonnull Map<Epoch, List<Range<Token>>> migratingRangesByEpoch)
{
this.keyspaceName = keyspaceName;
this.tableName = tableName;
this.tableId = tableId;
this.targetProtocol = targetProtocol;
this.migratedRanges = ImmutableList.copyOf(normalize(migratedRanges));
this.migratingRangesByEpoch = ImmutableSortedMap.copyOf(
migratingRangesByEpoch.entrySet()
.stream()
.map(entry -> new AbstractMap.SimpleEntry<>(entry.getKey(), ImmutableList.copyOf(normalize(entry.getValue()))))
.collect(Collectors.toList()));
this.migratingRanges = ImmutableList.copyOf(normalize(migratingRangesByEpoch.values().stream().flatMap(Collection::stream).collect(Collectors.toList())));
this.migratingAndMigratedRanges = ImmutableList.copyOf(normalize(ImmutableList.<Range<Token>>builder().addAll(migratedRanges).addAll(migratingRanges).build()));
}
public TableMigrationState reverseMigration(ConsensusMigrationTarget target, Epoch epoch)
{
IPartitioner partitioner = DatabaseDescriptor.getPartitioner();
Range<Token> fullRange = new Range<>(partitioner.getMinimumToken(), partitioner.getMinimumToken());
List<Range<Token>> allTouched = new ArrayList<>(migratedRanges);
allTouched.addAll(migratingRanges);
allTouched = Range.deoverlap(allTouched);
return new TableMigrationState(keyspaceName, tableName, tableId, target,
Range.normalize(fullRange.subtractAll(allTouched)),
Collections.singletonMap(epoch, migratingRanges));
}
public boolean hasMigratedFullTokenRange(IPartitioner partitioner)
{
// migrated ranges are normalized
if (!migratingRanges.isEmpty() || migratedRanges.size() > 1)
return false;
Range<Token> fullRange = new Range<>(partitioner.getMinimumToken(), partitioner.getMinimumToken());
return migratedRanges.get(0).contains(fullRange);
}
@Nonnull
public List<Range<Token>> migratingRanges() {
return migratingRanges;
}
public TableMigrationState withRangesMigrating(@Nonnull Collection<Range<Token>> ranges,
@Nonnull ConsensusMigrationTarget target)
{
checkState(!migratingRangesByEpoch.containsKey(Epoch.EMPTY), "Shouldn't already have an entry for the empty epoch");
// Doesn't matter which epoch the range started migrating in for this context so merge them all
Collection<Range<Token>> migratingRanges = normalize(migratingRangesByEpoch.values().stream().flatMap(Collection::stream).collect(Collectors.toList()));
checkArgument(target == targetProtocol, "Requested migration to target protocol " + target + " conflicts with in progress migration to protocol " + targetProtocol);
List<Range<Token>> normalizedRanges = normalize(ranges);
if (subtract(normalizedRanges, migratingRanges).isEmpty())
logger.warn("Range " + ranges + " is already being migrated");
Set<Range<Token>> withoutAlreadyMigrated = subtract(normalizedRanges, migratedRanges);
if (withoutAlreadyMigrated.isEmpty())
logger.warn("Range " + ranges + " is already migrated");
Set<Range<Token>> withoutBoth = subtract(withoutAlreadyMigrated, migratingRanges);
if (withoutBoth.isEmpty())
logger.warn("Range " + ranges + " is already migrating/migrated");
if (!Range.equals(normalizedRanges, withoutBoth))
logger.warn("Ranges " + normalizedRanges + " to start migrating is already partially migrating/migrated " + withoutBoth);
Map<Epoch, List<Range<Token>>> newMigratingRanges = new HashMap<>(migratingRangesByEpoch.size() + 1);
newMigratingRanges.putAll(migratingRangesByEpoch);
newMigratingRanges.put(Epoch.EMPTY, normalizedRanges);
return new TableMigrationState(keyspaceName, tableName, tableId, targetProtocol, migratedRanges, newMigratingRanges);
}
public TableMigrationState withReplacementForEmptyEpoch(@Nonnull Epoch replacementEpoch)
{
if (!migratingRangesByEpoch.containsKey(Epoch.EMPTY))
return this;
Map<Epoch, List<Range<Token>>> newMigratingRangesByEpoch = new HashMap<>(migratingRangesByEpoch.size());
migratingRangesByEpoch.forEach((epoch, ranges) -> {
if (epoch.equals(Epoch.EMPTY))
newMigratingRangesByEpoch.put(replacementEpoch, ranges);
else
newMigratingRangesByEpoch.put(epoch, ranges);
});
if (newMigratingRangesByEpoch != null)
return new TableMigrationState(keyspaceName, tableName, tableId, targetProtocol, migratedRanges, newMigratingRangesByEpoch);
else
return this;
}
public TableMigrationState withRangesRepairedAtEpoch(@Nonnull Collection<Range<Token>> ranges,
@Nonnull Epoch epoch)
{
checkState(!migratingRangesByEpoch.containsKey(Epoch.EMPTY), "Shouldn't have an entry for the empty epoch");
checkArgument(epoch.isAfter(Epoch.EMPTY), "Epoch shouldn't be empty");
List<Range<Token>> normalizedRepairedRanges = normalize(ranges);
// This should be inclusive because the epoch we store in the map is the epoch in which the range has been marked migrating
// in startMigrationToConsensusProtocol
NavigableMap<Epoch, List<Range<Token>>> coveredEpochs = migratingRangesByEpoch.headMap(epoch, true);
List<Range<Token>> normalizedMigratingRanges = normalize(coveredEpochs.values().stream().flatMap(Collection::stream).collect(Collectors.toList()));
List<Range<Token>> normalizedRepairedIntersection = intersectionOfNormalizedRanges(normalizedRepairedRanges, normalizedMigratingRanges);
checkState(!normalizedRepairedIntersection.isEmpty(), "None of Ranges " + ranges + " were being migrated");
Map<Epoch, List<Range<Token>>> newMigratingRangesByEpoch = new HashMap<>();
// Everything in this epoch or later can't have been migrated so re-add all of them
newMigratingRangesByEpoch.putAll(migratingRangesByEpoch.tailMap(epoch, false));
// Include anything still remaining to be migrated after subtracting what was repaired
for (Map.Entry<Epoch, List<Range<Token>>> e : coveredEpochs.entrySet())
{
// Epoch when these ranges started migrating
Epoch rangesEpoch = e.getKey();
List<Range<Token>> epochMigratingRanges = e.getValue();
List<Range<Token>> remainingRanges = subtractNormalizedRanges(epochMigratingRanges, normalizedRepairedIntersection);
if (!remainingRanges.isEmpty())
newMigratingRangesByEpoch.put(rangesEpoch, remainingRanges);
}
List<Range<Token>> newMigratedRanges = new ArrayList<>(normalizedMigratingRanges.size() + ranges.size());
newMigratedRanges.addAll(migratedRanges);
newMigratedRanges.addAll(normalizedRepairedIntersection);
return new TableMigrationState(keyspaceName, tableName, tableId, targetProtocol, newMigratedRanges, newMigratingRangesByEpoch);
}
public boolean paxosReadSatisfiedByKeyMigrationAtEpoch(DecoratedKey key, ConsensusMigratedAt consensusMigratedAt)
{
// This check is being done from a Paxos read attempt which needs to
// check if Accord needs to resolve any in flight accord transactions
// if the migration target is Accord then nothing needs to be done
if (targetProtocol != ConsensusMigrationTarget.paxos)
return true;
return satisfiedByKeyMigrationAtEpoch(key, consensusMigratedAt);
}
public boolean satisfiedByKeyMigrationAtEpoch(@Nonnull DecoratedKey key, @Nullable ConsensusMigratedAt consensusMigratedAt)
{
if (consensusMigratedAt == null)
{
// It hasn't been migrated and needs migration if it is in a migrating range
return Range.isInNormalizedRanges(key.getToken(), migratingRanges);
}
else
{
// It has been migrated and might be from a late enough epoch to satisfy this migration
return consensusMigratedAt.migratedAtTarget == targetProtocol
&& migratingRangesByEpoch.headMap(consensusMigratedAt.migratedAtEpoch, true).values()
.stream()
.flatMap(List::stream)
.anyMatch(range -> range.contains(key.getToken()));
}
}
public Epoch minMigrationEpoch(Token token)
{
// TODO should there be an index to make this more efficient?
for (Map.Entry<Epoch, List<Range<Token>>> e : migratingRangesByEpoch.entrySet())
{
if (Range.isInNormalizedRanges(token, e.getValue()))
return e.getKey();
}
return Epoch.EMPTY;
}
public @Nonnull TableId getTableId()
{
return tableId;
}
public TableMigrationState withMigrationTarget(ConsensusMigrationTarget newTargetProtocol)
{
checkState(!migratingRangesByEpoch.containsKey(Epoch.EMPTY), "Shouldn't have an entry for the empty epoch");
if (targetProtocol == newTargetProtocol)
return this;
// Migrating ranges remain migrating because individual keys may have already been migrated
// So for correctness we need to perform key migration
// We do need to update the epoch so that a new repair is required to drive the migration
Map<Epoch, List<Range<Token>>> migratingRangesByEpoch = ImmutableMap.of(Epoch.EMPTY, migratingRanges);
Token minToken = ColumnFamilyStore.getIfExists(tableId).getPartitioner().getMinimumToken();
Range<Token> fullRange = new Range(minToken, minToken);
// What is migrated already is anything that was never migrated/migrating before (untouched)
List<Range<Token>> migratedRanges = ImmutableList.copyOf(normalize(fullRange.subtractAll(migratingAndMigratedRanges)));
return new TableMigrationState(keyspaceName, tableName, tableId, newTargetProtocol, migratedRanges, migratingRangesByEpoch);
}
public Map<String, Object> toMap()
{
ImmutableMap.Builder<String, Object> builder = ImmutableMap.builder();
builder.put("keyspace", keyspaceName);
builder.put("table", tableName);
builder.put("tableId", tableId.toString());
builder.put("targetProtocol", targetProtocol.toString());
builder.put("migratedRanges", migratedRanges.stream().map(Objects::toString).collect(toImmutableList()));
Map<Long, List<String>> rangesByEpoch = new LinkedHashMap<>();
for (Map.Entry<Epoch, List<Range<Token>>> entry : migratingRangesByEpoch.entrySet())
{
rangesByEpoch.put(entry.getKey().getEpoch(), entry.getValue().stream().map(Objects::toString).collect(toImmutableList()));
}
builder.put("migratingRangesByEpoch", rangesByEpoch);
return builder.build();
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TableMigrationState that = (TableMigrationState) o;
return keyspaceName.equals(that.keyspaceName) && tableName.equals(that.tableName) && tableId.equals(that.tableId) && targetProtocol == that.targetProtocol && migratedRanges.equals(that.migratedRanges) && migratingRangesByEpoch.equals(that.migratingRangesByEpoch) && migratingRanges.equals(that.migratingRanges) && migratingAndMigratedRanges.equals(that.migratingAndMigratedRanges);
}
@Override
public int hashCode()
{
return Objects.hash(keyspaceName, tableName, tableId, targetProtocol, migratedRanges, migratingRangesByEpoch, migratingRanges, migratingAndMigratedRanges);
}
public static final MetadataSerializer<TableMigrationState> serializer = new MetadataSerializer<TableMigrationState>()
{
@Override
public void serialize(TableMigrationState t, DataOutputPlus out, Version version) throws IOException
{
out.write(t.targetProtocol.value);
out.writeUTF(t.keyspaceName);
out.writeUTF(t.tableName);
t.tableId.serialize(out);
serializeCollection(t.migratedRanges, out, version, Range.serializer);
serializeMap(t.migratingRangesByEpoch, out, version, Epoch.serializer, ConsensusTableMigration.rangesSerializer);
}
@Override
public TableMigrationState deserialize(DataInputPlus in, Version version) throws IOException
{
ConsensusMigrationTarget targetProtocol = ConsensusMigrationTarget.fromValue(in.readByte());
String keyspaceName = in.readUTF();
String tableName = in.readUTF();
TableId tableId = TableId.deserialize(in);
Set<Range<Token>> migratedRanges = deserializeSet(in, version, Range.serializer);
Map<Epoch, List<Range<Token>>> migratingRangesByEpoch = deserializeMap(in, version, Epoch.serializer, ConsensusTableMigration.rangesSerializer, newHashMap());
return new TableMigrationState(keyspaceName, tableName, tableId, targetProtocol, migratedRanges, migratingRangesByEpoch);
}
@Override
public long serializedSize(TableMigrationState t, Version version)
{
return sizeof(t.targetProtocol.value)
+ sizeof(t.keyspaceName)
+ sizeof(t.tableName)
+ t.tableId.serializedSize()
+ serializedCollectionSize(t.migratedRanges, version, Range.serializer)
+ serializedMapSize(t.migratingRangesByEpoch, version, Epoch.serializer, ConsensusTableMigration.rangesSerializer);
}
};
}

View File

@ -0,0 +1,84 @@
/*
* 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.service.consensus.migration;
import org.apache.cassandra.service.consensus.TransactionalMode;
import org.apache.cassandra.utils.LocalizeString;
/**
* This tracks the state of a migration either from Paxos -> Accord, Accord [interop mode a] -> Accord [interop mode b] or Accord -> Paxos.
* The `TransactionalMode` associated with each transition from a system is how interoperability should be achieved during the migration with various performance/safety tradeoffs.
*/
public enum TransactionalMigrationFromMode
{
none(null), // No migration is in progress. The currently active transaction system could be either Accord or Paxos.
off(TransactionalMode.off),
unsafe(TransactionalMode.unsafe),
unsafe_writes(TransactionalMode.unsafe_writes),
mixed_reads(TransactionalMode.mixed_reads),
full(TransactionalMode.full);
public final TransactionalMode from;
TransactionalMigrationFromMode(TransactionalMode from)
{
this.from = from;
}
public static TransactionalMigrationFromMode fromMode(TransactionalMode prev, TransactionalMode next)
{
if (next.accordIsEnabled == prev.accordIsEnabled)
return none;
switch (prev)
{
default: throw new IllegalArgumentException();
case off: return off;
case unsafe: return unsafe;
case unsafe_writes: return unsafe_writes;
case mixed_reads: return mixed_reads;
case full: return full;
}
}
public static TransactionalMigrationFromMode fromOrdinal(int ordinal)
{
return values()[ordinal];
}
public static TransactionalMigrationFromMode fromString(String name)
{
return valueOf(LocalizeString.toLowerCaseLocalized(name));
}
public boolean migratingFromAccord()
{
return from != null && from.accordIsEnabled;
}
public boolean writesThroughAccord()
{
return from != null && from.writesThroughAccord;
}
public boolean isMigrating()
{
return this != none;
}
}

View File

@ -72,7 +72,8 @@ import static org.apache.cassandra.locator.InetAddressAndPort.Serializer.inetAdd
import static org.apache.cassandra.net.Verb.PAXOS2_PREPARE_REQ;
import static org.apache.cassandra.net.Verb.PAXOS2_PREPARE_RSP;
import static org.apache.cassandra.service.consensus.migration.ConsensusKeyMigrationState.getKeyMigrationState;
import static org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState.ConsensusMigratedAt;
import org.apache.cassandra.service.consensus.migration.ConsensusMigratedAt;
import static org.apache.cassandra.service.paxos.Ballot.Flag.NONE;
import static org.apache.cassandra.service.paxos.Commit.Accepted;
import static org.apache.cassandra.service.paxos.Commit.Committed;

View File

@ -34,8 +34,7 @@ import accord.primitives.Keys;
import accord.primitives.Txn;
import com.codahale.metrics.Meter;
import org.apache.cassandra.concurrent.Stage;
import org.apache.cassandra.config.Config.LWTStrategy;
import org.apache.cassandra.config.Config.NonSerialWriteStrategy;
import org.apache.cassandra.service.consensus.TransactionalMode;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.db.DecoratedKey;
@ -196,9 +195,8 @@ public class BlockingReadRepair<E extends Endpoints<E>, P extends ReplicaPlan.Fo
@Override
public void repairPartition(DecoratedKey dk, Map<Replica, Mutation> mutations, ReplicaPlan.ForWrite writePlan)
{
NonSerialWriteStrategy nonSerialWriteStrategy = DatabaseDescriptor.getNonSerialWriteStrategy();
if (coordinator.isEventuallyConsistent() && (DatabaseDescriptor.getLWTStrategy() == LWTStrategy.accord
|| nonSerialWriteStrategy.blockingReadRepairThroughAccord))
TransactionalMode transactionalMode = command.metadata().params.transactionalMode;
if (coordinator.isEventuallyConsistent() && transactionalMode.blockingReadRepairThroughAccord)
{
Collection<PartitionUpdate> partitionUpdates = Mutation.merge(mutations.values()).getPartitionUpdates();
checkState(partitionUpdates.size() == 1, "Expect only one PartitionUpdate");

View File

@ -25,7 +25,7 @@ import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.locator.RangesAtEndpoint;
import org.apache.cassandra.locator.Replica;
import org.apache.cassandra.schema.KeyspaceMetadata;
import org.apache.cassandra.service.accord.AccordService;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.utils.TimeUUID;
import static com.google.common.collect.Iterables.all;
@ -238,7 +238,7 @@ public class StreamPlan
public static String[] nonAccordTablesForKeyspace(KeyspaceMetadata ksm)
{
String[] result = ksm.tables.stream()
.filter(tbl -> !AccordService.instance().isAccordManagedTable(tbl.id))
.filter(tbl -> !tbl.isAccordEnabled())
.map(tbl -> tbl.name)
.toArray(String[]::new);
@ -247,11 +247,11 @@ public class StreamPlan
public static boolean hasNonAccordTables(KeyspaceMetadata ksm)
{
return ksm.tables.stream().anyMatch(tbl -> !AccordService.instance().isAccordManagedTable(tbl.id));
return ksm.tables.stream().anyMatch(tbl -> !tbl.isAccordEnabled());
}
public static boolean hasAccordTables(KeyspaceMetadata ksm)
{
return ksm.tables.stream().anyMatch(tbl -> AccordService.instance().isAccordManagedTable(tbl.id));
return ksm.tables.stream().anyMatch(TableMetadata::isAccordEnabled);
}
}

View File

@ -57,13 +57,12 @@ import org.apache.cassandra.schema.KeyspaceMetadata;
import org.apache.cassandra.schema.Keyspaces;
import org.apache.cassandra.schema.ReplicationParams;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState.ConsensusMigrationState;
import org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState.TableMigrationState;
import org.apache.cassandra.service.consensus.migration.ConsensusMigrationState;
import org.apache.cassandra.service.consensus.migration.TableMigrationState;
import org.apache.cassandra.tcm.extensions.ExtensionKey;
import org.apache.cassandra.tcm.extensions.ExtensionValue;
import org.apache.cassandra.service.accord.AccordFastPath;
import org.apache.cassandra.tcm.membership.*;
import org.apache.cassandra.tcm.ownership.AccordTables;
import org.apache.cassandra.tcm.ownership.DataPlacement;
import org.apache.cassandra.tcm.ownership.DataPlacements;
import org.apache.cassandra.tcm.ownership.PrimaryRangeComparator;
@ -96,7 +95,6 @@ public class ClusterMetadata
public final Directory directory;
public final TokenMap tokenMap;
public final DataPlacements placements;
public final AccordTables accordTables;
public final AccordFastPath accordFastPath;
public final LockedRanges lockedRanges;
public final InProgressSequences inProgressSequences;
@ -133,7 +131,6 @@ public class ClusterMetadata
directory,
new TokenMap(partitioner),
DataPlacements.EMPTY,
AccordTables.EMPTY,
AccordFastPath.EMPTY,
LockedRanges.EMPTY,
InProgressSequences.EMPTY,
@ -147,7 +144,6 @@ public class ClusterMetadata
Directory directory,
TokenMap tokenMap,
DataPlacements placements,
AccordTables accordTables,
AccordFastPath accordFastPath,
LockedRanges lockedRanges,
InProgressSequences inProgressSequences,
@ -161,7 +157,6 @@ public class ClusterMetadata
directory,
tokenMap,
placements,
accordTables,
accordFastPath,
lockedRanges,
inProgressSequences,
@ -176,7 +171,6 @@ public class ClusterMetadata
Directory directory,
TokenMap tokenMap,
DataPlacements placements,
AccordTables accordTables,
AccordFastPath accordFastPath,
LockedRanges lockedRanges,
InProgressSequences inProgressSequences,
@ -194,7 +188,6 @@ public class ClusterMetadata
this.directory = directory;
this.tokenMap = tokenMap;
this.placements = placements;
this.accordTables = accordTables;
this.accordFastPath = accordFastPath;
this.lockedRanges = lockedRanges;
this.inProgressSequences = inProgressSequences;
@ -205,12 +198,12 @@ public class ClusterMetadata
public ClusterMetadata withDirectory(Directory directory)
{
return new ClusterMetadata(epoch, partitioner, schema, directory, tokenMap, placements, accordTables, accordFastPath, lockedRanges, inProgressSequences, consensusMigrationState, extensions);
return new ClusterMetadata(epoch, partitioner, schema, directory, tokenMap, placements, accordFastPath, lockedRanges, inProgressSequences, consensusMigrationState, extensions);
}
public ClusterMetadata withPlacements(DataPlacements placements)
{
return new ClusterMetadata(epoch, partitioner, schema, directory, tokenMap, placements, accordTables, accordFastPath, lockedRanges, inProgressSequences, consensusMigrationState, extensions);
return new ClusterMetadata(epoch, partitioner, schema, directory, tokenMap, placements, accordFastPath, lockedRanges, inProgressSequences, consensusMigrationState, extensions);
}
public Set<InetAddressAndPort> fullCMSMembers()
@ -262,7 +255,6 @@ public class ClusterMetadata
capLastModified(directory, epoch),
capLastModified(tokenMap, epoch),
capLastModified(placements, epoch),
capLastModified(accordTables, epoch),
capLastModified(accordFastPath, epoch),
capLastModified(lockedRanges, epoch),
capLastModified(inProgressSequences, epoch),
@ -285,7 +277,6 @@ public class ClusterMetadata
directory,
tokenMap,
placements,
accordTables,
accordFastPath,
lockedRanges,
inProgressSequences,
@ -413,7 +404,6 @@ public class ClusterMetadata
private Directory directory;
private TokenMap tokenMap;
private DataPlacements placements;
private AccordTables accordTables;
private AccordFastPath accordFastPath;
private LockedRanges lockedRanges;
private InProgressSequences inProgressSequences;
@ -430,7 +420,6 @@ public class ClusterMetadata
this.directory = metadata.directory;
this.tokenMap = metadata.tokenMap;
this.placements = metadata.placements;
this.accordTables = metadata.accordTables;
this.accordFastPath = metadata.accordFastPath;
this.lockedRanges = metadata.lockedRanges;
this.inProgressSequences = metadata.inProgressSequences;
@ -439,6 +428,11 @@ public class ClusterMetadata
modifiedKeys = new HashSet<>();
}
public Epoch epoch()
{
return epoch;
}
public Transformer with(DistributedSchema schema)
{
this.schema = schema;
@ -552,12 +546,6 @@ public class ClusterMetadata
return this;
}
public Transformer withAccordTable(TableId table)
{
accordTables = accordTables.with(table);
return this;
}
public Transformer withFastPathStatusSince(Node.Id node, AccordFastPath.Status status, long updateTimeMillis, long updateDelayMillis)
{
accordFastPath = accordFastPath.withNodeStatusSince(node, status, updateTimeMillis, updateDelayMillis);
@ -601,6 +589,12 @@ public class ClusterMetadata
return this;
}
public Transformer with(ConsensusMigrationState consensusMigrationState)
{
this.consensusMigrationState = consensusMigrationState;
return this;
}
public Transformer with(ExtensionKey<?, ?> key, ExtensionValue<?> obj)
{
if (MetadataKeys.CORE_METADATA.contains(key))
@ -678,12 +672,6 @@ public class ClusterMetadata
placements = placements.withLastModified(epoch);
}
if (accordTables != base.accordTables)
{
modifiedKeys.add(MetadataKeys.ACCORD_TABLES);
accordTables = accordTables.withLastModified(epoch);
}
if (accordFastPath != base.accordFastPath)
{
modifiedKeys.add(MetadataKeys.ACCORD_FAST_PATH);
@ -708,6 +696,11 @@ public class ClusterMetadata
consensusMigrationState = consensusMigrationState.withLastModified(epoch);
}
if (consensusMigrationState != base.consensusMigrationState || schema != base.schema)
{
consensusMigrationState.validateAgainstSchema(schema);
}
return new Transformed(new ClusterMetadata(base.metadataIdentifier,
epoch,
partitioner,
@ -715,7 +708,6 @@ public class ClusterMetadata
directory,
tokenMap,
placements,
accordTables,
accordFastPath,
lockedRanges,
inProgressSequences,
@ -733,7 +725,6 @@ public class ClusterMetadata
directory,
tokenMap,
placements,
accordTables,
accordFastPath,
lockedRanges,
inProgressSequences,
@ -866,7 +857,7 @@ public class ClusterMetadata
directory.equals(that.directory) &&
tokenMap.equals(that.tokenMap) &&
placements.equals(that.placements) &&
accordTables.equals(that.accordTables) &&
accordFastPath.equals(that.accordFastPath) &&
lockedRanges.equals(that.lockedRanges) &&
inProgressSequences.equals(that.inProgressSequences) &&
consensusMigrationState.equals(that.consensusMigrationState) &&
@ -918,7 +909,7 @@ public class ClusterMetadata
@Override
public int hashCode()
{
return Objects.hash(epoch, schema, directory, tokenMap, placements, accordTables, lockedRanges, inProgressSequences, consensusMigrationState, extensions);
return Objects.hash(epoch, schema, directory, tokenMap, placements, accordFastPath, lockedRanges, inProgressSequences, consensusMigrationState, extensions);
}
public static ClusterMetadata current()
@ -997,12 +988,11 @@ public class ClusterMetadata
DataPlacements.serializer.serialize(metadata.placements, out, version);
if (version.isAtLeast(V2))
{
AccordTables.serializer.serialize(metadata.accordTables, out, version);
AccordFastPath.serializer.serialize(metadata.accordFastPath, out, version);
ConsensusMigrationState.serializer.serialize(metadata.consensusMigrationState, out, version);
}
LockedRanges.serializer.serialize(metadata.lockedRanges, out, version);
InProgressSequences.serializer.serialize(metadata.inProgressSequences, out, version);
ConsensusMigrationState.serializer.serialize(metadata.consensusMigrationState, out, version);
out.writeInt(metadata.extensions.size());
for (Map.Entry<ExtensionKey<?, ?>, ExtensionValue<?>> entry : metadata.extensions.entrySet())
{
@ -1037,21 +1027,20 @@ public class ClusterMetadata
Directory dir = Directory.serializer.deserialize(in, version);
TokenMap tokenMap = TokenMap.serializer.deserialize(in, version);
DataPlacements placements = DataPlacements.serializer.deserialize(in, version);
AccordTables accordTables;
AccordFastPath accordFastPath;
ConsensusMigrationState consensusMigrationState;
if (version.isAtLeast(V2))
{
accordTables = AccordTables.serializer.deserialize(in, version);
accordFastPath = AccordFastPath.serializer.deserialize(in, version);
consensusMigrationState = ConsensusMigrationState.serializer.deserialize(in, version);
}
else
{
accordTables = AccordTables.EMPTY;
accordFastPath = AccordFastPath.EMPTY;
consensusMigrationState = ConsensusMigrationState.EMPTY;
}
LockedRanges lockedRanges = LockedRanges.serializer.deserialize(in, version);
InProgressSequences ips = InProgressSequences.serializer.deserialize(in, version);
ConsensusMigrationState consensusMigrationState = ConsensusMigrationState.serializer.deserialize(in, version);
int items = in.readInt();
Map<ExtensionKey<?, ?>, ExtensionValue<?>> extensions = new HashMap<>(items);
for (int i = 0; i < items; i++)
@ -1068,7 +1057,6 @@ public class ClusterMetadata
dir,
tokenMap,
placements,
accordTables,
accordFastPath,
lockedRanges,
ips,
@ -1092,14 +1080,12 @@ public class ClusterMetadata
DistributedSchema.serializer.serializedSize(metadata.schema, version) +
Directory.serializer.serializedSize(metadata.directory, version) +
TokenMap.serializer.serializedSize(metadata.tokenMap, version) +
DataPlacements.serializer.serializedSize(metadata.placements, version) +
ConsensusMigrationState.serializer.serializedSize(metadata.consensusMigrationState, version);
DataPlacements.serializer.serializedSize(metadata.placements, version);
if (version.isAtLeast(V2))
{
size += AccordTables.serializer.serializedSize(metadata.accordTables, version) +
AccordFastPath.serializer.serializedSize(metadata.accordFastPath, version);
size += AccordFastPath.serializer.serializedSize(metadata.accordFastPath, version) +
ConsensusMigrationState.serializer.serializedSize(metadata.consensusMigrationState, version);
}
size += LockedRanges.serializer.serializedSize(metadata.lockedRanges, version) +

View File

@ -39,7 +39,6 @@ public class MetadataKeys
public static final MetadataKey NODE_DIRECTORY = make(CORE_NS, "membership", "node_directory");
public static final MetadataKey TOKEN_MAP = make(CORE_NS, "ownership", "token_map");
public static final MetadataKey DATA_PLACEMENTS = make(CORE_NS, "ownership", "data_placements");
public static final MetadataKey ACCORD_TABLES = make(CORE_NS, "ownership", "accord_tables");
public static final MetadataKey ACCORD_FAST_PATH = make(CORE_NS, "ownership", "accord_fast_path");
public static final MetadataKey LOCKED_RANGES = make(CORE_NS, "sequences", "locked_ranges");
public static final MetadataKey IN_PROGRESS_SEQUENCES = make(CORE_NS, "sequences", "in_progress");
@ -49,7 +48,6 @@ public class MetadataKeys
NODE_DIRECTORY,
TOKEN_MAP,
DATA_PLACEMENTS,
ACCORD_TABLES,
ACCORD_FAST_PATH,
LOCKED_RANGES,
IN_PROGRESS_SEQUENCES,

View File

@ -29,12 +29,11 @@ import org.apache.cassandra.schema.DistributedSchema;
import org.apache.cassandra.schema.KeyspaceMetadata;
import org.apache.cassandra.schema.Keyspaces;
import org.apache.cassandra.service.accord.AccordFastPath;
import org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState;
import org.apache.cassandra.service.consensus.migration.ConsensusMigrationState;
import org.apache.cassandra.tcm.Commit.Replicator;
import org.apache.cassandra.tcm.log.Entry;
import org.apache.cassandra.tcm.log.LocalLog;
import org.apache.cassandra.tcm.membership.Directory;
import org.apache.cassandra.tcm.ownership.AccordTables;
import org.apache.cassandra.tcm.ownership.DataPlacements;
import org.apache.cassandra.tcm.ownership.PlacementProvider;
import org.apache.cassandra.tcm.ownership.TokenMap;
@ -175,11 +174,10 @@ public class StubClusterMetadataService extends ClusterMetadataService
Directory.EMPTY,
new TokenMap(partitioner),
DataPlacements.EMPTY,
AccordTables.EMPTY,
AccordFastPath.EMPTY,
LockedRanges.EMPTY,
InProgressSequences.EMPTY,
ConsensusTableMigrationState.ConsensusMigrationState.EMPTY,
ConsensusMigrationState.EMPTY,
ImmutableMap.of());
}
return new StubClusterMetadataService(new UniformRangePlacement(),

View File

@ -38,7 +38,6 @@ import org.apache.cassandra.tcm.sequences.LockedRanges;
import org.apache.cassandra.tcm.serialization.AsymmetricMetadataSerializer;
import org.apache.cassandra.tcm.serialization.VerboseMetadataSerializer;
import org.apache.cassandra.tcm.serialization.Version;
import org.apache.cassandra.tcm.transformations.AddAccordTable;
import org.apache.cassandra.tcm.transformations.AlterSchema;
import org.apache.cassandra.tcm.transformations.AlterTopology;
import org.apache.cassandra.tcm.transformations.Assassinate;
@ -53,7 +52,6 @@ import org.apache.cassandra.tcm.transformations.PrepareMove;
import org.apache.cassandra.tcm.transformations.PrepareReplace;
import org.apache.cassandra.tcm.transformations.ReconfigureAccordFastPath;
import org.apache.cassandra.tcm.transformations.Register;
import org.apache.cassandra.tcm.transformations.SetConsensusMigrationTargetProtocol;
import org.apache.cassandra.tcm.transformations.Startup;
import org.apache.cassandra.tcm.transformations.TriggerSnapshot;
import org.apache.cassandra.tcm.transformations.Unregister;
@ -240,11 +238,10 @@ public interface Transformation
CANCEL_CMS_RECONFIGURATION(34, () -> CancelCMSReconfiguration.serializer),
ALTER_TOPOLOGY(35, () -> AlterTopology.serializer),
ADD_ACCORD_TABLE(36, () -> AddAccordTable.serializer),
UPDATE_AVAILABILITY(37, () -> ReconfigureAccordFastPath.serializer),
BEGIN_CONSENSUS_MIGRATION_FOR_TABLE_AND_RANGE(38, () -> BeginConsensusMigrationForTableAndRange.serializer),
MAYBE_FINISH_CONSENSUS_MIGRATION_FOR_TABLE_AND_RANGE(39, () -> MaybeFinishConsensusMigrationForTableAndRange.serializer),
SET_CONSENSUS_MIGRATION_TARGET_PROTOCOL(40, () -> SetConsensusMigrationTargetProtocol.serializer)
UPDATE_AVAILABILITY(36, () -> ReconfigureAccordFastPath.serializer),
BEGIN_CONSENSUS_MIGRATION_FOR_TABLE_AND_RANGE(37, () -> BeginConsensusMigrationForTableAndRange.serializer),
MAYBE_FINISH_CONSENSUS_MIGRATION_FOR_TABLE_AND_RANGE(38, () -> MaybeFinishConsensusMigrationForTableAndRange.serializer),
;
private final Supplier<AsymmetricMetadataSerializer<Transformation, ? extends Transformation>> serializer;

View File

@ -54,7 +54,7 @@ import org.apache.cassandra.schema.Keyspaces;
import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.schema.SchemaKeyspace;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState.ConsensusMigrationState;
import org.apache.cassandra.service.consensus.migration.ConsensusMigrationState;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.tcm.MultiStepOperation;
@ -67,7 +67,6 @@ import org.apache.cassandra.tcm.membership.NodeAddresses;
import org.apache.cassandra.tcm.membership.NodeId;
import org.apache.cassandra.tcm.membership.NodeState;
import org.apache.cassandra.tcm.membership.NodeVersion;
import org.apache.cassandra.tcm.ownership.AccordTables;
import org.apache.cassandra.tcm.ownership.DataPlacements;
import org.apache.cassandra.tcm.ownership.TokenMap;
import org.apache.cassandra.tcm.ownership.UniformRangePlacement;
@ -297,7 +296,6 @@ public class GossipHelper
Directory.EMPTY,
new TokenMap(DatabaseDescriptor.getPartitioner()),
DataPlacements.empty(),
AccordTables.EMPTY,
AccordFastPath.EMPTY,
LockedRanges.EMPTY,
InProgressSequences.EMPTY,
@ -387,7 +385,6 @@ public class GossipHelper
directory,
tokenMap,
DataPlacements.empty(),
AccordTables.EMPTY,
AccordFastPath.EMPTY,
LockedRanges.EMPTY,
InProgressSequences.EMPTY,
@ -402,7 +399,6 @@ public class GossipHelper
directory,
tokenMap,
placements,
AccordTables.EMPTY,
AccordFastPath.EMPTY,
LockedRanges.EMPTY,
InProgressSequences.EMPTY,

View File

@ -1,109 +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.tcm.ownership;
import java.io.IOException;
import java.util.Arrays;
import com.google.common.collect.ImmutableSet;
import org.apache.cassandra.db.TypeSizes;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.tcm.MetadataValue;
import org.apache.cassandra.tcm.serialization.MetadataSerializer;
import org.apache.cassandra.tcm.serialization.Version;
public class AccordTables implements MetadataValue<AccordTables>
{
public static final AccordTables EMPTY = new AccordTables(Epoch.EMPTY, ImmutableSet.of());
private final Epoch lastModified;
private final ImmutableSet<TableId> tables;
public AccordTables(Epoch lastModified, ImmutableSet<TableId> tables)
{
this.lastModified = lastModified;
this.tables = tables;
}
public String toString()
{
return "AccordTables{" + lastModified + ", " + tables + '}';
}
public AccordTables withLastModified(Epoch epoch)
{
return new AccordTables(epoch, tables);
}
public Epoch lastModified()
{
return lastModified;
}
public boolean contains(TableId table)
{
return tables.contains(table);
}
public AccordTables with(TableId table)
{
if (tables.contains(table))
return this;
return new AccordTables(lastModified, ImmutableSet.<TableId>builder().addAll(tables).add(table).build());
}
public static final MetadataSerializer<AccordTables> serializer = new MetadataSerializer<AccordTables>()
{
public void serialize(AccordTables accordTables, DataOutputPlus out, Version version) throws IOException
{
int size = accordTables.tables.size();
out.writeUnsignedVInt32(size);
TableId[] tables = new TableId[size];
accordTables.tables.toArray(tables);
Arrays.sort(tables);
for (TableId table : tables)
table.serialize(out);
Epoch.serializer.serialize(accordTables.lastModified, out, version);
}
public AccordTables deserialize(DataInputPlus in, Version version) throws IOException
{
int size = in.readUnsignedVInt32();
ImmutableSet.Builder<TableId> builder = ImmutableSet.builder();
for (int i=0; i<size; i++)
builder.add(TableId.deserialize(in));
Epoch lastModified = Epoch.serializer.deserialize(in, version);
return new AccordTables(lastModified, builder.build());
}
public long serializedSize(AccordTables accordTables, Version version)
{
long size = TypeSizes.sizeofUnsignedVInt(accordTables.tables.size());
for (TableId table : accordTables.tables)
size += table.serializedSize();
size += Epoch.serializer.serializedSize(accordTables.lastModified, version);
return size;
}
};
}

View File

@ -1,91 +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.tcm.transformations;
import java.io.IOException;
import accord.utils.Invariants;
import org.apache.cassandra.exceptions.ExceptionCode;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.ClusterMetadataService;
import org.apache.cassandra.tcm.Transformation;
import org.apache.cassandra.tcm.sequences.LockedRanges;
import org.apache.cassandra.tcm.serialization.AsymmetricMetadataSerializer;
import org.apache.cassandra.tcm.serialization.Version;
// TODO (expected, interop): improve mechanism for adding tables (probably want table level granularity, option to not-auto-add, and option to remove)
public class AddAccordTable implements Transformation
{
private final TableId table;
public AddAccordTable(TableId table)
{
this.table = table;
}
public Kind kind()
{
return Kind.ADD_ACCORD_TABLE;
}
@Override
public Result execute(ClusterMetadata metadata)
{
if (metadata.accordTables.contains(table))
return new Rejected(ExceptionCode.ALREADY_EXISTS, table + " is already an accord table");
return Transformation.success(metadata.transformer().withAccordTable(table), LockedRanges.AffectedRanges.EMPTY);
}
public static void addTable(TableId table)
{
ClusterMetadataService.instance().commit(new AddAccordTable(table),
metadata -> null,
(code, message) -> {
Invariants.checkState(code == ExceptionCode.ALREADY_EXISTS,
"Expected %s, got %s", ExceptionCode.ALREADY_EXISTS, code);
return null;
});
}
public static final AsymmetricMetadataSerializer<Transformation, AddAccordTable> serializer = new AsymmetricMetadataSerializer<Transformation, AddAccordTable>()
{
public void serialize(Transformation t, DataOutputPlus out, Version version) throws IOException
{
assert t instanceof AddAccordTable;
AddAccordTable addTable = (AddAccordTable) t;
addTable.table.serialize(out);
}
public AddAccordTable deserialize(DataInputPlus in, Version version) throws IOException
{
return new AddAccordTable(TableId.deserialize(in));
}
public long serializedSize(Transformation t, Version version)
{
assert t instanceof AddAccordTable;
AddAccordTable addTable = (AddAccordTable) t;
return addTable.table.serializedSize();
}
};
}

View File

@ -19,20 +19,26 @@
package org.apache.cassandra.tcm.transformations;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Streams;
import org.apache.cassandra.config.AccordSpec;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.cql3.statements.schema.AlterSchemaStatement;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.exceptions.AlreadyExistsException;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.exceptions.InvalidRequestException;
@ -50,8 +56,7 @@ import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.schema.Tables;
import org.apache.cassandra.schema.ViewMetadata;
import org.apache.cassandra.schema.Views;
import org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState.ConsensusMigrationState;
import org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState.TableMigrationState;
import org.apache.cassandra.service.consensus.migration.ConsensusMigrationState;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.ClusterMetadata.Transformer;
import org.apache.cassandra.tcm.ClusterMetadataService;
@ -72,7 +77,6 @@ import static org.apache.cassandra.exceptions.ExceptionCode.CONFIG_ERROR;
import static org.apache.cassandra.exceptions.ExceptionCode.INVALID;
import static org.apache.cassandra.exceptions.ExceptionCode.SERVER_ERROR;
import static org.apache.cassandra.exceptions.ExceptionCode.SYNTAX_ERROR;
import static org.apache.cassandra.utils.Collectors3.toImmutableMap;
public class AlterSchema implements Transformation
{
@ -241,7 +245,7 @@ public class AlterSchema implements Transformation
});
next = next.with(newPlacementsBuilder.build());
}
next = maybeUpdateConsensusTableMigrationStateForDroppedTables(prev.consensusMigrationState, next, diff.altered, diff.dropped);
next = maybeUpdateConsensusMigrationState(prev.consensusMigrationState, next, diff.altered, diff.dropped);
return Transformation.success(next, LockedRanges.AffectedRanges.EMPTY);
}
@ -257,22 +261,69 @@ public class AlterSchema implements Transformation
return byReplication;
}
private Transformer maybeUpdateConsensusTableMigrationStateForDroppedTables(ConsensusMigrationState prev, Transformer next, ImmutableList<KeyspaceDiff> altered, Keyspaces dropped)
private Transformer maybeUpdateConsensusMigrationState(ConsensusMigrationState prev, Transformer next, ImmutableList<KeyspaceDiff> altered, Keyspaces dropped)
{
Set<TableId> tableIds = Streams.concat(
altered.stream().flatMap(diff -> diff.tables.dropped.stream().map(TableMetadata::id)),
dropped.stream().flatMap(ks -> ks.tables.stream().map(TableMetadata::id)))
ConsensusMigrationState migrationState = prev;
Set<TableId> droppedIds = Streams.concat(altered.stream().flatMap(diff -> diff.tables.dropped.stream().map(TableMetadata::id)),
dropped.stream().flatMap(ks -> ks.tables.stream().map(TableMetadata::id)))
.collect(toImmutableSet());
if (!droppedIds.isEmpty())
migrationState = migrationState.withMigrationsRemovedFor(droppedIds);
Set<TableId> completedIds = altered.stream()
.flatMap(diff -> diff.tables.altered.stream())
.filter(alt -> alt.before.params.transactionalMigrationFrom.isMigrating()
&& !alt.after.params.transactionalMigrationFrom.isMigrating())
.map(alt -> alt.after.id)
.collect(toImmutableSet());
if (tableIds.stream().anyMatch(prev.tableStates.keySet()::contains))
if (!completedIds.isEmpty())
migrationState = migrationState.withMigrationsCompletedFor(completedIds);
Map<TableId, TableMetadata> reversals = altered.stream()
.flatMap(diff -> diff.tables.altered.stream())
.filter(alt -> alt.before.params.transactionalMigrationFrom.from == alt.after.params.transactionalMode)
.map(alt -> alt.after)
.collect(Collectors.toMap(TableMetadata::id, Function.identity()));
// we treat explicitly switched migration types as a new migration here
Set<TableMetadata> started = altered.stream()
.flatMap(diff -> diff.tables.altered.stream())
.filter(alt -> !reversals.containsKey(alt.after.id))
.filter(alt -> alt.after.params.transactionalMigrationFrom.isMigrating()
&& !alt.before.params.transactionalMigrationFrom.isMigrating())
.map(alt -> alt.after)
.collect(Collectors.toUnmodifiableSet());
if (!started.isEmpty())
{
ImmutableMap<TableId, TableMigrationState> newTableStates =
prev.tableStates.entrySet().stream().filter(e -> !tableIds.contains(e.getKey())).collect(toImmutableMap());
return next.with(newTableStates);
}
else
{
return next;
List<Range<Token>> ranges;
AccordSpec.TransactionalRangeMigration migration = DatabaseDescriptor.getTransactionalRangeMigration();
switch (migration)
{
default: throw new IllegalStateException("Unhandled transactional range migration: " + migration);
case auto:
Token minToken = DatabaseDescriptor.getPartitioner().getMinimumToken();
ranges = Range.normalize(Collections.singletonList(new Range<>(minToken, minToken)));
break;
case explicit:
ranges = Collections.emptyList();
break;
}
if (!ranges.isEmpty())
migrationState = migrationState.withRangesMigrating(started, ranges, true);
}
migrationState = migrationState.withReversedMigrations(reversals, next.epoch());
if (migrationState != prev)
next = next.with(migrationState);
return next;
}
private static Iterable<TableMetadata> normaliseTableEpochs(Epoch nextEpoch, Stream<TableMetadata> tables)

View File

@ -19,15 +19,11 @@
package org.apache.cassandra.tcm.transformations;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.TypeSizes;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token;
@ -35,9 +31,11 @@ import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.consensus.migration.ConsensusMigrationTarget;
import org.apache.cassandra.service.consensus.migration.ConsensusTableMigration;
import org.apache.cassandra.service.consensus.migration.ConsensusMigrationState;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.tcm.Transformation;
import org.apache.cassandra.tcm.sequences.LockedRanges;
import org.apache.cassandra.tcm.serialization.AsymmetricMetadataSerializer;
@ -45,14 +43,10 @@ import org.apache.cassandra.tcm.serialization.Version;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState.ConsensusMigrationTarget;
import static org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState.TableMigrationState;
import static org.apache.cassandra.tcm.ClusterMetadata.Transformer;
import static org.apache.cassandra.utils.CollectionSerializers.deserializeList;
import static org.apache.cassandra.utils.CollectionSerializers.serializeCollection;
import static org.apache.cassandra.utils.CollectionSerializers.serializedCollectionSize;
import static org.apache.cassandra.utils.Collectors3.toImmutableMap;
public class BeginConsensusMigrationForTableAndRange implements Transformation
{
@ -88,20 +82,10 @@ public class BeginConsensusMigrationForTableAndRange implements Transformation
public Result execute(ClusterMetadata prev)
{
Map<TableId, TableMigrationState> tableStates = prev.consensusMigrationState.tableStates;
List<ColumnFamilyStore> columnFamilyStores = tables.stream().map(Schema.instance::getColumnFamilyStoreInstance).collect(toImmutableList());
Transformer transformer = prev.transformer();
Map<TableId, TableMigrationState> newStates = columnFamilyStores
.stream()
.map(cfs ->
tableStates.containsKey(cfs.getTableId()) ?
tableStates.get(cfs.getTableId()).withRangesMigrating(ranges, targetProtocol) :
new TableMigrationState(cfs.keyspace.getName(), cfs.name, cfs.getTableId(), targetProtocol, ImmutableSet.of(), ImmutableMap.of(Epoch.EMPTY, ranges)))
.collect(toImmutableMap(TableMigrationState::getTableId, Function.identity()));
return Transformation.success(transformer.with(newStates), LockedRanges.AffectedRanges.EMPTY);
Collection<TableMetadata> metadata = tables.stream().map(Schema.instance::getTableMetadata).collect(Collectors.toList());
ConsensusMigrationState consensusMigrationState = prev.consensusMigrationState.withRangesMigrating(metadata, ranges, false);
return Transformation.success(transformer.with(consensusMigrationState), LockedRanges.AffectedRanges.EMPTY);
}
static class Serializer implements AsymmetricMetadataSerializer<Transformation, BeginConsensusMigrationForTableAndRange>
@ -111,14 +95,14 @@ public class BeginConsensusMigrationForTableAndRange implements Transformation
{
BeginConsensusMigrationForTableAndRange v = (BeginConsensusMigrationForTableAndRange)t;
out.writeUTF(v.targetProtocol.toString());
ConsensusTableMigrationState.rangesSerializer.serialize(v.ranges, out, version);
ConsensusTableMigration.rangesSerializer.serialize(v.ranges, out, version);
serializeCollection(v.tables, out, version, TableId.metadataSerializer);
}
public BeginConsensusMigrationForTableAndRange deserialize(DataInputPlus in, Version version) throws IOException
{
ConsensusMigrationTarget targetProtocol = ConsensusMigrationTarget.fromString(in.readUTF());
List<Range<Token>> ranges = ConsensusTableMigrationState.rangesSerializer.deserialize(in, version);
List<Range<Token>> ranges = ConsensusTableMigration.rangesSerializer.deserialize(in, version);
List<TableId> tables = deserializeList(in, version, TableId.metadataSerializer);
return new BeginConsensusMigrationForTableAndRange(targetProtocol, ranges, tables);
}
@ -127,8 +111,8 @@ public class BeginConsensusMigrationForTableAndRange implements Transformation
{
BeginConsensusMigrationForTableAndRange v = (BeginConsensusMigrationForTableAndRange) t;
return TypeSizes.sizeof(v.targetProtocol.toString())
+ ConsensusTableMigrationState.rangesSerializer.serializedSize(v.ranges, version)
+ serializedCollectionSize(v.tables, version, TableId.metadataSerializer);
+ ConsensusTableMigration.rangesSerializer.serializedSize(v.ranges, version)
+ serializedCollectionSize(v.tables, version, TableId.metadataSerializer);
}
}
}

View File

@ -22,19 +22,29 @@ import java.io.IOException;
import java.util.List;
import javax.annotation.Nonnull;
import com.google.common.collect.ImmutableMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.db.TypeSizes;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.schema.DistributedSchema;
import org.apache.cassandra.schema.KeyspaceMetadata;
import org.apache.cassandra.schema.Keyspaces;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState;
import org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState.ConsensusMigrationRepairType;
import org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState.ConsensusMigrationTarget;
import org.apache.cassandra.schema.TableParams;
import org.apache.cassandra.service.consensus.migration.ConsensusMigrationRepairType;
import org.apache.cassandra.service.consensus.migration.ConsensusMigrationTarget;
import org.apache.cassandra.service.consensus.migration.ConsensusTableMigration;
import org.apache.cassandra.service.consensus.migration.ConsensusMigrationState;
import org.apache.cassandra.service.consensus.migration.TableMigrationState;
import org.apache.cassandra.service.consensus.migration.TransactionalMigrationFromMode;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.ClusterMetadata.Transformer;
import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.tcm.Transformation;
import org.apache.cassandra.tcm.sequences.LockedRanges;
@ -47,12 +57,12 @@ import static java.lang.String.format;
import static org.apache.cassandra.dht.Range.intersects;
import static org.apache.cassandra.dht.Range.normalize;
import static org.apache.cassandra.exceptions.ExceptionCode.INVALID;
import static org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState.ConsensusMigrationState;
import static org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState.TableMigrationState;
public class MaybeFinishConsensusMigrationForTableAndRange implements Transformation
{
private static final Logger logger = LoggerFactory.getLogger(MaybeFinishConsensusMigrationForTableAndRange.class);
public static Serializer serializer = new Serializer();
@Nonnull
@ -96,9 +106,27 @@ public class MaybeFinishConsensusMigrationForTableAndRange implements Transforma
return Kind.MAYBE_FINISH_CONSENSUS_MIGRATION_FOR_TABLE_AND_RANGE;
}
private static Transformer resetMigrationOnSchema(ClusterMetadata prev, Transformer transformer, String ksName, String tblName, TableId id)
{
Keyspaces schema = prev.schema.getKeyspaces();
KeyspaceMetadata keyspace = schema.getNullable(ksName);
TableMetadata table = null == keyspace
? null
: keyspace.getTableOrViewNullable(tblName);
if (table == null || !table.id.equals(id))
return transformer;
TableParams params = table.params.unbuild().transactionalMigrationFrom(TransactionalMigrationFromMode.none).build();
keyspace = keyspace.withSwapped(keyspace.tables.withSwapped(table.withSwapped(params)));
schema = schema.withAddedOrUpdated(keyspace);
return transformer.with(new DistributedSchema(schema));
}
public Result execute(@Nonnull ClusterMetadata metadata)
{
System.out.println("Completed repair " + repairType + " ranges " + repairedRanges);
logger.info("Completed repair {} ranges {}", repairType, repairedRanges);
checkNotNull(metadata, "clusterMetadata should not be null");
String ksAndCF = keyspace + "." + cf;
TableMetadata tbm = Schema.instance.getTableMetadata(keyspace, cf);
@ -106,7 +134,7 @@ public class MaybeFinishConsensusMigrationForTableAndRange implements Transforma
return new Rejected(INVALID, format("Table %s is not currently performing consensus migration", ksAndCF));
ConsensusMigrationState consensusMigrationState = metadata.consensusMigrationState;
ConsensusTableMigrationState.TableMigrationState tms = consensusMigrationState.tableStates.get(tbm.id);
TableMigrationState tms = consensusMigrationState.tableStates.get(tbm.id);
if (tms == null)
return new Rejected(INVALID, format("Table %s is not currently performing consensus migration", ksAndCF));
@ -122,9 +150,16 @@ public class MaybeFinishConsensusMigrationForTableAndRange implements Transforma
if (!intersects(tms.migratingRanges, normalizedRepairedRanges))
return new Rejected(INVALID, format("Table %s is migrating ranges %s, which doesn't include repaired ranges %s", ksAndCF, tms.migratingRanges, normalizedRepairedRanges));
TableMigrationState newTableMigrationState = tms.withRangesRepairedAtEpoch(normalizedRepairedRanges, minEpoch);
Transformer next = metadata.transformer();
ConsensusMigrationState migrationState = metadata.consensusMigrationState.withRangesRepairedAtEpoch(tbm, normalizedRepairedRanges, minEpoch);
next = next.with(migrationState);
return Transformation.success(metadata.transformer().with(ImmutableMap.of(newTableMigrationState.tableId, newTableMigrationState)), LockedRanges.AffectedRanges.EMPTY);
// reset the migration value on the table if the migration has completed
TableMigrationState tableState = migrationState.tableStates.get(tbm.id);
if (tableState == null || tableState.hasMigratedFullTokenRange(metadata.partitioner))
next = resetMigrationOnSchema(metadata, next, keyspace, cf, tbm.id);
return Transformation.success(next, LockedRanges.AffectedRanges.EMPTY);
}
static class Serializer implements AsymmetricMetadataSerializer<Transformation, MaybeFinishConsensusMigrationForTableAndRange>
@ -134,7 +169,7 @@ public class MaybeFinishConsensusMigrationForTableAndRange implements Transforma
MaybeFinishConsensusMigrationForTableAndRange v = (MaybeFinishConsensusMigrationForTableAndRange)t;
out.writeUTF(v.keyspace);
out.writeUTF(v.cf);
ConsensusTableMigrationState.rangesSerializer.serialize(v.repairedRanges, out, version);
ConsensusTableMigration.rangesSerializer.serialize(v.repairedRanges, out, version);
Epoch.serializer.serialize(v.minEpoch, out, version);
out.write(v.repairType.value);
}
@ -143,7 +178,7 @@ public class MaybeFinishConsensusMigrationForTableAndRange implements Transforma
{
String keyspace = in.readUTF();
String cf = in.readUTF();
List<Range<Token>> repairedRanges = ConsensusTableMigrationState.rangesSerializer.deserialize(in, version);
List<Range<Token>> repairedRanges = ConsensusTableMigration.rangesSerializer.deserialize(in, version);
Epoch minEpoch = Epoch.serializer.deserialize(in, version);
ConsensusMigrationRepairType repairType = ConsensusMigrationRepairType.fromValue(in.readByte());
return new MaybeFinishConsensusMigrationForTableAndRange(keyspace, cf, repairedRanges, minEpoch, repairType);
@ -153,10 +188,10 @@ public class MaybeFinishConsensusMigrationForTableAndRange implements Transforma
{
MaybeFinishConsensusMigrationForTableAndRange v = (MaybeFinishConsensusMigrationForTableAndRange)t;
return TypeSizes.sizeof(v.keyspace)
+ TypeSizes.sizeof(v.cf)
+ ConsensusTableMigrationState.rangesSerializer.serializedSize(v.repairedRanges, version)
+ Epoch.serializer.serializedSize(v.minEpoch)
+ TypeSizes.sizeof(v.repairType.value);
+ TypeSizes.sizeof(v.cf)
+ ConsensusTableMigration.rangesSerializer.serializedSize(v.repairedRanges, version)
+ Epoch.serializer.serializedSize(v.minEpoch)
+ TypeSizes.sizeof(v.repairType.value);
}
}
}

View File

@ -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.tcm.transformations;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import javax.annotation.Nonnull;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.TypeSizes;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState.ConsensusMigrationTarget;
import org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState.TableMigrationState;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.ClusterMetadata.Transformer;
import org.apache.cassandra.tcm.Transformation;
import org.apache.cassandra.tcm.sequences.LockedRanges;
import org.apache.cassandra.tcm.serialization.AsymmetricMetadataSerializer;
import org.apache.cassandra.tcm.serialization.Version;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState.ConsensusMigrationTarget.reset;
import static org.apache.cassandra.tcm.Transformation.Kind.SET_CONSENSUS_MIGRATION_TARGET_PROTOCOL;
import static org.apache.cassandra.utils.CollectionSerializers.deserializeList;
import static org.apache.cassandra.utils.CollectionSerializers.serializeCollection;
import static org.apache.cassandra.utils.CollectionSerializers.serializedCollectionSize;
import static org.apache.cassandra.utils.Collectors3.toImmutableMap;
/*
* Narrowly focused on setting or changing the consensus migration protocol. The real use case
* is when a migration is already in progress or done and you want to change the target.
*/
public class SetConsensusMigrationTargetProtocol implements Transformation
{
public static Serializer serializer = new Serializer();
@Nonnull
public final ConsensusMigrationTarget targetProtocol;
@Nonnull
public final List<TableId> tables;
public SetConsensusMigrationTargetProtocol(@Nonnull ConsensusMigrationTarget targetProtocol,
@Nonnull List<TableId> tables)
{
this.targetProtocol = targetProtocol;
this.tables = tables;
}
@Override
public Kind kind()
{
return SET_CONSENSUS_MIGRATION_TARGET_PROTOCOL;
}
@Override
public Result execute(ClusterMetadata metadata)
{
Map<TableId, TableMigrationState> tableStates = metadata.consensusMigrationState.tableStates;
List<ColumnFamilyStore> columnFamilyStores = tables.stream().map(Schema.instance::getColumnFamilyStoreInstance).collect(toImmutableList());
Transformer transformer = metadata.transformer();
Map<TableId, TableMigrationState> newStates;
if (targetProtocol == reset)
{
newStates = tableStates.entrySet().stream().filter(entry -> !tables.contains(entry.getKey())).collect(toImmutableMap());
}
else
{
newStates = columnFamilyStores
.stream()
.map(cfs ->
tableStates.containsKey(cfs.getTableId()) ?
tableStates.get(cfs.getTableId()).withMigrationTarget(targetProtocol) :
new TableMigrationState(cfs.keyspace.getName(), cfs.name, cfs.getTableId(), targetProtocol, ImmutableSet.of(), ImmutableMap.of()))
.collect(toImmutableMap(TableMigrationState::getTableId, Function.identity()));
}
return Transformation.success(transformer.with(newStates, targetProtocol == reset ? false : true), LockedRanges.AffectedRanges.EMPTY);
}
static class Serializer implements AsymmetricMetadataSerializer<Transformation, SetConsensusMigrationTargetProtocol>
{
public void serialize(Transformation t, DataOutputPlus out, Version version) throws IOException
{
SetConsensusMigrationTargetProtocol v = (SetConsensusMigrationTargetProtocol)t;
out.writeUTF(v.targetProtocol.toString());
serializeCollection(v.tables, out, version, TableId.metadataSerializer);
}
public SetConsensusMigrationTargetProtocol deserialize(DataInputPlus in, Version version) throws IOException
{
ConsensusMigrationTarget targetProtocol = ConsensusMigrationTarget.fromString(in.readUTF());
List<TableId> tables = deserializeList(in, version, TableId.metadataSerializer);
return new SetConsensusMigrationTargetProtocol(targetProtocol, tables);
}
public long serializedSize(Transformation t, Version version)
{
SetConsensusMigrationTargetProtocol v = (SetConsensusMigrationTargetProtocol) t;
return TypeSizes.sizeof(v.targetProtocol.toString())
+ serializedCollectionSize(v.tables, version, TableId.metadataSerializer);
}
}
}

View File

@ -275,7 +275,6 @@ public class NodeTool
.withDescription("List and mark ranges as migrating between consensus protocols")
.withDefaultCommand(CassHelp.class)
.withCommand(ConsensusMigrationAdmin.BeginMigration.class)
.withCommands(ConsensusMigrationAdmin.SetTargetProtocol.class)
.withCommands(ConsensusMigrationAdmin.ListCmd.class)
.withCommands(ConsensusMigrationAdmin.FinishMigration.class);

View File

@ -31,9 +31,7 @@ import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool;
import static com.google.common.base.Preconditions.checkArgument;
import static java.util.Collections.emptyList;
import static java.util.Collections.singleton;
import static java.util.Collections.singletonList;
/**
* For managing migration from one consensus protocol to another.
@ -122,25 +120,4 @@ public abstract class ConsensusMigrationAdmin extends NodeTool.NodeToolCmd
probe.output().out.printf("Finished consensus migration range (%s) of keyspaces %s and tables %s%n", maybeRangesStr, keyspaceNames, maybeTableNames);
}
}
@Command(name = "set-target-protocol", description = "Set or change the target consensus protocol of the specified tables. If a migration is in progress then the migration will be reversed with migrating ranges still migrating, unmigrated ranges marked as migrated, and migrating ranges will need migration. Be aware that if no migration was in progress for a table it will immediately cause the table to run on the target protocol because the ranges requiring migration are derived from the migrated ranges that don't exist.")
public static class SetTargetProtocol extends ConsensusMigrationAdmin
{
@Arguments(usage = "[<keyspace> <tables>...]", description = "The keyspace followed by one or many tables")
private List<String> schemaArgs = new ArrayList<>();
@Option(title = "target_protocol", name = {"-tp", "--target-protocol"}, description = "Use -tp to specify what consensus protocol should be migrated to", required=true)
private String targetProtocol = null;
@Option(title = "force_completion", name = {"-f", "--force-completion"}, description = "Forces migration state for all ranges of the specified table regardless of whether migration completed successfully or not. Should only be used if table is empty or has had no writes since last repair.")
private boolean forceCompletion = false;
protected void execute(NodeProbe probe)
{
checkArgument(schemaArgs.size() >= 2, "Must specify a keyspace and at least one table");
List<String> keyspaceNames = schemaArgs.size() > 0 ? singletonList(schemaArgs.get(0)) : emptyList();
List<String> maybeTableNames = schemaArgs.size() > 1 ? schemaArgs.subList(1, schemaArgs.size()) : null;
probe.getStorageService().setConsensusMigrationTargetProtocol(targetProtocol, keyspaceNames, maybeTableNames);
}
}
}

View File

@ -27,10 +27,9 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.google.common.util.concurrent.FutureCallback;
import org.apache.cassandra.distributed.api.*;
import org.apache.cassandra.distributed.test.accord.AccordTestBase;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.*;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
@ -46,15 +45,11 @@ import org.apache.cassandra.db.partitions.PartitionUpdate;
import org.apache.cassandra.dht.Murmur3Partitioner;
import org.apache.cassandra.dht.Token;
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.ICoordinator;
import org.apache.cassandra.distributed.api.IInstanceConfig;
import org.apache.cassandra.distributed.api.IMessageFilters.Filter;
import org.apache.cassandra.distributed.api.TokenSupplier;
import org.apache.cassandra.distributed.shared.NetworkTopology;
import org.apache.cassandra.locator.Replica;
import org.apache.cassandra.locator.ReplicaPlan;
import org.apache.cassandra.service.consensus.TransactionalMode;
import org.apache.cassandra.service.reads.repair.BlockingReadRepair;
import org.apache.cassandra.service.reads.repair.ReadRepairStrategy;
import org.apache.cassandra.utils.concurrent.Condition;
@ -80,6 +75,34 @@ import static org.junit.Assert.fail;
public class ReadRepairTest extends TestBaseImpl
{
private static Cluster cluster;
private static int tableNum = 0;
private String tableName;
@BeforeClass
public static void beforeClass() throws Throwable
{
cluster = init(Cluster.create(3, c -> c.with(Feature.GOSSIP, Feature.NETWORK)));
}
@AfterClass
public static void afterClass() throws Throwable
{
if (cluster != null)
cluster.close();
}
private void incrementTableName()
{
tableName = "tbl" + tableNum++;
}
@Before
public void setup()
{
incrementTableName();
}
/**
* Tests basic behaviour of read repair with {@code BLOCKING} read repair strategy.
*/
@ -87,6 +110,7 @@ public class ReadRepairTest extends TestBaseImpl
public void testBlockingReadRepair() throws Throwable
{
testReadRepair(ReadRepairStrategy.BLOCKING, false);
incrementTableName();
testReadRepair(ReadRepairStrategy.BLOCKING, true);
}
/**
@ -106,68 +130,63 @@ public class ReadRepairTest extends TestBaseImpl
private void testReadRepair(ReadRepairStrategy strategy, boolean brrThroughAccord) throws Throwable
{
try (Cluster cluster = init(Cluster.create(3, config -> config.set("non_serial_write_strategy", brrThroughAccord ? "migration" : "normal"))))
{
cluster.schemaChange(withKeyspace("CREATE TABLE %s.t (k int, c int, v int, PRIMARY KEY (k, c)) " +
String.format("WITH read_repair='%s'", strategy)));
AccordTestBase.ensureTableIsAccordManaged(cluster, KEYSPACE, "t");
TransactionalMode transactionalMode = brrThroughAccord ? TransactionalMode.unsafe_writes : TransactionalMode.off;
cluster.schemaChange(withKeyspace("CREATE TABLE %s." + tableName + " (k int, c int, v int, PRIMARY KEY (k, c)) WITH transactional_mode='" + transactionalMode.toString().toLowerCase() + '\'' +
String.format(" AND read_repair='%s'", strategy)));
AccordTestBase.ensureTableIsAccordManaged(cluster, KEYSPACE, "t");
Object[] row = row(1, 1, 1);
String insertQuery = withKeyspace("INSERT INTO %s.t (k, c, v) VALUES (?, ?, ?)");
String selectQuery = withKeyspace("SELECT * FROM %s.t WHERE k=1");
Object[] row = row(1, 1, 1);
String insertQuery = withKeyspace("INSERT INTO %s." + tableName + " (k, c, v) VALUES (?, ?, ?)");
String selectQuery = withKeyspace("SELECT * FROM %s." + tableName + " WHERE k=1");
// insert data in two nodes, simulating a quorum write that has missed one node
cluster.get(1).executeInternal(insertQuery, row);
cluster.get(2).executeInternal(insertQuery, row);
// insert data in two nodes, simulating a quorum write that has missed one node
cluster.get(1).executeInternal(insertQuery, row);
cluster.get(2).executeInternal(insertQuery, row);
// verify that the third node doesn't have the row
// verify that the third node doesn't have the row
assertRows(cluster.get(3).executeInternal(selectQuery));
// read with CL=QUORUM to trigger read repair, force 3 to be involved in the read so that read repair
// will occur
Filter blockReadFromOne = cluster.filters().inbound().from(3).to(1).verbs(READ_REQ.id).drop();
assertRows(cluster.coordinator(3).execute(selectQuery, QUORUM), row);
blockReadFromOne.off();
// verify whether the coordinator has the repaired row depending on the read repair strategy
if (strategy == ReadRepairStrategy.NONE)
assertRows(cluster.get(3).executeInternal(selectQuery));
// read with CL=QUORUM to trigger read repair, force 3 to be involved in the read so that read repair
// will occur
Filter blockReadFromOne = cluster.filters().inbound().from(3).to(1).verbs(READ_REQ.id).drop();
assertRows(cluster.coordinator(3).execute(selectQuery, QUORUM), row);
blockReadFromOne.off();
// verify whether the coordinator has the repaired row depending on the read repair strategy
if (strategy == ReadRepairStrategy.NONE)
assertRows(cluster.get(3).executeInternal(selectQuery));
else
assertRows(cluster.get(3).executeInternal(selectQuery), row);
}
else
assertRows(cluster.get(3).executeInternal(selectQuery), row);
}
@Test
public void readRepairTimeoutTest() throws Throwable
{
final long reducedReadTimeout = 3000L;
try (Cluster cluster = init(builder().withNodes(3).start()))
cluster.forEach(i -> i.runOnInstance(() -> DatabaseDescriptor.setReadRpcTimeout(reducedReadTimeout)));
cluster.schemaChange("CREATE TABLE " + KEYSPACE + "." + tableName + " (pk int, ck int, v int, PRIMARY KEY (pk, ck)) WITH read_repair='blocking'");
cluster.get(1).executeInternal("INSERT INTO " + KEYSPACE + "." + tableName + " (pk, ck, v) VALUES (1, 1, 1)");
cluster.get(2).executeInternal("INSERT INTO " + KEYSPACE + "." + tableName + " (pk, ck, v) VALUES (1, 1, 1)");
assertRows(cluster.get(3).executeInternal("SELECT * FROM " + KEYSPACE + "." + tableName + " WHERE pk = 1"));
cluster.verbs(READ_REPAIR_RSP).to(1).drop();
final long start = currentTimeMillis();
try
{
cluster.forEach(i -> i.runOnInstance(() -> DatabaseDescriptor.setReadRpcTimeout(reducedReadTimeout)));
cluster.schemaChange("CREATE TABLE " + KEYSPACE + ".tbl (pk int, ck int, v int, PRIMARY KEY (pk, ck)) WITH read_repair='blocking'");
cluster.get(1).executeInternal("INSERT INTO " + KEYSPACE + ".tbl (pk, ck, v) VALUES (1, 1, 1)");
cluster.get(2).executeInternal("INSERT INTO " + KEYSPACE + ".tbl (pk, ck, v) VALUES (1, 1, 1)");
assertRows(cluster.get(3).executeInternal("SELECT * FROM " + KEYSPACE + ".tbl WHERE pk = 1"));
cluster.verbs(READ_REPAIR_RSP).to(1).drop();
final long start = currentTimeMillis();
try
{
cluster.coordinator(1).execute("SELECT * FROM " + KEYSPACE + ".tbl WHERE pk = 1", ConsistencyLevel.ALL);
fail("Read timeout expected but it did not occur");
}
catch (Exception ex)
{
// the containing exception class was loaded by another class loader. Comparing the message as a workaround to assert the exception
assertTrue(ex.getClass().toString().contains("ReadTimeoutException"));
long actualTimeTaken = currentTimeMillis() - start;
long magicDelayAmount = 100L; // it might not be the best way to check if the time taken is around the timeout value.
// Due to the delays, the actual time taken from client perspective is slighly more than the timeout value
assertTrue(actualTimeTaken > reducedReadTimeout);
// But it should not exceed too much
assertTrue(actualTimeTaken < reducedReadTimeout + magicDelayAmount);
assertRows(cluster.get(3).executeInternal("SELECT * FROM " + KEYSPACE + ".tbl WHERE pk = 1"),
row(1, 1, 1)); // the partition happened when the repaired node sending back ack. The mutation should be in fact applied.
}
cluster.coordinator(1).execute("SELECT * FROM " + KEYSPACE + "." + tableName + " WHERE pk = 1", ConsistencyLevel.ALL);
fail("Read timeout expected but it did not occur");
}
catch (Exception ex)
{
// the containing exception class was loaded by another class loader. Comparing the message as a workaround to assert the exception
assertTrue(ex.getClass().toString().contains("ReadTimeoutException"));
long actualTimeTaken = currentTimeMillis() - start;
long magicDelayAmount = 100L; // it might not be the best way to check if the time taken is around the timeout value.
// Due to the delays, the actual time taken from client perspective is slighly more than the timeout value
assertTrue(actualTimeTaken > reducedReadTimeout);
// But it should not exceed too much
assertTrue(actualTimeTaken < reducedReadTimeout + magicDelayAmount);
assertRows(cluster.get(3).executeInternal("SELECT * FROM " + KEYSPACE + "." + tableName + " WHERE pk = 1"),
row(1, 1, 1)); // the partition happened when the repaired node sending back ack. The mutation should be in fact applied.
}
}
@ -176,20 +195,20 @@ public class ReadRepairTest extends TestBaseImpl
{
try (Cluster cluster = init(builder().withNodes(3).start()))
{
cluster.schemaChange("CREATE TABLE " + KEYSPACE + ".tbl (pk int, ck int, v int, PRIMARY KEY (pk, ck)) WITH read_repair='blocking'");
cluster.schemaChange("CREATE TABLE " + KEYSPACE + "." + tableName + " (pk int, ck int, v int, PRIMARY KEY (pk, ck)) WITH read_repair='blocking'");
for (int i = 1 ; i <= 2 ; ++i)
cluster.get(i).executeInternal("INSERT INTO " + KEYSPACE + ".tbl (pk, ck, v) VALUES (1, 1, 1)");
cluster.get(i).executeInternal("INSERT INTO " + KEYSPACE + "." + tableName + " (pk, ck, v) VALUES (1, 1, 1)");
assertRows(cluster.get(3).executeInternal("SELECT * FROM " + KEYSPACE + ".tbl WHERE pk = 1"));
assertRows(cluster.get(3).executeInternal("SELECT * FROM " + KEYSPACE + "." + tableName + " WHERE pk = 1"));
cluster.filters().verbs(READ_REPAIR_REQ.id).to(3).drop();
assertRows(cluster.coordinator(1).execute("SELECT * FROM " + KEYSPACE + ".tbl WHERE pk = 1",
assertRows(cluster.coordinator(1).execute("SELECT * FROM " + KEYSPACE + "." + tableName + " WHERE pk = 1",
ConsistencyLevel.QUORUM),
row(1, 1, 1));
// Data was not repaired
assertRows(cluster.get(3).executeInternal("SELECT * FROM " + KEYSPACE + ".tbl WHERE pk = 1"));
assertRows(cluster.get(3).executeInternal("SELECT * FROM " + KEYSPACE + "." + tableName + " WHERE pk = 1"));
}
}

View File

@ -28,7 +28,6 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.IntStream;
import org.apache.cassandra.distributed.test.accord.AccordTestBase;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
@ -37,16 +36,13 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.apache.cassandra.config.Config.LWTStrategy;
import org.apache.cassandra.config.Config.NonSerialWriteStrategy;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.dht.Murmur3Partitioner;
import org.apache.cassandra.distributed.Cluster;
import org.apache.cassandra.distributed.api.ConsistencyLevel;
import org.apache.cassandra.distributed.api.IInvokableInstance;
import org.apache.cassandra.distributed.shared.AssertUtils;
import org.apache.cassandra.service.consensus.TransactionalMode;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.Pair;
import static com.google.common.collect.Iterators.toArray;
import static java.lang.String.format;
@ -88,17 +84,17 @@ public class ShortReadProtectionTest extends TestBaseImpl
public boolean paging;
@Parameterized.Parameter(3)
public Pair<LWTStrategy, NonSerialWriteStrategy> transactionStrategies;
public TransactionalMode transactionalMode;
@Parameterized.Parameters(name = "{index}: read_cl={0} flush={1} paging={2}, transactionStrategies={3}")
@Parameterized.Parameters(name = "{index}: read_cl={0} flush={1} paging={2}, transactionalMode={3}")
public static Collection<Object[]> data()
{
List<Object[]> result = new ArrayList<>();
for (Pair<LWTStrategy, NonSerialWriteStrategy> transactionStrategies : Arrays.asList(Pair.create(LWTStrategy.accord, NonSerialWriteStrategy.migration), Pair.create(LWTStrategy.migration, NonSerialWriteStrategy.normal)))
for (TransactionalMode mode : TransactionalMode.values())
for (ConsistencyLevel readConsistencyLevel : Arrays.asList(ALL, QUORUM, SERIAL))
for (boolean flush : BOOLEANS)
for (boolean paging : BOOLEANS)
result.add(new Object[]{ readConsistencyLevel, flush, paging, transactionStrategies});
result.add(new Object[]{ readConsistencyLevel, flush, paging, mode});
return result;
}
@ -123,17 +119,14 @@ public class ShortReadProtectionTest extends TestBaseImpl
@Before
public void setupTester()
{
String lwtStrategy = transactionStrategies.left.toString();
String nonSerialWriteStrategy = transactionStrategies.right.toString();
cluster.forEach(node -> {
node.runOnInstance(() -> {
DatabaseDescriptor.setLWTStrategy(LWTStrategy.valueOf(lwtStrategy));
DatabaseDescriptor.setNonSerialWriteStrategy(NonSerialWriteStrategy.valueOf(nonSerialWriteStrategy));
});
});
tester = new Tester(readConsistencyLevel, flush, paging);
}
private String transactionalModeCQL()
{
return " WITH transactional_mode='" + transactionalMode + '\'';
}
@After
public void teardownTester()
{
@ -150,7 +143,7 @@ public class ShortReadProtectionTest extends TestBaseImpl
@Test
public void testSkinnyTableWithoutLiveRows()
{
tester.createTable("CREATE TABLE %s (id int PRIMARY KEY)")
tester.createTable("CREATE TABLE %s (id int PRIMARY KEY)" + transactionalModeCQL())
.allNodes("INSERT INTO %s (id) VALUES (0) USING TIMESTAMP 0")
.toNode1("DELETE FROM %s WHERE id = 0")
.assertRows("SELECT DISTINCT id FROM %s WHERE id = 0")
@ -167,7 +160,7 @@ public class ShortReadProtectionTest extends TestBaseImpl
@Test
public void testSkinnyTableWithLiveRows()
{
tester.createTable("CREATE TABLE %s (id int PRIMARY KEY)")
tester.createTable("CREATE TABLE %s (id int PRIMARY KEY)" + transactionalModeCQL())
.allNodes(0, 10, i -> format("INSERT INTO %%s (id) VALUES (%d) USING TIMESTAMP 0", i)) // order is 5,1,8,0,2,4,7,6,9,3
.toNode1("DELETE FROM %s WHERE id IN (1, 0, 4, 6, 3)") // delete every other row
.assertRows("SELECT DISTINCT token(id), id FROM %s",
@ -184,7 +177,7 @@ public class ShortReadProtectionTest extends TestBaseImpl
@Test
public void testSkinnyTableWithComplementaryDeletions()
{
tester.createTable("CREATE TABLE %s (id int PRIMARY KEY)")
tester.createTable("CREATE TABLE %s (id int PRIMARY KEY)" + transactionalModeCQL())
.allNodes(0, 10, i -> format("INSERT INTO %%s (id) VALUES (%d) USING TIMESTAMP 0", i)) // order is 5,1,8,0,2,4,7,6,9,3
.toNode1("DELETE FROM %s WHERE id IN (5, 8, 2, 7, 9)") // delete every other row
.toNode2("DELETE FROM %s WHERE id IN (1, 0, 4, 6)") // delete every other row but the last one
@ -202,7 +195,7 @@ public class ShortReadProtectionTest extends TestBaseImpl
@Test
public void testMultipleMissedRows()
{
tester.createTable("CREATE TABLE %s (pk int, ck int, PRIMARY KEY (pk, ck))")
tester.createTable("CREATE TABLE %s (pk int, ck int, PRIMARY KEY (pk, ck))" + transactionalModeCQL())
.allNodes(0, 4, i -> format("INSERT INTO %%s (pk, ck) VALUES (0, %d) USING TIMESTAMP 0", i))
.toNode1("DELETE FROM %s WHERE pk = 0 AND ck IN (1, 2, 3)",
"INSERT INTO %s (pk, ck) VALUES (0, 5)")
@ -221,7 +214,7 @@ public class ShortReadProtectionTest extends TestBaseImpl
@Test
public void testAscendingOrder()
{
tester.createTable("CREATE TABLE %s (k int, c int, v int, PRIMARY KEY(k, c))")
tester.createTable("CREATE TABLE %s (k int, c int, v int, PRIMARY KEY(k, c))" + transactionalModeCQL())
.allNodes(1, 10, i -> format("INSERT INTO %%s (k, c, v) VALUES (0, %d, %d) USING TIMESTAMP 0", i, i * 10))
.toNode1("DELETE FROM %s WHERE k=0 AND c=1")
.toNode2("DELETE FROM %s WHERE k=0 AND c=2")
@ -243,7 +236,7 @@ public class ShortReadProtectionTest extends TestBaseImpl
@Test
public void testDescendingOrder()
{
tester.createTable("CREATE TABLE %s (k int, c int, v int, PRIMARY KEY(k, c))")
tester.createTable("CREATE TABLE %s (k int, c int, v int, PRIMARY KEY(k, c))" + transactionalModeCQL())
.allNodes(1, 10, i -> format("INSERT INTO %%s (k, c, v) VALUES (0, %d, %d) USING TIMESTAMP 0", i, i * 10))
.toNode1("DELETE FROM %s WHERE k=0 AND c=7")
.toNode2("DELETE FROM %s WHERE k=0 AND c=8")
@ -266,7 +259,7 @@ public class ShortReadProtectionTest extends TestBaseImpl
@Test
public void testDeletePartition()
{
tester.createTable("CREATE TABLE %s (k int, c int, v int, PRIMARY KEY(k, c))")
tester.createTable("CREATE TABLE %s (k int, c int, v int, PRIMARY KEY(k, c))" + transactionalModeCQL())
.allNodes("INSERT INTO %s (k, c, v) VALUES (0, 1, 10) USING TIMESTAMP 0",
"INSERT INTO %s (k, c, v) VALUES (0, 2, 20) USING TIMESTAMP 0")
.toNode2("DELETE FROM %s WHERE k=0")
@ -279,7 +272,7 @@ public class ShortReadProtectionTest extends TestBaseImpl
@Test
public void testDeletePartitionWithStatic()
{
tester.createTable("CREATE TABLE %s (k int, c int, v int, s int STATIC, PRIMARY KEY(k, c))")
tester.createTable("CREATE TABLE %s (k int, c int, v int, s int STATIC, PRIMARY KEY(k, c))" + transactionalModeCQL())
.allNodes("INSERT INTO %s (k, c, v, s) VALUES (0, 1, 10, 100) USING TIMESTAMP 0",
"INSERT INTO %s (k, c, v) VALUES (0, 2, 20) USING TIMESTAMP 0")
.toNode2("DELETE FROM %s WHERE k=0")
@ -292,7 +285,7 @@ public class ShortReadProtectionTest extends TestBaseImpl
@Test
public void testDeleteClustering()
{
tester.createTable("CREATE TABLE %s (k int, c int, v int, PRIMARY KEY(k, c))")
tester.createTable("CREATE TABLE %s (k int, c int, v int, PRIMARY KEY(k, c))" + transactionalModeCQL())
.allNodes("INSERT INTO %s (k, c, v) VALUES (0, 1, 10) USING TIMESTAMP 0",
"INSERT INTO %s (k, c, v) VALUES (0, 2, 20) USING TIMESTAMP 0")
.toNode2("DELETE FROM %s WHERE k=0 AND c=1")
@ -307,7 +300,7 @@ public class ShortReadProtectionTest extends TestBaseImpl
@Test
public void testDeleteClusteringWithStatic()
{
tester.createTable("CREATE TABLE %s (k int, c int, v int, s int STATIC, PRIMARY KEY(k, c))")
tester.createTable("CREATE TABLE %s (k int, c int, v int, s int STATIC, PRIMARY KEY(k, c))" + transactionalModeCQL())
.allNodes("INSERT INTO %s (k, c, v, s) VALUES (0, 1, 10, 100) USING TIMESTAMP 0",
"INSERT INTO %s (k, c, v) VALUES (0, 2, 20) USING TIMESTAMP 0")
.toNode2("DELETE FROM %s WHERE k=0 AND c=1")
@ -324,7 +317,7 @@ public class ShortReadProtectionTest extends TestBaseImpl
@Test
public void testGroupByRegularRow()
{
tester.createTable("CREATE TABLE %s (pk int, ck int, PRIMARY KEY (pk, ck))")
tester.createTable("CREATE TABLE %s (pk int, ck int, PRIMARY KEY (pk, ck))" + transactionalModeCQL())
.toNode1("INSERT INTO %s (pk, ck) VALUES (1, 1) USING TIMESTAMP 0",
"DELETE FROM %s WHERE pk=0 AND ck=0",
"INSERT INTO %s (pk, ck) VALUES (2, 2) USING TIMESTAMP 0")
@ -347,7 +340,7 @@ public class ShortReadProtectionTest extends TestBaseImpl
@Test
public void testGroupByStaticRow()
{
tester.createTable("CREATE TABLE %s (pk int, ck int, s int static, PRIMARY KEY (pk, ck))")
tester.createTable("CREATE TABLE %s (pk int, ck int, s int static, PRIMARY KEY (pk, ck))" + transactionalModeCQL())
.toNode1("INSERT INTO %s (pk, s) VALUES (1, 1) USING TIMESTAMP 0",
"INSERT INTO %s (pk, s) VALUES (0, null)",
"INSERT INTO %s (pk, s) VALUES (2, 2) USING TIMESTAMP 0")
@ -370,7 +363,7 @@ public class ShortReadProtectionTest extends TestBaseImpl
@Test
public void testSkipEarlyTermination()
{
tester.createTable("CREATE TABLE %s (pk int, ck int, PRIMARY KEY (pk, ck))")
tester.createTable("CREATE TABLE %s (pk int, ck int, PRIMARY KEY (pk, ck))" + transactionalModeCQL())
.toNode1("INSERT INTO %s (pk, ck) VALUES (0, 0)")
.toNode2("DELETE FROM %s WHERE pk = 0 AND ck IN (1, 2)")
.assertRows("SELECT DISTINCT pk FROM %s", row(0));
@ -387,7 +380,7 @@ public class ShortReadProtectionTest extends TestBaseImpl
@Test
public void testSkipEarlyTerminationRows()
{
tester.createTable("CREATE TABLE %s (pk int, ck int, PRIMARY KEY (pk, ck))")
tester.createTable("CREATE TABLE %s (pk int, ck int, PRIMARY KEY (pk, ck))" + transactionalModeCQL())
.toNode1("INSERT INTO %s (pk, ck) VALUES (0, 0) USING TIMESTAMP 0",
"INSERT INTO %s (pk, ck) VALUES (0, 1) USING TIMESTAMP 0",
"INSERT INTO %s (pk, ck) VALUES (2, 0) USING TIMESTAMP 0",
@ -411,7 +404,7 @@ public class ShortReadProtectionTest extends TestBaseImpl
@Test
public void testSkipEarlyTerminationPartitions()
{
tester.createTable("CREATE TABLE %s (pk int, ck int, PRIMARY KEY (pk, ck))")
tester.createTable("CREATE TABLE %s (pk int, ck int, PRIMARY KEY (pk, ck))" + transactionalModeCQL())
.toNode1("INSERT INTO %s (pk, ck) VALUES (0, 0) USING TIMESTAMP 0",
"INSERT INTO %s (pk, ck) VALUES (0, 1) USING TIMESTAMP 0",
"DELETE FROM %s USING TIMESTAMP 42 WHERE pk = 2 AND ck IN (0, 1)")
@ -455,8 +448,7 @@ public class ShortReadProtectionTest extends TestBaseImpl
private Tester createTable(String query)
{
cluster.schemaChange(format(query) + " WITH read_repair='NONE'");
AccordTestBase.ensureTableIsAccordManaged(cluster, KEYSPACE, table);
cluster.schemaChange(format(query) + " AND read_repair='NONE'");
return this;
}

View File

@ -206,7 +206,7 @@ public class AccordBootstrapTest extends TestBaseImpl
}
cluster.schemaChange("CREATE KEYSPACE ks WITH REPLICATION={'class':'SimpleStrategy', 'replication_factor':2}");
cluster.schemaChange("CREATE TABLE ks.tbl (k int, c int, v int, primary key(k, c))");
cluster.schemaChange("CREATE TABLE ks.tbl (k int, c int, v int, primary key(k, c)) WITH transactional_mode='full'");
long schemaChangeMax = maxEpoch(cluster);
for (IInvokableInstance node : cluster)
@ -384,7 +384,7 @@ public class AccordBootstrapTest extends TestBaseImpl
}
cluster.schemaChange("CREATE KEYSPACE ks WITH REPLICATION={'class':'SimpleStrategy', 'replication_factor':2}");
cluster.schemaChange("CREATE TABLE ks.tbl (k int, c int, v int, primary key(k, c))");
cluster.schemaChange("CREATE TABLE ks.tbl (k int, c int, v int, primary key(k, c)) WITH transactional_mode='full'");
long schemaChangeMax = maxEpoch(cluster);
for (IInvokableInstance node : cluster)

View File

@ -46,8 +46,6 @@ import org.slf4j.LoggerFactory;
import accord.primitives.Unseekables;
import accord.topology.Topologies;
import org.apache.cassandra.config.Config.NonSerialWriteStrategy;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.CQLTester;
import org.apache.cassandra.cql3.functions.types.utils.Bytes;
import org.apache.cassandra.db.marshal.Int32Type;
@ -63,6 +61,7 @@ import org.apache.cassandra.distributed.api.SimpleQueryResult;
import org.apache.cassandra.distributed.shared.AssertUtils;
import org.apache.cassandra.service.accord.AccordService;
import org.apache.cassandra.service.accord.AccordTestUtils;
import org.apache.cassandra.service.consensus.TransactionalMode;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.assertj.core.api.Assertions;
@ -85,35 +84,28 @@ public class AccordCQLTest extends AccordTestBase
}
@Parameterized.Parameter
public String nonSerialWriteStrategyName;
public String transactionalModeName;
NonSerialWriteStrategy nonSerialWriteStrategy;
TransactionalMode transactionalMode;
@Parameterized.Parameters(name = "nonSerialWriteStrategy={0}")
@Parameterized.Parameters(name = "transactionalMode={0}")
public static Collection<Object[]> data()
{
return ImmutableList.of(new Object[] {NonSerialWriteStrategy.accord.toString()}, new Object[] {NonSerialWriteStrategy.migration.toString()});
return ImmutableList.of(new Object[] {TransactionalMode.full.toString()},
new Object[] {TransactionalMode.mixed_reads.toString()});
}
@Before
public void setNonSerialWriteStrategy()
{
nonSerialWriteStrategy = NonSerialWriteStrategy.valueOf(nonSerialWriteStrategyName);
String nonSerialWriteStrategyName = this.nonSerialWriteStrategyName;
SHARED_CLUSTER.forEach(node -> {
node.runOnInstance(() -> {
DatabaseDescriptor.setNonSerialWriteStrategy(NonSerialWriteStrategy.valueOf(nonSerialWriteStrategyName));
});
});
transactionalMode = TransactionalMode.valueOf(transactionalModeName);
}
@BeforeClass
public static void setupClass() throws IOException
{
AccordTestBase.setupCluster(builder -> builder.appendConfig(config -> config.set("lwt_strategy", "accord")
.set("non_serial_write_strategy", "migration")), 2);
AccordTestBase.setupCluster(builder -> builder, 2);
SHARED_CLUSTER.schemaChange("CREATE TYPE " + KEYSPACE + ".person (height int, age int)");
SHARED_CLUSTER.get(1).runOnInstance(() -> AccordService.instance().ensureKeyspaceIsAccordManaged(KEYSPACE));
}
@Test
@ -177,7 +169,7 @@ public class AccordCQLTest extends AccordTestBase
String currentTable = keyspace + ".tbl";
List<String> ddls = Arrays.asList("DROP KEYSPACE IF EXISTS " + keyspace + ";",
"CREATE KEYSPACE " + keyspace + " WITH REPLICATION={'class':'SimpleStrategy', 'replication_factor': 1}",
"CREATE TABLE " + currentTable + " (k blob, c int, v int, primary key (k, c))");
"CREATE TABLE " + currentTable + " (k blob, c int, v int, primary key (k, c)) WITH transactional_mode='" + transactionalMode + "'");
List<String> tokens = tokens();
List<ByteBuffer> keys = tokensToKeys(tokens);
List<String> keyStrings = keys.stream().map(bb -> "0x" + ByteBufferUtil.bytesToHex(bb)).collect(Collectors.toList());
@ -262,13 +254,13 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testRegularScalarIsNull() throws Throwable
{
testScalarIsNull("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int, primary key (k, c))");
testScalarIsNull("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int, primary key (k, c)) WITH transactional_mode='" + transactionalMode + "'");
}
@Test
public void testStaticScalarIsNull() throws Throwable
{
testScalarIsNull("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int static, primary key (k, c))");
testScalarIsNull("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int static, primary key (k, c)) WITH transactional_mode='" + transactionalMode + "'");
}
private void testScalarIsNull(String tableDDL) throws Exception {
@ -303,7 +295,7 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testQueryStaticColumn() throws Exception
{
test("CREATE TABLE " + qualifiedTableName + " (k int, c int, s int static, v int, primary key (k, c))",
test("CREATE TABLE " + qualifiedTableName + " (k int, c int, s int static, v int, primary key (k, c)) WITH transactional_mode='" + transactionalMode + "'",
cluster ->
{
// select partition key, clustering key and static column, restrict on partition and clustering
@ -357,7 +349,7 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testUpdateStaticColumn() throws Exception {
test("CREATE TABLE " + qualifiedTableName + " (k int, c int, s int static, v int, primary key (k, c))",
test("CREATE TABLE " + qualifiedTableName + " (k int, c int, s int static, v int, primary key (k, c)) WITH transactional_mode='" + transactionalMode + '\'',
cluster ->
{
checkUpdateStatic(cluster, "SET s=1 WHERE k=?", 101, "[[101, null, 1, null]]", "[]");
@ -393,7 +385,7 @@ public class AccordCQLTest extends AccordTestBase
private void assertResultsFromAccordMatches(Cluster cluster, String accordRead, String simpleRead, int key)
{
Object[][] simpleReadResult;
if (nonSerialWriteStrategy.ignoresSuppliedConsistencyLevel)
if (transactionalMode.ignoresSuppliedConsistencyLevel)
// With accord non-SERIAL write strategy the commit CL is effectively ANY so we need to read at SERIAL
simpleReadResult = cluster.coordinator(1).execute(simpleRead, ConsistencyLevel.SERIAL, key);
else
@ -453,12 +445,12 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testStaticScalarEQ() throws Throwable
{
testScalarCondition("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int static, primary key (k, c))", 3, "=", 3, "=");
testScalarCondition("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int static, primary key (k, c)) WITH transactional_mode='" + transactionalMode + "'", 3, "=", 3, "=");
}
private void testScalarCondition(int lhs, String operator, int rhs, String reversedOperator) throws Exception
{
testScalarCondition("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int, primary key (k, c))", lhs, operator, rhs, reversedOperator);
testScalarCondition("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int, primary key (k, c)) WITH transactional_mode='" + transactionalMode + "'", lhs, operator, rhs, reversedOperator);
}
private void testScalarCondition(String tableDDL, int lhs, String operator, int rhs, String reversedOperator) throws Exception
@ -580,7 +572,7 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testReversedClusteringReference() throws Exception
{
test("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int, PRIMARY KEY (k, c)) WITH CLUSTERING ORDER BY (c DESC)",
test("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int, PRIMARY KEY (k, c)) WITH CLUSTERING ORDER BY (c DESC) AND transactional_mode='" + transactionalMode + "'",
cluster ->
{
cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, c, v) VALUES (1, 1, 1)", ConsistencyLevel.ALL);
@ -615,7 +607,7 @@ public class AccordCQLTest extends AccordTestBase
private void testScalarShorthandOperation(int startingValue, String operation, int endingvalue) throws Exception
{
test("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, v int)",
test("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, v int) WITH transactional_mode='" + transactionalMode + "'",
cluster ->
{
cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, v) VALUES (1, ?)", ConsistencyLevel.ALL, startingValue);
@ -637,7 +629,7 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testConstantNonStaticRowReadBeforeUpdate() throws Exception
{
test("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int, PRIMARY KEY (k, c))",
test("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int, PRIMARY KEY (k, c)) WITH transactional_mode='" + transactionalMode + "'",
cluster ->
{
cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, c, v) VALUES (1, 2, ?)", ConsistencyLevel.ALL, 3);
@ -659,7 +651,7 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testRangeDeletion() throws Exception
{
test("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int, PRIMARY KEY (k, c))",
test("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int, PRIMARY KEY (k, c)) WITH transactional_mode='" + transactionalMode + "'",
cluster ->
{
cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, c, v) VALUES (1, 2, ?)", ConsistencyLevel.ALL, 3);
@ -683,7 +675,7 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testPartitionKeyReferenceCondition() throws Exception
{
test("CREATE TABLE " + qualifiedTableName + " (k INT, c INT, v INT, PRIMARY KEY (k, c)) WITH CLUSTERING ORDER BY (c DESC)",
test("CREATE TABLE " + qualifiedTableName + " (k INT, c INT, v INT, PRIMARY KEY (k, c)) WITH CLUSTERING ORDER BY (c DESC) AND transactional_mode='" + transactionalMode + "'",
cluster ->
{
cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, c, v) VALUES (1, 1, 1)", ConsistencyLevel.ALL);
@ -731,13 +723,13 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testMultiCellListEqCondition() throws Exception
{
testListEqCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list list<int>)");
testListEqCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list list<int>) WITH transactional_mode='" + transactionalMode + "'");
}
@Test
public void testFrozenListEqCondition() throws Exception
{
testListEqCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list frozen<list<int>>)");
testListEqCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list frozen<list<int>>) WITH transactional_mode='" + transactionalMode + "'");
}
private void testListEqCondition(String ddl) throws Exception
@ -778,13 +770,13 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testMultiCellSetEqCondition() throws Exception
{
testSetEqCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set set<int>)");
testSetEqCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set set<int>) WITH transactional_mode='" + transactionalMode + "'");
}
@Test
public void testFrozenSetEqCondition() throws Exception
{
testSetEqCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set frozen<set<int>>)");
testSetEqCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set frozen<set<int>>) WITH transactional_mode='" + transactionalMode + "'");
}
private void testSetEqCondition(String ddl) throws Exception
@ -825,13 +817,13 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testMultiCellMapEqCondition() throws Exception
{
testMapEqCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map map<text, int>)", true);
testMapEqCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map map<text, int>) WITH transactional_mode='" + transactionalMode + "'", true);
}
@Test
public void testFrozenMapEqCondition() throws Exception
{
testMapEqCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map frozen<map<text, int>>)", false);
testMapEqCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map frozen<map<text, int>>) WITH transactional_mode='" + transactionalMode + "'", false);
}
private void testMapEqCondition(String ddl, boolean isMultiCell) throws Exception
@ -872,13 +864,13 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testMultiCellUDTEqCondition() throws Exception
{
testUDTEqCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer person)");
testUDTEqCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer person) WITH transactional_mode='" + transactionalMode + "'");
}
@Test
public void testFrozenUDTEqCondition() throws Exception
{
testUDTEqCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer frozen<person>)");
testUDTEqCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer frozen<person>) WITH transactional_mode='" + transactionalMode + "'");
}
private void testUDTEqCondition(String tableDDL) throws Exception
@ -918,7 +910,7 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testTupleEqCondition() throws Exception
{
test("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, pair tuple<text, int>)",
test("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, pair tuple<text, int>) WITH transactional_mode='" + transactionalMode + "'",
cluster ->
{
Object initialTupleValue = CQLTester.tuple("age", 37);
@ -953,7 +945,7 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testIsNullWithComplexDeletion() throws Exception
{
test("CREATE TABLE " + qualifiedTableName + " (k int, c int, int_list list<int>, PRIMARY KEY (k, c))",
test("CREATE TABLE " + qualifiedTableName + " (k int, c int, int_list list<int>, PRIMARY KEY (k, c)) WITH transactional_mode='" + transactionalMode + "'",
cluster ->
{
ListType<Integer> listType = ListType.getInstance(Int32Type.instance, true);
@ -987,13 +979,13 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testNullMultiCellListConditions() throws Exception
{
testNullListConditions("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list list<int>)");
testNullListConditions("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list list<int>) WITH transactional_mode='" + transactionalMode + "'");
}
@Test
public void testNullFrozenListConditions() throws Exception
{
testNullListConditions("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list frozen<list<int>>)");
testNullListConditions("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list frozen<list<int>>) WITH transactional_mode='" + transactionalMode + "'");
}
private void testNullListConditions(String ddl) throws Exception
@ -1039,13 +1031,13 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testNullMultiCellSetConditions() throws Exception
{
testNullSetConditions("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set set<int>)");
testNullSetConditions("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set set<int>) WITH transactional_mode='" + transactionalMode + "'");
}
@Test
public void testNullFrozenSetConditions() throws Exception
{
testNullSetConditions("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set frozen<set<int>>)");
testNullSetConditions("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set frozen<set<int>>) WITH transactional_mode='" + transactionalMode + "'");
}
private void testNullSetConditions(String ddl) throws Exception
@ -1091,13 +1083,13 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testNullMultiCellMapConditions() throws Exception
{
testNullMapConditions("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map map<text, int>)", true);
testNullMapConditions("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map map<text, int>) WITH transactional_mode='" + transactionalMode + "'", true);
}
@Test
public void testNullFrozenMapConditions() throws Exception
{
testNullMapConditions("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map frozen<map<text, int>>)", false);
testNullMapConditions("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map frozen<map<text, int>>) WITH transactional_mode='" + transactionalMode + "'", false);
}
private void testNullMapConditions(String ddl, boolean isMultiCell) throws Exception
@ -1148,13 +1140,13 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testNullMultiCellUDTCondition() throws Exception
{
testNullUDTCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer person)");
testNullUDTCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer person) WITH transactional_mode='" + transactionalMode + "'");
}
@Test
public void testNullFrozenUDTCondition() throws Exception
{
testNullUDTCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer frozen<person>)");
testNullUDTCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer frozen<person>) WITH transactional_mode='" + transactionalMode + "'");
}
private void testNullUDTCondition(String tableDDL) throws Exception
@ -1202,13 +1194,13 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testNullMultiCellSetElementConditions() throws Exception
{
testNullSetElementConditions("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set set<int>)");
testNullSetElementConditions("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set set<int>) WITH transactional_mode='" + transactionalMode + "'");
}
@Test
public void testNullFrozenSetElementConditions() throws Exception
{
testNullSetElementConditions("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set frozen<set<int>>)");
testNullSetElementConditions("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set frozen<set<int>>) WITH transactional_mode='" + transactionalMode + "'");
}
private void testNullSetElementConditions(String ddl) throws Exception
@ -1254,13 +1246,13 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testNullMultiCellMapElementConditions() throws Exception
{
testNullMapElementConditions("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map map<text, int>)", true);
testNullMapElementConditions("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map map<text, int>) WITH transactional_mode='" + transactionalMode + "'", true);
}
@Test
public void testNullFrozenMapElementConditions() throws Exception
{
testNullMapElementConditions("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map frozen<map<text, int>>)", false);
testNullMapElementConditions("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map frozen<map<text, int>>) WITH transactional_mode='" + transactionalMode + "'", false);
}
private void testNullMapElementConditions(String ddl, boolean isMultiCell) throws Exception
@ -1311,13 +1303,13 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testNullMultiCellUDTFieldCondition() throws Exception
{
testNullUDTFieldCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer person)");
testNullUDTFieldCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer person) WITH transactional_mode='" + transactionalMode + "'");
}
@Test
public void testNullFrozenUDTFieldCondition() throws Exception
{
testNullUDTFieldCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer frozen<person>)");
testNullUDTFieldCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer frozen<person>) WITH transactional_mode='" + transactionalMode + "'");
}
private void testNullUDTFieldCondition(String tableDDL) throws Exception
@ -1365,13 +1357,13 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testMultiCellListSubstitution() throws Exception
{
testListSubstitution("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list list<int>)", true);
testListSubstitution("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list list<int>) WITH transactional_mode='" + transactionalMode + "'", true);
}
@Test
public void testFrozenListSubstitution() throws Exception
{
testListSubstitution("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list frozen<list<int>>)", false);
testListSubstitution("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list frozen<list<int>>) WITH transactional_mode='" + transactionalMode + "'", false);
}
private void testListSubstitution(String ddl, boolean isMultiCell) throws Exception
@ -1405,13 +1397,13 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testMultiCellSetSubstitution() throws Exception
{
testSetSubstitution("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set set<int>)", true);
testSetSubstitution("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set set<int>) WITH transactional_mode='" + transactionalMode + "'", true);
}
@Test
public void testFrozenSetSubstitution() throws Exception
{
testSetSubstitution("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set frozen<set<int>>)", false);
testSetSubstitution("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set frozen<set<int>>) WITH transactional_mode='" + transactionalMode + "'", false);
}
private void testSetSubstitution(String ddl, boolean isMultiCell) throws Exception
@ -1445,13 +1437,13 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testMultiCellMapSubstitution() throws Exception
{
testMapSubstitution("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map map<text, int>)", true);
testMapSubstitution("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map map<text, int>) WITH transactional_mode='" + transactionalMode + "'", true);
}
@Test
public void testFrozenMapSubstitution() throws Exception
{
testMapSubstitution("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map frozen<map<text, int>>)", false);
testMapSubstitution("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map frozen<map<text, int>>) WITH transactional_mode='" + transactionalMode + "'", false);
}
private void testMapSubstitution(String ddl, boolean isMultiCell) throws Exception
@ -1485,13 +1477,13 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testMultiCellUDTSubstitution() throws Exception
{
testUDTSubstitution("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer person)");
testUDTSubstitution("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer person) WITH transactional_mode='" + transactionalMode + "'");
}
@Test
public void testFrozenUDTSubstitution() throws Exception
{
testUDTSubstitution("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer frozen<person>)");
testUDTSubstitution("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer frozen<person>) WITH transactional_mode='" + transactionalMode + "'");
}
private void testUDTSubstitution(String tableDDL) throws Exception
@ -1523,7 +1515,7 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testTupleSubstitution() throws Exception
{
test("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, pair tuple<text, int>)",
test("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, pair tuple<text, int>) WITH transactional_mode='" + transactionalMode + "'",
cluster ->
{
Object initialTupleValue = CQLTester.tuple("age", 37);
@ -1550,13 +1542,13 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testMultiCellListReplacement() throws Exception
{
testListReplacement("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list list<int>)");
testListReplacement("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list list<int>) WITH transactional_mode='" + transactionalMode + "'");
}
@Test
public void testFrozenListReplacement() throws Exception
{
testListReplacement("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list frozen<list<int>>)");
testListReplacement("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list frozen<list<int>>) WITH transactional_mode='" + transactionalMode + "'");
}
private void testListReplacement(String ddl) throws Exception
@ -1587,13 +1579,13 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testMultiCellSetReplacement() throws Exception
{
testSetReplacement("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set set<int>)");
testSetReplacement("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set set<int>) WITH transactional_mode='" + transactionalMode + "'");
}
@Test
public void testFrozenSetReplacement() throws Exception
{
testSetReplacement("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set frozen<set<int>>)");
testSetReplacement("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set frozen<set<int>>) WITH transactional_mode='" + transactionalMode + "'");
}
private void testSetReplacement(String ddl) throws Exception
@ -1624,7 +1616,7 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testListAppendFromReference() throws Exception
{
test("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list list<int>)",
test("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list list<int>) WITH transactional_mode='" + transactionalMode + "'",
cluster ->
{
cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, int_list) VALUES (0, [1, 2]);", ConsistencyLevel.ALL);
@ -1650,13 +1642,13 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testSetByIndexFromMultiCellListElement() throws Exception
{
testListSetByIndexFromListElement("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, src_int_list list<int>, dest_int_list list<int>)");
testListSetByIndexFromListElement("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, src_int_list list<int>, dest_int_list list<int>) WITH transactional_mode='" + transactionalMode + "'");
}
@Test
public void testSetByIndexFromFrozenListElement() throws Exception
{
testListSetByIndexFromListElement("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, src_int_list frozen<list<int>>, dest_int_list list<int>)");
testListSetByIndexFromListElement("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, src_int_list frozen<list<int>>, dest_int_list list<int>) WITH transactional_mode='" + transactionalMode + "'");
}
private void testListSetByIndexFromListElement(String ddl) throws Exception
@ -1685,7 +1677,7 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testListSetByIndexFromScalar() throws Exception
{
test("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list list<int>)",
test("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list list<int>) WITH transactional_mode='" + transactionalMode + "'",
cluster ->
{
cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, int_list) VALUES (0, [1, 2]);", ConsistencyLevel.ALL);
@ -1708,7 +1700,7 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testAutoReadSelectionConstruction() throws Exception
{
test("CREATE TABLE " + qualifiedTableName + " (k int, c int, counter int, other_counter int, PRIMARY KEY (k, c))",
test("CREATE TABLE " + qualifiedTableName + " (k int, c int, counter int, other_counter int, PRIMARY KEY (k, c)) WITH transactional_mode='" + transactionalMode + "'",
cluster ->
{
cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, c, counter, other_counter) VALUES (0, 0, 1, 1);", ConsistencyLevel.ALL);
@ -1732,7 +1724,7 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testMultiMutationsSameKey() throws Exception
{
test("CREATE TABLE " + qualifiedTableName + " (k int, c int, counter int, int_list list<int>, PRIMARY KEY (k, c))",
test("CREATE TABLE " + qualifiedTableName + " (k int, c int, counter int, int_list list<int>, PRIMARY KEY (k, c)) WITH transactional_mode='" + transactionalMode + "'",
cluster ->
{
cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, c, counter, int_list) VALUES (0, 0, 0, [1, 2]);", ConsistencyLevel.ALL);
@ -1784,7 +1776,7 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testListSetByIndexMultiRow() throws Exception
{
test("CREATE TABLE " + qualifiedTableName + " (k int, c int, int_list list<int>, PRIMARY KEY (k, c))",
test("CREATE TABLE " + qualifiedTableName + " (k int, c int, int_list list<int>, PRIMARY KEY (k, c)) WITH transactional_mode='" + transactionalMode + "'",
cluster ->
{
cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, c, int_list) VALUES (0, 0, [1, 2]);", ConsistencyLevel.ALL);
@ -1812,7 +1804,7 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testSetAppend() throws Exception
{
test("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set set<int>)",
test("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set set<int>) WITH transactional_mode='" + transactionalMode + "'",
cluster ->
{
cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, int_set) VALUES (0, {1, 2});", ConsistencyLevel.ALL);
@ -1836,13 +1828,13 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testAssignmentFromMultiCellSetElement() throws Exception
{
testAssignmentFromSetElement("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, v int, int_set set<int>)");
testAssignmentFromSetElement("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, v int, int_set set<int>) WITH transactional_mode='" + transactionalMode + "'");
}
@Test
public void testAssignmentFromFrozenSetElement() throws Exception
{
testAssignmentFromSetElement("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, v int, int_set frozen<set<int>>)");
testAssignmentFromSetElement("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, v int, int_set frozen<set<int>>) WITH transactional_mode='" + transactionalMode + "'");
}
private void testAssignmentFromSetElement(String ddl) throws Exception
@ -1871,7 +1863,7 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testMapAppend() throws Exception
{
test("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map map<text, int>)",
test("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map map<text, int>) WITH transactional_mode='" + transactionalMode + "'",
cluster ->
{
cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, int_map) VALUES (0, {'one': 2});", ConsistencyLevel.ALL);
@ -1895,13 +1887,13 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testAssignmentFromMultiCellMapElement() throws Exception
{
testAssignmentFromMapElement("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, v int, int_map map<text, int>)");
testAssignmentFromMapElement("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, v int, int_map map<text, int>) WITH transactional_mode='" + transactionalMode + "'");
}
@Test
public void testAssignmentFromFrozenMapElement() throws Exception
{
testAssignmentFromMapElement("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, v int, int_map frozen<map<text, int>>)");
testAssignmentFromMapElement("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, v int, int_map frozen<map<text, int>>) WITH transactional_mode='" + transactionalMode + "'");
}
private void testAssignmentFromMapElement(String ddl) throws Exception
@ -1930,13 +1922,13 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testAssignmentFromMultiCellUDTField() throws Exception
{
testAssignmentFromUDTField("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, v int, customer person)");
testAssignmentFromUDTField("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, v int, customer person) WITH transactional_mode='" + transactionalMode + "'");
}
@Test
public void testAssignmentFromFrozenUDTField() throws Exception
{
testAssignmentFromUDTField("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, v int, customer frozen<person>)");
testAssignmentFromUDTField("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, v int, customer frozen<person>) WITH transactional_mode='" + transactionalMode + "'");
}
private void testAssignmentFromUDTField(String tableDDL) throws Exception
@ -1967,7 +1959,7 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testSetMapElementFromMapElementReference() throws Exception
{
test("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map map<text, int>)",
test("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map map<text, int>) WITH transactional_mode='" + transactionalMode + "'",
cluster ->
{
cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, int_map) VALUES (0, {'one': 2});", ConsistencyLevel.ALL);
@ -1991,7 +1983,7 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testSetUDTFieldFromUDTFieldReference() throws Exception
{
test("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer person)",
test("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer person) WITH transactional_mode='" + transactionalMode + "'",
cluster ->
{
Object youngPerson = CQLTester.userType("height", 58, "age", 9);
@ -2020,13 +2012,13 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testMultiCellListElementCondition() throws Exception
{
testListElementCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list list<int>)");
testListElementCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list list<int>) WITH transactional_mode='" + transactionalMode + "'");
}
@Test
public void testFrozenListElementCondition() throws Exception
{
testListElementCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list frozen<list<int>>)");
testListElementCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list frozen<list<int>>) WITH transactional_mode='" + transactionalMode + "'");
}
private void testListElementCondition(String ddl) throws Exception
@ -2057,13 +2049,13 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testMultiCellMapElementCondition() throws Exception
{
testMapElementCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map map<text, int>)");
testMapElementCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map map<text, int>) WITH transactional_mode='" + transactionalMode + "'");
}
@Test
public void testFrozenMapElementCondition() throws Exception
{
testMapElementCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map frozen<map<text, int>>)");
testMapElementCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map frozen<map<text, int>>) WITH transactional_mode='" + transactionalMode + "'");
}
private void testMapElementCondition(String ddl) throws Exception
@ -2094,13 +2086,13 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testMultiCellUDTFieldCondition() throws Exception
{
testUDTFieldCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer person)");
testUDTFieldCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer person) WITH transactional_mode='" + transactionalMode + "'");
}
@Test
public void testFrozenUDTFieldCondition() throws Exception
{
testUDTFieldCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer frozen<person>)");
testUDTFieldCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer frozen<person>) WITH transactional_mode='" + transactionalMode + "'");
}
private void testUDTFieldCondition(String tableDDL) throws Exception
@ -2145,7 +2137,7 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testListSubtraction() throws Exception
{
test("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list list<int>)",
test("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list list<int>) WITH transactional_mode='" + transactionalMode + "'",
cluster ->
{
cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, int_list) VALUES (0, [1, 2, 3, 4]);", ConsistencyLevel.ALL);
@ -2171,7 +2163,7 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testSetSubtraction() throws Exception
{
test("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set set<int>)",
test("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set set<int>) WITH transactional_mode='" + transactionalMode + "'",
cluster ->
{
cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, int_set) VALUES (0, {1, 2, 3, 4});", ConsistencyLevel.ALL);
@ -2197,13 +2189,13 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testMultiCellMapSubtraction() throws Exception
{
testMapSubtraction("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map map<text, int>, int_set set<text>)");
testMapSubtraction("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map map<text, int>, int_set set<text>) WITH transactional_mode='" + transactionalMode + "'");
}
@Test
public void testFrozenMapSubtraction() throws Exception
{
testMapSubtraction("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map map<text, int>, int_set frozen<set<text>>)");
testMapSubtraction("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map map<text, int>, int_set frozen<set<text>>) WITH transactional_mode='" + transactionalMode + "'");
}
private void testMapSubtraction(String ddl) throws Exception
@ -2234,13 +2226,13 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testMultiCellListSelection() throws Exception
{
testListSelection("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list list<int>)");
testListSelection("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list list<int>) WITH transactional_mode='" + transactionalMode + "'");
}
@Test
public void testFrozenListSelection() throws Exception
{
testListSelection("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list frozen<list<int>>)");
testListSelection("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list frozen<list<int>>) WITH transactional_mode='" + transactionalMode + "'");
}
private void testListSelection(String ddl) throws Exception
@ -2272,13 +2264,13 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testMultiCellSetSelection() throws Exception
{
testSetSelection("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set set<int>)");
testSetSelection("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set set<int>) WITH transactional_mode='" + transactionalMode + "'");
}
@Test
public void testFrozenSetSelection() throws Exception
{
testSetSelection("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set frozen<set<int>>)");
testSetSelection("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set frozen<set<int>>) WITH transactional_mode='" + transactionalMode + "'");
}
private void testSetSelection(String ddl) throws Exception
@ -2316,7 +2308,7 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testFrozenMapSelection() throws Exception
{
testMapSelection("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map frozen<map<text, int>>)");
testMapSelection("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map frozen<map<text, int>>) WITH transactional_mode='" + transactionalMode + "'");
}
private void testMapSelection(String ddl) throws Exception
@ -2349,8 +2341,8 @@ public class AccordCQLTest extends AccordTestBase
{
String KEYSPACE = "ks" + System.currentTimeMillis();
SHARED_CLUSTER.schemaChange("CREATE KEYSPACE " + KEYSPACE + " WITH REPLICATION={'class':'SimpleStrategy', 'replication_factor': 2}");
SHARED_CLUSTER.schemaChange("CREATE TABLE " + qualifiedTableName + "1 (k int, c int, v int, primary key (k, c))");
SHARED_CLUSTER.schemaChange("CREATE TABLE " + qualifiedTableName + "2 (k int, c int, v int, primary key (k, c))");
SHARED_CLUSTER.schemaChange("CREATE TABLE " + qualifiedTableName + "1 (k int, c int, v int, primary key (k, c)) WITH transactional_mode='" + transactionalMode + "'");
SHARED_CLUSTER.schemaChange("CREATE TABLE " + qualifiedTableName + "2 (k int, c int, v int, primary key (k, c)) WITH transactional_mode='" + transactionalMode + "'");
SHARED_CLUSTER.forEach(node -> node.runOnInstance(() -> AccordService.instance().setCacheSize(0)));
SHARED_CLUSTER.coordinator(1).execute("INSERT INTO " + qualifiedTableName + "1 (k, c, v) VALUES (1, 2, 3);", ConsistencyLevel.ALL);
SHARED_CLUSTER.coordinator(1).execute("INSERT INTO " + qualifiedTableName + "2 (k, c, v) VALUES (2, 2, 4);", ConsistencyLevel.ALL);
@ -2375,13 +2367,13 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testRegularScalarInsertSubstitution() throws Exception
{
testScalarInsertSubstitution("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int, PRIMARY KEY (k, c))");
testScalarInsertSubstitution("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int, PRIMARY KEY (k, c)) WITH transactional_mode='" + transactionalMode + "'");
}
@Test
public void testStaticScalarInsertSubstitution() throws Exception
{
testScalarInsertSubstitution("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int static, PRIMARY KEY (k, c))");
testScalarInsertSubstitution("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int static, PRIMARY KEY (k, c)) WITH transactional_mode='" + transactionalMode + "'");
}
private void testScalarInsertSubstitution(String tableDDL) throws Exception
@ -2411,13 +2403,13 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testSelectMultiCellUDTReference() throws Exception
{
testSelectUDTReference("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer person)");
testSelectUDTReference("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer person) WITH transactional_mode='" + transactionalMode + "'");
}
@Test
public void testSelectFrozenUDTReference() throws Exception
{
testSelectUDTReference("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer frozen<person>)");
testSelectUDTReference("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer frozen<person>) WITH transactional_mode='" + transactionalMode + "'");
}
private void testSelectUDTReference(String tableDDL) throws Exception
@ -2446,13 +2438,13 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testSelectMultiCellUDTFieldReference() throws Exception
{
testSelectUDTFieldReference("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer person)");
testSelectUDTFieldReference("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer person) WITH transactional_mode='" + transactionalMode + "'");
}
@Test
public void testSelectFrozenUDTFieldReference() throws Exception
{
testSelectUDTFieldReference("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer frozen<person>)");
testSelectUDTFieldReference("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer frozen<person>) WITH transactional_mode='" + transactionalMode + "'");
}
private void testSelectUDTFieldReference(String tableDDL) throws Exception
@ -2483,7 +2475,7 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testMultiKeyQueryAndInsert() throws Throwable
{
test("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int, primary key (k, c))",
test("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int, primary key (k, c)) WITH transactional_mode='" + transactionalMode + "'",
cluster ->
{
String query1 = "BEGIN TRANSACTION\n" +
@ -2525,11 +2517,9 @@ public class AccordCQLTest extends AccordTestBase
{
SHARED_CLUSTER.schemaChange("DROP KEYSPACE IF EXISTS demo_ks;");
SHARED_CLUSTER.schemaChange("CREATE KEYSPACE demo_ks WITH REPLICATION={'class':'SimpleStrategy', 'replication_factor':2};");
SHARED_CLUSTER.schemaChange("CREATE TABLE demo_ks.org_docs ( org_name text, doc_id int, contents_version int static, title text, permissions int, PRIMARY KEY (org_name, doc_id) );");
SHARED_CLUSTER.schemaChange("CREATE TABLE demo_ks.org_users ( org_name text, user text, members_version int static, permissions int, PRIMARY KEY (org_name, user) );");
SHARED_CLUSTER.schemaChange("CREATE TABLE demo_ks.user_docs ( user text, doc_id int, title text, org_name text, permissions int, PRIMARY KEY (user, doc_id) );");
SHARED_CLUSTER.get(1).runOnInstance(() -> AccordService.instance().ensureKeyspaceIsAccordManaged("demo_ks"));
SHARED_CLUSTER.schemaChange("CREATE TABLE demo_ks.org_docs ( org_name text, doc_id int, contents_version int static, title text, permissions int, PRIMARY KEY (org_name, doc_id) ) WITH transactional_mode='" + transactionalMode + "';");
SHARED_CLUSTER.schemaChange("CREATE TABLE demo_ks.org_users ( org_name text, user text, members_version int static, permissions int, PRIMARY KEY (org_name, user) ) WITH transactional_mode='" + transactionalMode + "';");
SHARED_CLUSTER.schemaChange("CREATE TABLE demo_ks.user_docs ( user text, doc_id int, title text, org_name text, permissions int, PRIMARY KEY (user, doc_id) ) WITH transactional_mode='" + transactionalMode + "';");
SHARED_CLUSTER.forEach(node -> node.runOnInstance(() -> AccordService.instance().setCacheSize(0)));
@ -2607,7 +2597,7 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testCASAndSerialRead() throws Exception
{
test("CREATE TABLE " + qualifiedTableName + " (id int, c int, v int, s int static, PRIMARY KEY ((id), c));",
test("CREATE TABLE " + qualifiedTableName + " (id int, c int, v int, s int static, PRIMARY KEY ((id), c)) WITH transactional_mode='" + transactionalMode + "';",
cluster -> {
ICoordinator coordinator = cluster.coordinator(1);
int startingAccordCoordinateCount = getAccordCoordinateCount();
@ -2644,7 +2634,7 @@ public class AccordCQLTest extends AccordTestBase
assertEquals(1, rangeDeletionCheck.length);
// Make sure all the consensus using queries actually were run on Accord
if (nonSerialWriteStrategy.writesThroughAccord)
if (transactionalMode.writesThroughAccord)
assertEquals( 20, getAccordCoordinateCount() - startingAccordCoordinateCount);
else
// Non-serial writes don't go through Accord in these modes
@ -2656,7 +2646,7 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testCASSimulatorLite() throws Exception
{
test("CREATE TABLE " + qualifiedTableName + " (pk int, count int, seq1 text, seq2 list<int>, PRIMARY KEY (pk))",
test("CREATE TABLE " + qualifiedTableName + " (pk int, count int, seq1 text, seq2 list<int>, PRIMARY KEY (pk)) WITH transactional_mode='" + transactionalMode + "'",
cluster -> {
ICoordinator coordinator = cluster.coordinator(1);
coordinator.execute("INSERT INTO " + qualifiedTableName + " (pk, count, seq1, seq2) VALUES (1, 0, '', []) USING TIMESTAMP 0", ConsistencyLevel.ALL);
@ -2688,7 +2678,7 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testTransactionCasSimulatorLite() throws Exception
{
test("CREATE TABLE " + qualifiedTableName + " (pk int, count int, seq1 text, seq2 list<int>, PRIMARY KEY (pk))",
test("CREATE TABLE " + qualifiedTableName + " (pk int, count int, seq1 text, seq2 list<int>, PRIMARY KEY (pk)) WITH transactional_mode='" + transactionalMode + "'",
cluster ->
{
ICoordinator coordinator = cluster.coordinator(1);
@ -2729,7 +2719,7 @@ public class AccordCQLTest extends AccordTestBase
@Test
public void testSerialReadDescending() throws Throwable
{
test("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int, PRIMARY KEY(k, c))",
test("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int, PRIMARY KEY(k, c)) WITH transactional_mode='" + transactionalMode + "'",
cluster -> {
ICoordinator coordinator = cluster.coordinator(1);
for (int i = 1; i <= 10; i++)

View File

@ -19,35 +19,16 @@
package org.apache.cassandra.distributed.test.accord;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.Test;
import org.apache.cassandra.db.virtual.AccordVirtualTables;
import org.apache.cassandra.db.virtual.SystemViewsKeyspace;
import org.apache.cassandra.db.virtual.VirtualTable;
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.IIsolatedExecutor;
import org.apache.cassandra.distributed.test.TestBaseImpl;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.service.accord.AccordService;
import org.apache.cassandra.transport.Dispatcher;
import org.apache.cassandra.utils.AssertionUtils;
import org.assertj.core.api.Assertions;
import static org.apache.cassandra.config.DatabaseDescriptor.NO_ACCORD_PAXOS_STRATEGY_WITH_ACCORD_DISABLED_MESSAGE;
import static org.apache.cassandra.cql3.statements.TransactionStatement.TRANSACTIONS_DISABLED_MESSAGE;
import static org.apache.cassandra.schema.SchemaConstants.ACCORD_KEYSPACE_NAME;
import static org.junit.Assert.assertEquals;
public class AccordFeatureFlagTest extends TestBaseImpl
{
@Test
@ -58,46 +39,9 @@ public class AccordFeatureFlagTest extends TestBaseImpl
.withConfig(c -> c.with(Feature.NETWORK).set("accord.enabled", "false"))
.start()))
{
cluster.schemaChange("CREATE TABLE " + KEYSPACE + ".tbl (k int, c int, v int, primary key (k, c))");
// Any transaction should fail to execute:
String query = "BEGIN TRANSACTION\n" +
" SELECT * FROM " + KEYSPACE + ".tbl WHERE k=0 AND c=0;\n" +
"COMMIT TRANSACTION";
Assertions.assertThatThrownBy(() -> cluster.coordinator(1).executeWithResult(query, ConsistencyLevel.ANY))
.has(AssertionUtils.isThrowableInstanceof(InvalidRequestException.class))
.hasMessage(TRANSACTIONS_DISABLED_MESSAGE);
// The Accord system keyspace should not be present:
assertEquals("The Accord system keyspace should not exist",
Optional.empty(), cluster.get(1).callOnInstance(() -> Schema.instance.localKeyspaces().get(ACCORD_KEYSPACE_NAME)));
// Make sure virtual tables don't exist:
IIsolatedExecutor.SerializableCallable<Stream<VirtualTable>> hasAccordVirtualTables =
() -> SystemViewsKeyspace.instance.tables().stream().filter(t -> t.getClass().equals(AccordVirtualTables.Epoch.class));
List<VirtualTable> tables = cluster.get(1).callOnInstance(hasAccordVirtualTables).collect(Collectors.toList());
assertEquals("No Accord virtual tables should exist", Collections.emptyList(), tables);
// Make sure we throw if someone tries to coordinate a transaction against the no-op service:
Assertions.assertThatThrownBy(() -> cluster.get(1).callOnInstance(() -> AccordService.instance().coordinate(null, null, Dispatcher.RequestTime.forImmediateExecution())))
.isInstanceOf(UnsupportedOperationException.class);
}
}
@SuppressWarnings("Convert2MethodRef")
@Test
public void shouldFailOnAccordMigrationWithAccordDisabled() throws IOException
{
try (Cluster cluster = Cluster.build(1)
.withoutVNodes()
.withConfig(c -> c.with(Feature.NETWORK)
.set("accord.enabled", "false")
.set("lwt_strategy", "accord")).createWithoutStarting())
{
Assertions.assertThatThrownBy(() -> cluster.startup())
.has(AssertionUtils.isThrowableInstanceof(ConfigurationException.class))
.hasMessage(NO_ACCORD_PAXOS_STRATEGY_WITH_ACCORD_DISABLED_MESSAGE);
Assertions.assertThatThrownBy(() -> cluster.schemaChange("CREATE TABLE " + KEYSPACE + ".tbl (k int, c int, v int, primary key (k, c)) WITH transactional_mode='full'"))
.has(AssertionUtils.isThrowableInstanceof(InvalidRequestException.class))
.hasMessageContaining("accord.enabled");
}
}
}

View File

@ -32,7 +32,6 @@ import org.apache.cassandra.distributed.test.TestBaseImpl;
import org.apache.cassandra.distributed.util.QueryResultUtil;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.QueryState;
import org.apache.cassandra.service.accord.AccordService;
import static org.apache.cassandra.distributed.api.Feature.GOSSIP;
import static org.apache.cassandra.distributed.api.Feature.NETWORK;
@ -61,14 +60,10 @@ public class AccordInteropReadTest extends TestBaseImpl
public void serialReadTest() throws Throwable
{
try (Cluster cluster = builder().withNodes(3)
.withConfig(config -> config.with(GOSSIP).with(NETWORK)
.set("non_serial_write_strategy", "mixed")
.set("lwt_strategy", "accord"))
.start())
.withConfig(config -> config.with(GOSSIP).with(NETWORK)).start())
{
cluster.schemaChange("CREATE KEYSPACE ks WITH REPLICATION={'class':'SimpleStrategy', 'replication_factor':3}");
cluster.schemaChange("CREATE TABLE ks.tbl (k int, c int, v int, PRIMARY KEY (k, c))");
cluster.get(1).runOnInstance(() -> AccordService.instance().ensureKeyspaceIsAccordManaged("ks"));
cluster.schemaChange("CREATE TABLE ks.tbl (k int, c int, v int, PRIMARY KEY (k, c)) WITH transactional_mode='unsafe_writes'");
cluster.get(1).runOnInstance(() -> localWrite("INSERT INTO ks.tbl (k, c, v) VALUES (1, 1, 1)"));
cluster.get(2).runOnInstance(() -> localWrite("INSERT INTO ks.tbl (k, c, v) VALUES (1, 1, 2)"));

View File

@ -42,14 +42,13 @@ public class AccordInteroperabilityTest extends AccordTestBase
@BeforeClass
public static void setupClass() throws IOException
{
AccordTestBase.setupCluster(builder -> builder.appendConfig(config -> config.set("lwt_strategy", "accord")
.set("non_serial_write_strategy", "accord")), 3);
AccordTestBase.setupCluster(builder -> builder, 3);
}
@Test
public void testSerialReadDescending() throws Throwable
{
test("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int, PRIMARY KEY(k, c))",
test("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int, PRIMARY KEY(k, c)) WITH transactional_mode='full'",
cluster -> {
ICoordinator coordinator = cluster.coordinator(1);
for (int i = 1; i <= 10; i++)

View File

@ -24,6 +24,7 @@ import java.util.Map;
import java.util.function.Function;
import com.google.common.base.Throwables;
import org.apache.cassandra.service.consensus.TransactionalMode;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
@ -89,14 +90,14 @@ public class AccordMetricsTest extends AccordTestBase
public void beforeTest()
{
SHARED_CLUSTER.filters().reset();
SHARED_CLUSTER.schemaChange("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int, PRIMARY KEY (k, c))");
SHARED_CLUSTER.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, c, v) VALUES (0, 0, 0)", ConsistencyLevel.ALL);
SHARED_CLUSTER.schemaChange("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int, PRIMARY KEY (k, c)) WITH " + TransactionalMode.full.asCqlParam());
}
@Test
public void testRegularMetrics() throws Exception
{
countingMetrics0 = getMetrics();
assertCoordinatorMetrics(0, "rw", 0, 0, 0, 0, 0);
SHARED_CLUSTER.coordinator(1).executeWithResult(writeCql(), ConsistencyLevel.ALL, 0, 0, 0, 0);
assertCoordinatorMetrics(0, "rw", 1, 0, 0, 0, 0);
assertCoordinatorMetrics(1, "rw", 0, 0, 0, 0, 0);

View File

@ -31,8 +31,10 @@ import java.util.function.Function;
import javax.annotation.Nonnull;
import com.google.common.collect.ImmutableList;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.slf4j.Logger;
@ -58,21 +60,24 @@ import org.apache.cassandra.distributed.api.NodeToolResult;
import org.apache.cassandra.gms.EndpointState;
import org.apache.cassandra.gms.Gossiper;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.service.consensus.TransactionalMode;
import org.apache.cassandra.service.consensus.migration.ConsensusKeyMigrationState;
import org.apache.cassandra.service.consensus.migration.ConsensusRequestRouter;
import org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState;
import org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState.ConsensusMigrationState;
import org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState.ConsensusMigrationTarget;
import org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState.TableMigrationState;
import org.apache.cassandra.service.consensus.migration.ConsensusMigrationState;
import org.apache.cassandra.service.consensus.migration.ConsensusMigrationTarget;
import org.apache.cassandra.service.consensus.migration.TableMigrationState;
import org.apache.cassandra.service.consensus.migration.TransactionalMigrationFromMode;
import org.apache.cassandra.service.paxos.Ballot;
import org.apache.cassandra.service.paxos.Ballot.Flag;
import org.apache.cassandra.service.paxos.BallotGenerator;
import org.apache.cassandra.service.paxos.Commit.Agreed;
import org.apache.cassandra.service.paxos.Commit.Proposal;
import org.apache.cassandra.service.paxos.PaxosState;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.transport.Dispatcher;
import org.apache.cassandra.utils.ByteArrayUtil;
@ -86,6 +91,7 @@ import static com.google.common.collect.ImmutableList.toImmutableList;
import static java.lang.String.format;
import static java.util.Collections.emptyList;
import static org.apache.cassandra.Util.spinUntilSuccess;
import static org.apache.cassandra.cql3.QueryProcessor.executeInternal;
import static org.apache.cassandra.db.SystemKeyspace.CONSENSUS_MIGRATION_STATE;
import static org.apache.cassandra.db.SystemKeyspace.PAXOS;
import static org.apache.cassandra.dht.Range.normalize;
@ -96,8 +102,7 @@ import static org.apache.cassandra.schema.SchemaConstants.SYSTEM_KEYSPACE_NAME;
import static org.apache.cassandra.service.consensus.migration.ConsensusRequestRouter.ConsensusRoutingDecision.paxosV2;
import static org.apache.cassandra.service.paxos.PaxosState.MaybePromise.Outcome.PROMISE;
import static org.assertj.core.api.Fail.fail;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.*;
/*
* This test suite is intended to serve as an integration test with some pretty good visibility into actual execution
@ -146,11 +151,8 @@ public class AccordMigrationTest extends AccordTestBase
ServerTestUtils.daemonInitialization();
// Otherwise repair complains if you don't specify a keyspace
CassandraRelevantProperties.SYSTEM_TRACES_DEFAULT_RF.setInt(3);
AccordTestBase.setupCluster(builder ->
builder.appendConfig(config ->
config.set("paxos_variant", PaxosVariant.v2.name())
.set("non_serial_write_strategy", "migration")),
3);
AccordTestBase.setupCluster(builder -> builder.appendConfig(config -> config.set("paxos_variant", PaxosVariant.v2.name())
.set("accord.range_migration", "explicit")), 3);
partitioner = FBUtilities.newPartitioner(SHARED_CLUSTER.get(1).callsOnInstance(() -> DatabaseDescriptor.getPartitioner().getClass().getSimpleName()).call());
StorageService.instance.setPartitionerUnsafe(partitioner);
ServerTestUtils.prepareServerNoRegister();
@ -177,9 +179,6 @@ public class AccordMigrationTest extends AccordTestBase
ConsensusRequestRouter.resetInstance();
ConsensusKeyMigrationState.reset();
});
SHARED_CLUSTER.get(1).runOnInstance(() -> {
ConsensusTableMigrationState.reset();
});
SHARED_CLUSTER.coordinators().forEach(coordinator -> coordinator.execute(format("TRUNCATE TABLE %s.%s", SYSTEM_KEYSPACE_NAME, CONSENSUS_MIGRATION_STATE), ALL));
SHARED_CLUSTER.coordinators().forEach(coordinator -> coordinator.execute(format("TRUNCATE TABLE %s.%s", SYSTEM_KEYSPACE_NAME, PAXOS), ALL));
}
@ -243,10 +242,10 @@ public class AccordMigrationTest extends AccordTestBase
boolean routed;
@Override
protected ConsensusRoutingDecision routeAndMaybeMigrate(@Nonnull DecoratedKey key, @Nonnull ColumnFamilyStore cfs, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime, long timeoutNanos, boolean isForWrite)
protected ConsensusRoutingDecision routeAndMaybeMigrate(ClusterMetadata cm, @Nonnull TableMetadata tmd, @Nonnull DecoratedKey key, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime, long timeoutNanos, boolean isForWrite)
{
if (routed)
return super.routeAndMaybeMigrate(key, cfs, consistencyLevel, requestTime, timeoutNanos, isForWrite);
return super.routeAndMaybeMigrate(cm, tmd, key, consistencyLevel, requestTime, timeoutNanos, isForWrite);
routed = true;
return paxosV2;
}
@ -279,10 +278,10 @@ public class AccordMigrationTest extends AccordTestBase
boolean routed;
@Override
protected ConsensusRoutingDecision routeAndMaybeMigrate(@Nonnull DecoratedKey key, @Nonnull ColumnFamilyStore cfs, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime, long timeoutNanos, boolean isForWrite)
protected ConsensusRoutingDecision routeAndMaybeMigrate(ClusterMetadata cm, @Nonnull TableMetadata tmd, @Nonnull DecoratedKey key, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime, long timeoutNanos, boolean isForWrite)
{
if (routed)
return super.routeAndMaybeMigrate(key, cfs, consistencyLevel, requestTime, timeoutNanos, isForWrite);
return super.routeAndMaybeMigrate(cm, tmd, key, consistencyLevel, requestTime, timeoutNanos, isForWrite);
routed = true;
return ConsensusRoutingDecision.accord;
}
@ -343,6 +342,21 @@ public class AccordMigrationTest extends AccordTestBase
{
test(format(TABLE_FMT, qualifiedTableName),
cluster -> {
String table = tableName;
cluster.forEach(node -> node.runOnInstance(() -> {
TableMetadata tbl = Schema.instance.getTableMetadata(KEYSPACE, table);
Assert.assertEquals(TransactionalMode.off, tbl.params.transactionalMode);
Assert.assertEquals(TransactionalMigrationFromMode.none, tbl.params.transactionalMigrationFrom);
}));
cluster.schemaChange(format("ALTER TABLE %s.%s WITH transactional_mode='%s'", KEYSPACE, tableName, TransactionalMode.full));
cluster.forEach(node -> node.runOnInstance(() -> {
TableMetadata tbl = Schema.instance.getTableMetadata(KEYSPACE, table);
Assert.assertEquals(TransactionalMode.full, tbl.params.transactionalMode);
Assert.assertEquals(TransactionalMigrationFromMode.off, tbl.params.transactionalMigrationFrom);
}));
String casCQL = format(CAS_FMT, qualifiedTableName, CLUSTERING_VALUE);
Consumer<Integer> runCasNoApply = key -> assertRowEquals(cluster, new Object[]{false}, casCQL, key);
Consumer<Integer> runCasApplies = key -> assertRowEquals(cluster, new Object[]{true}, casCQL, key);
@ -412,14 +426,18 @@ public class AccordMigrationTest extends AccordTestBase
// key migration occurred
assertTargetAccordWrite(runCasApplies, 1, migratingKey, 1, 0, 1, 0, 0);
// This will force the request to run on Paxos up to Accept
// and the accept will be rejected at both nodes and we are certain we need to retry the transaction
// This will force the write to use the normal write patch
cluster.get(1).runOnInstance(() -> ConsensusRequestRouter.setInstance(new PaxosToAccordMigrationNotHappeningUpToBegin()));
// Update inserted row so the condition can apply, if the condition check doesn't apply
// then it won't get to propose/accept
migratingKey = testingKeys.next();
Consumer<Integer> makeCASApply = key -> cluster.coordinator(1).execute("UPDATE " + qualifiedTableName + " SET v = 42 WHERE id = ? AND c = ?", ALL, key, CLUSTERING_VALUE);
String query = "UPDATE " + qualifiedTableName + " SET v = 42 WHERE id = ? AND c = ?";
Consumer<Integer> makeCASApply = key -> cluster.forEach(instance -> instance.runOnInstance(() -> executeInternal(query, key, CLUSTERING_VALUE)));
makeCASApply.accept(migratingKey);
// This will force the request to run on Paxos up to Accept
// and the accept will be rejected at both nodes and we are certain we need to retry the transaction
cluster.get(1).runOnInstance(() -> ConsensusRequestRouter.setInstance(new PaxosToAccordMigrationNotHappeningUpToBegin()));
assertTargetAccordWrite(runCasApplies, 1, migratingKey, 1, 1, 1, 0, 1);
// One node will now accept the other will reject and we are uncertain if we should retry the transaction
@ -429,7 +447,9 @@ public class AccordMigrationTest extends AccordTestBase
cluster.get(1).runOnInstance(() -> ConsensusRequestRouter.setInstance(new PaxosToAccordMigrationNotHappeningUpToAccept()));
try
{
cluster.filters().allVerbs().to(3).from(3).drop();
runCasNoApply.accept(migratingKey);
cluster.filters().reset();
fail("Should have thrown timeout exception");
}
catch (Throwable t)
@ -469,7 +489,7 @@ public class AccordMigrationTest extends AccordTestBase
{
test(format(TABLE_FMT, qualifiedTableName),
cluster -> {
String tableName = qualifiedTableName.split("\\.")[1];
cluster.schemaChange(format("ALTER TABLE %s.%s WITH transactional_mode='%s'", KEYSPACE, tableName, TransactionalMode.full));
String readCQL = format("SELECT * FROM %s WHERE id = ? and c = %s", qualifiedTableName, CLUSTERING_VALUE);
Function<Integer, Object[][]> runRead = key -> cluster.coordinator(1).execute(readCQL, SERIAL, key);
Range<Token> migratingRange = new Range<>(new LongToken(Long.MIN_VALUE + 1), new LongToken(Long.MIN_VALUE));
@ -490,6 +510,25 @@ public class AccordMigrationTest extends AccordTestBase
});
}
private void alterTableTransactionalMode(TransactionalMode mode)
{
SHARED_CLUSTER.schemaChange(format("ALTER TABLE %s WITH %s", qualifiedTableName, mode.asCqlParam()));
}
private void assertTransactionalModes(String keyspace, String table, TransactionalMode mode, TransactionalMigrationFromMode migration)
{
forEach(() -> {
TableMetadata metadata = Schema.instance.getTableMetadata(keyspace, table);
Assert.assertEquals(mode, metadata.params.transactionalMode);
Assert.assertEquals(migration, metadata.params.transactionalMigrationFrom);
});
}
private void assertTransactionalModes(TransactionalMode mode, TransactionalMigrationFromMode migration)
{
assertTransactionalModes(KEYSPACE, tableName, mode, migration);
}
@Test
public void testAccordToPaxos() throws Exception
{
@ -499,6 +538,9 @@ public class AccordMigrationTest extends AccordTestBase
Consumer<Integer> runCasNoApply = key -> assertRowEquals(cluster, new Object[]{false}, casCQL, key);
String tableName = qualifiedTableName.split("\\.")[1];
alterTableTransactionalMode(TransactionalMode.mixed_reads);
assertTransactionalModes(TransactionalMode.mixed_reads, TransactionalMigrationFromMode.off);
// Mark a subrange as migrating and finish migrating half of it
nodetool(coordinator, "consensus_admin", "begin-migration", "-st", midToken.toString(), "-et", maxToken.toString(), "-tp", "accord", KEYSPACE, tableName);
nodetool(coordinator, "consensus_admin", "finish-migration", "-st", midToken.toString(), "-et", "3074457345618258601");
@ -508,7 +550,8 @@ public class AccordMigrationTest extends AccordTestBase
assertMigrationState(tableName, ConsensusMigrationTarget.accord, ImmutableList.of(accordMigratedRange), ImmutableList.of(accordMigratingRange), 1);
// Test that we can reverse the migration and go back to Paxos
nodetool(coordinator, "consensus_admin", "set-target-protocol", "-tp", "paxos", KEYSPACE, tableName);
alterTableTransactionalMode(TransactionalMode.off);
assertTransactionalModes(TransactionalMode.off, TransactionalMigrationFromMode.mixed_reads);
assertMigrationState(tableName, ConsensusMigrationTarget.paxos, ImmutableList.of(new Range(minToken, midToken), new Range(maxToken, minToken)), ImmutableList.of(accordMigratingRange), 1);
Iterator<Integer> paxosNonMigratingKeys = getKeysBetweenTokens(minToken, midToken);
Iterator<Integer> paxosMigratingKeys = getKeysBetweenTokens(upperMidToken, maxToken);
@ -547,6 +590,38 @@ public class AccordMigrationTest extends AccordTestBase
});
}
private static void assertCompletedMigrationState(String tableName) throws Throwable
{
// Validate nodetool consensus admin list output
String yamlResultString = nodetool(SHARED_CLUSTER.coordinator(1), "consensus_admin", "list");
Map<String, Object> yamlStateMap = new Yaml().load(yamlResultString);
String minifiedYamlResultString = nodetool(SHARED_CLUSTER.coordinator(1), "consensus_admin", "list", "-f", "minified-yaml");
Map<String, Object> minifiedYamlStateMap = new Yaml().load(minifiedYamlResultString);
String jsonResultString = nodetool(SHARED_CLUSTER.coordinator(1), "consensus_admin", "list", "-f", "json");
Map<String, Object> jsonStateMap = JsonUtils.JSON_OBJECT_MAPPER.readValue(jsonResultString, new TypeReference<Map<String, Object>>(){});
String minifiedJsonResultString = nodetool(SHARED_CLUSTER.coordinator(1), "consensus_admin", "list", "-f", "minified-json");
Map<String, Object> minifiedJsonStateMap = JsonUtils.JSON_OBJECT_MAPPER.readValue(minifiedJsonResultString, new TypeReference<Map<String, Object>>(){});
for (Map<String, Object> migrationStateMap : ImmutableList.of(yamlStateMap, jsonStateMap, minifiedYamlStateMap, minifiedJsonStateMap)) {
assertEquals(PojoToString.CURRENT_VERSION, migrationStateMap.get("version"));
assertTrue(Epoch.EMPTY.getEpoch() < ((Number) migrationStateMap.get("lastModifiedEpoch")).longValue());
List<Map<String, Object>> tableStates = (List<Map<String, Object>>) migrationStateMap.get("tableStates");
assertEquals(0, tableStates.size());
}
spinUntilSuccess(() -> {
for (IInvokableInstance instance : SHARED_CLUSTER)
{
ConsensusMigrationState snapshot = getMigrationStateSnapshot(instance);
assertEquals(0, snapshot.tableStates.size());
instance.runOnInstance(() -> {
TableMetadata tbl = Schema.instance.getTableMetadata(KEYSPACE, tableName);
Assert.assertEquals(TransactionalMigrationFromMode.none, tbl.params.transactionalMigrationFrom);
});
}
});
}
private static void assertMigrationState(String tableName, ConsensusMigrationTarget target, List<Range<Token>> migratedRanges, List<Range<Token>> migratingRanges, int numMigratingEpochs) throws Throwable
{
// Validate nodetool consensus admin list output
@ -564,9 +639,20 @@ public class AccordMigrationTest extends AccordTestBase
{
assertEquals(PojoToString.CURRENT_VERSION, migrationStateMap.get("version"));
assertTrue(Epoch.EMPTY.getEpoch() < ((Number) migrationStateMap.get("lastModifiedEpoch")).longValue());
List<Map<String, Object>> tableStates = (List<Map<String, Object>>) migrationStateMap.get("tableStates");
assertEquals(tableStates.size(), 1);
Map<String, Object> tableStateMap = tableStates.get(0);
Map<String, Object> tableStateMap = null;
for (Map<String, Object> stateMap : (List<Map<String, Object>>) migrationStateMap.get("tableStates"))
{
Object table = stateMap.get("table");
Object keyspace = stateMap.get("keyspace");
if (KEYSPACE.equals(keyspace) && tableName.equals(table))
{
tableStateMap = stateMap;
break;
}
}
assertNotNull(tableStateMap);
assertEquals(tableName, tableStateMap.get("table"));
assertEquals(KEYSPACE, tableStateMap.get("keyspace"));
tableIds.add((String) tableStateMap.get("tableId"));
@ -591,21 +677,22 @@ public class AccordMigrationTest extends AccordTestBase
for (IInvokableInstance instance : SHARED_CLUSTER)
{
ConsensusMigrationState snapshot = getMigrationStateSnapshot(instance);
assertEquals(1, snapshot.tableStates.size());
TableMigrationState state = snapshot.tableStates.values().iterator().next();
assertEquals(KEYSPACE, state.keyspaceName);
assertEquals(tableName, state.tableName);
for (String tableId : tableIds)
assertEquals(tableId, state.tableId.toString());
assertEquals(target, state.targetProtocol);
assertEquals("Migrated ranges:", migratedRanges, state.migratedRanges);
assertEquals("Migrating ranges:", migratingRanges, state.migratingRanges);
assertEquals("Migrating and migrated ranges:", migratingAndMigratedRanges, state.migratingAndMigratedRanges);
assertEquals(numMigratingEpochs, state.migratingRangesByEpoch.size());
if (migratingRanges.isEmpty())
assertEquals(0, state.migratingRangesByEpoch.size());
else
assertEquals(migratingRanges, state.migratingRangesByEpoch.values().iterator().next());
{
TableMigrationState state = snapshot.tableStates.get(TableId.fromString(tableId));
assertNotNull(state);
assertEquals(KEYSPACE, state.keyspaceName);
assertEquals(tableName, state.tableName);
assertEquals(target, state.targetProtocol);
assertEquals("Migrated ranges:", migratedRanges, state.migratedRanges);
assertEquals("Migrating ranges:", migratingRanges, state.migratingRanges);
assertEquals("Migrating and migrated ranges:", migratingAndMigratedRanges, state.migratingAndMigratedRanges);
assertEquals(numMigratingEpochs, state.migratingRangesByEpoch.size());
if (migratingRanges.isEmpty())
assertEquals(0, state.migratingRangesByEpoch.size());
else
assertEquals(migratingRanges, state.migratingRangesByEpoch.values().iterator().next());
}
}
});
}

View File

@ -27,6 +27,7 @@ import org.apache.cassandra.service.accord.AccordFastPath;
import org.apache.cassandra.tcm.ClusterMetadataService;
import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.service.consensus.TransactionalMode;
import org.junit.Assert;
import org.junit.Test;
@ -90,7 +91,7 @@ public class AccordSimpleFastPathTest extends TestBaseImpl
.start()))
{
cluster.schemaChange("CREATE KEYSPACE ks WITH replication={'class':'SimpleStrategy', 'replication_factor': 3}");
cluster.schemaChange("CREATE TABLE ks.tbl (k int, c int, v int, primary key (k, c))");
cluster.schemaChange("CREATE TABLE ks.tbl (k int, c int, v int, primary key (k, c)) WITH " + TransactionalMode.full.asCqlParam());
String query = "BEGIN TRANSACTION\n" +
" SELECT * FROM ks.tbl WHERE k=0 AND c=0;\n" +
"COMMIT TRANSACTION";

View File

@ -37,6 +37,7 @@ import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.TableMetadata;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -70,7 +71,7 @@ import org.apache.cassandra.service.accord.AccordService;
import org.apache.cassandra.service.accord.AccordTestUtils;
import org.apache.cassandra.service.accord.exceptions.ReadPreemptedException;
import org.apache.cassandra.service.accord.exceptions.WritePreemptedException;
import org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState.ConsensusMigrationState;
import org.apache.cassandra.service.consensus.migration.ConsensusMigrationState;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.serialization.Version;
import org.apache.cassandra.utils.AssertionUtils;
@ -137,11 +138,10 @@ public abstract class AccordTestBase extends TestBaseImpl
public static void ensureTableIsAccordManaged(Cluster cluster, String ksname, String tableName)
{
cluster.get(1).runOnInstance(() -> {
// TODO: remove when accord enabled is handled via schema
TableMetadata metadata = Schema.instance.getTableMetadata(ksname, tableName);
if (metadata == null)
return; // bad plumbing from shared utils....
AccordService.instance().ensureTableIsAccordManaged(metadata.id);
Assert.assertTrue(metadata.params.transactionalMode.accordIsEnabled);
});
}
@ -150,7 +150,6 @@ public abstract class AccordTestBase extends TestBaseImpl
for (String ddl : ddls)
SHARED_CLUSTER.schemaChange(ddl);
ensureTableIsAccordManaged(SHARED_CLUSTER, KEYSPACE, tableName);
// Evict commands from the cache immediately to expose problems loading from disk.
SHARED_CLUSTER.forEach(node -> node.runOnInstance(() -> AccordService.instance().setCacheSize(0)));
@ -166,7 +165,7 @@ public abstract class AccordTestBase extends TestBaseImpl
protected void test(FailingConsumer<Cluster> fn) throws Exception
{
test("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int, primary key (k, c))", fn);
test("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int, primary key (k, c)) WITH transactional_mode='full'", fn);
}
protected static ConsensusMigrationState getMigrationStateSnapshot(IInvokableInstance instance) throws IOException

View File

@ -56,7 +56,7 @@ public class NewSchemaTest extends AccordTestBase
String ks = "ks" + i;
String table = ks + ".tbl" + i;
SHARED_CLUSTER.schemaChange("CREATE KEYSPACE " + ks + " WITH REPLICATION={'class':'SimpleStrategy', 'replication_factor': 1}");
SHARED_CLUSTER.schemaChange(String.format("CREATE TABLE %s (pk blob primary key)", table));
SHARED_CLUSTER.schemaChange(String.format("CREATE TABLE %s (pk blob primary key) WITH transactional_mode='full'", table));
SHARED_CLUSTER.forEach(node -> node.runOnInstance(() -> AccordService.instance().setCacheSize(0)));
List<ByteBuffer> keys = tokensToKeys(tokens());

View File

@ -69,7 +69,6 @@ import org.apache.cassandra.tcm.membership.NodeAddresses;
import org.apache.cassandra.tcm.membership.NodeId;
import org.apache.cassandra.tcm.membership.NodeState;
import org.apache.cassandra.tcm.membership.NodeVersion;
import org.apache.cassandra.tcm.ownership.AccordTables;
import org.apache.cassandra.tcm.ownership.DataPlacements;
import org.apache.cassandra.tcm.ownership.TokenMap;
import org.apache.cassandra.tcm.ownership.UniformRangePlacement;
@ -150,7 +149,6 @@ public class ClusterMetadataTestHelper
Directory.EMPTY,
new TokenMap(partitioner),
DataPlacements.empty(),
AccordTables.EMPTY,
AccordFastPath.EMPTY,
LockedRanges.EMPTY,
InProgressSequences.EMPTY,
@ -166,7 +164,6 @@ public class ClusterMetadataTestHelper
null,
null,
DataPlacements.empty(),
AccordTables.EMPTY,
AccordFastPath.EMPTY,
null,
null,
@ -182,7 +179,6 @@ public class ClusterMetadataTestHelper
null,
null,
DataPlacements.empty(),
AccordTables.EMPTY,
AccordFastPath.EMPTY,
null,
null,

View File

@ -1,80 +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.test.tcm;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Future;
import org.junit.Test;
import accord.primitives.Ranges;
import accord.primitives.Txn;
import org.apache.cassandra.distributed.Cluster;
import org.apache.cassandra.distributed.api.Feature;
import org.apache.cassandra.distributed.api.IInvokableInstance;
import org.apache.cassandra.distributed.test.TestBaseImpl;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.service.accord.AccordService;
import org.apache.cassandra.service.accord.TokenRange;
import org.apache.cassandra.service.accord.api.AccordRoutingKey;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.utils.FBUtilities;
public class AccordAddTableTest extends TestBaseImpl
{
@Test
public void test() throws IOException
{
try (Cluster cluster = builder().withNodes(6)
.withConfig(c -> c.with(Feature.GOSSIP, Feature.NETWORK))
.start())
{
List<Future<?>> results = new ArrayList<>(cluster.size());
for (IInvokableInstance inst : cluster)
{
Future<?> result = inst.asyncRunsOnInstance(() -> {
for (int i = 0; i < 100; i++)
{
AccordService.instance().maybeConvertTablesToAccord(fakeTxn(i));
if (!ClusterMetadata.current().accordTables.contains(fromNum(i)))
throw new AssertionError("Table not found in TCM!");
}
}).call();
results.add(result);
}
FBUtilities.waitOnFutures(results);
}
}
private static Txn fakeTxn(int i)
{
TableId id = fromNum(i);
Ranges of = Ranges.of(new TokenRange(AccordRoutingKey.SentinelKey.min(id), AccordRoutingKey.SentinelKey.max(id)));
return new Txn.InMemory(of, null, null);
}
private static TableId fromNum(int i)
{
return TableId.fromUUID(new UUID(i, 0)); // not valid... but do we care?
}
}

View File

@ -442,7 +442,7 @@ public class AuditLoggerTest extends CQLTester
@Test
public void testTransactionAuditing()
{
createTable("CREATE TABLE %s (key int PRIMARY KEY, val int)");
createTable("CREATE TABLE %s (key int PRIMARY KEY, val int) WITH transactional_mode='full'");
Session session = sessionNet();
String fqTableName = KEYSPACE + "." + currentTable();

View File

@ -66,7 +66,7 @@ public class TxnAuthTest extends CQLTester
@Before
public void setUpTest()
{
createTable("CREATE TABLE %s (k int, v int, PRIMARY KEY(k))");
createTable("CREATE TABLE %s (k int, v int, PRIMARY KEY(k)) WITH transactional_mode='full'");
}
@Test

View File

@ -78,6 +78,7 @@ public class DatabaseDescriptorRefTest
"org.apache.cassandra.auth.INetworkAuthorizer",
"org.apache.cassandra.auth.IRoleManager",
"org.apache.cassandra.config.AccordSpec",
"org.apache.cassandra.config.AccordSpec$TransactionalRangeMigration",
"org.apache.cassandra.config.CassandraRelevantProperties",
"org.apache.cassandra.config.CassandraRelevantProperties$PropertyConverter",
"org.apache.cassandra.config.Config",
@ -99,6 +100,7 @@ public class DatabaseDescriptorRefTest
"org.apache.cassandra.config.Config$PaxosVariant",
"org.apache.cassandra.config.Config$RepairCommandPoolFullStrategy",
"org.apache.cassandra.config.Config$SSTableConfig",
"org.apache.cassandra.config.Config$TransactionalRangeMigration",
"org.apache.cassandra.config.Config$TriggersPolicy",
"org.apache.cassandra.config.Config$UserFunctionTimeoutPolicy",
"org.apache.cassandra.config.ConfigBeanInfo",
@ -292,6 +294,7 @@ public class DatabaseDescriptorRefTest
"org.apache.cassandra.service.CacheService$CacheType",
"org.apache.cassandra.security.AbstractCryptoProvider",
"org.apache.cassandra.tcm.RegistrationStateCallbacks",
"org.apache.cassandra.service.consensus.TransactionalMode",
"org.apache.cassandra.transport.ProtocolException",
"org.apache.cassandra.utils.Closeable",
"org.apache.cassandra.utils.CloseableIterator",

View File

@ -93,7 +93,7 @@ public class NodeLocalConsistencyTest extends CQLTester
@Test
public void testTransaction()
{
createTable("CREATE TABLE %s (key text, val int, PRIMARY KEY(key))");
createTable("CREATE TABLE %s (key text, val int, PRIMARY KEY(key)) WITH transactional_mode='full'");
QueryProcessor.process(formatQuery("INSERT INTO %s (key, val) VALUES ('foo', 0)"), NODE_LOCAL);
String query = "BEGIN TRANSACTION\n" +

View File

@ -26,6 +26,9 @@ import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import com.google.common.util.concurrent.Uninterruptibles;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.ClusterMetadataService;
import org.apache.cassandra.tcm.Epoch;
import org.junit.Assume;
import org.junit.Before;
import org.junit.BeforeClass;
@ -76,6 +79,27 @@ public class PreparedStatementsTest extends CQLTester
public void setup()
{
requireNetwork();
for (int i=0; i<10; i++)
ClusterMetadataService.instance().log().waitForHighestConsecutive();
}
private static void runAndAwaitNextEpoch(Runnable runnable)
{
try
{
Epoch current = ClusterMetadata.current().epoch;
runnable.run();
ClusterMetadataService.instance().awaitAtLeast(Epoch.create(current.getEpoch() + 1));
}
catch (Throwable e)
{
throw new RuntimeException(e);
}
}
private static void sessionSchemaUpdate(Session session, String update)
{
runAndAwaitNextEpoch(() -> session.execute(update));
}
@Test
@ -173,30 +197,30 @@ public class PreparedStatementsTest extends CQLTester
public void testInvalidatePreparedStatementsOnDrop()
{
Session session = sessionNet(ProtocolVersion.V5);
session.execute(dropKsStatement);
session.execute(createKsStatement);
sessionSchemaUpdate(session, dropKsStatement);
sessionSchemaUpdate(session, createKsStatement);
String createTableStatement = "CREATE TABLE IF NOT EXISTS " + KEYSPACE + ".qp_cleanup (id int PRIMARY KEY, cid int, val text);";
String createTableStatement = "CREATE TABLE IF NOT EXISTS " + KEYSPACE + ".qp_cleanup (id int PRIMARY KEY, cid int, val text) WITH transactional_mode='unsafe';";
String dropTableStatement = "DROP TABLE IF EXISTS " + KEYSPACE + ".qp_cleanup;";
session.execute(createTableStatement);
sessionSchemaUpdate(session, createTableStatement);
String insert = "INSERT INTO " + KEYSPACE + ".qp_cleanup (id, cid, val) VALUES (?, ?, ?)";
PreparedStatement prepared = session.prepare(insert);
PreparedStatement preparedBatch = session.prepare(batch(insert));
PreparedStatement preparedTxn = session.prepare(txn(insert));
session.execute(dropTableStatement);
session.execute(createTableStatement);
sessionSchemaUpdate(session, dropTableStatement);
sessionSchemaUpdate(session, createTableStatement);
updateTxnState();
session.execute(prepared.bind(1, 1, "value"));
session.execute(preparedBatch.bind(2, 2, "value2"));
session.execute(preparedTxn.bind(3, 3, "value3"));
session.execute(dropKsStatement);
session.execute(createKsStatement);
session.execute(createTableStatement);
sessionSchemaUpdate(session, dropKsStatement);
sessionSchemaUpdate(session, createKsStatement);
sessionSchemaUpdate(session, createTableStatement);
updateTxnState();
// The driver will get a response about the prepared statement being invalid, causing it to transparently
@ -205,7 +229,7 @@ public class PreparedStatementsTest extends CQLTester
session.execute(prepared.bind(1, 1, "value"));
session.execute(preparedBatch.bind(2, 2, "value2"));
session.execute(preparedTxn.bind(3, 3, "value3"));
session.execute(dropKsStatement);
sessionSchemaUpdate(session, dropKsStatement);
}
@Test
@ -223,12 +247,12 @@ public class PreparedStatementsTest extends CQLTester
private void testInvalidatePreparedStatementOnAlter(ProtocolVersion version, boolean supportsMetadataChange)
{
Session session = sessionNet(version);
String createTableStatement = "CREATE TABLE IF NOT EXISTS " + KEYSPACE + ".qp_cleanup (a int PRIMARY KEY, b int, c int);";
String createTableStatement = "CREATE TABLE IF NOT EXISTS " + KEYSPACE + ".qp_cleanup (a int PRIMARY KEY, b int, c int) WITH transactional_mode='unsafe';";
String alterTableStatement = "ALTER TABLE " + KEYSPACE + ".qp_cleanup ADD d int;";
session.execute(dropKsStatement);
session.execute(createKsStatement);
session.execute(createTableStatement);
sessionSchemaUpdate(session, dropKsStatement);
sessionSchemaUpdate(session, createKsStatement);
sessionSchemaUpdate(session, createTableStatement);
updateTxnState();
String select = "SELECT * FROM " + KEYSPACE + ".qp_cleanup";
@ -247,7 +271,7 @@ public class PreparedStatementsTest extends CQLTester
assertRowsNet(session.execute(preparedSelectTxn.bind(2)),
row(2, 3, 4));
session.execute(alterTableStatement);
sessionSchemaUpdate(session, alterTableStatement);
updateTxnState();
session.execute("INSERT INTO " + KEYSPACE + ".qp_cleanup (a, b, c, d) VALUES (?, ?, ?, ?);",
@ -291,7 +315,7 @@ public class PreparedStatementsTest extends CQLTester
}
}
session.execute(dropKsStatement);
sessionSchemaUpdate(session, dropKsStatement);
}
@Test
@ -309,12 +333,12 @@ public class PreparedStatementsTest extends CQLTester
private void testInvalidatePreparedStatementOnAlterUnchangedMetadata(ProtocolVersion version)
{
Session session = sessionNet(version);
String createTableStatement = "CREATE TABLE IF NOT EXISTS " + KEYSPACE + ".qp_cleanup (a int PRIMARY KEY, b int, c int);";
String createTableStatement = "CREATE TABLE IF NOT EXISTS " + KEYSPACE + ".qp_cleanup (a int PRIMARY KEY, b int, c int) WITH transactional_mode='unsafe';";
String alterTableStatement = "ALTER TABLE " + KEYSPACE + ".qp_cleanup ADD d int;";
session.execute(dropKsStatement);
session.execute(createKsStatement);
session.execute(createTableStatement);
sessionSchemaUpdate(session, dropKsStatement);
sessionSchemaUpdate(session, createKsStatement);
sessionSchemaUpdate(session, createTableStatement);
updateTxnState();
String select = "SELECT a, b, c FROM " + KEYSPACE + ".qp_cleanup";
@ -338,7 +362,7 @@ public class PreparedStatementsTest extends CQLTester
Assertions.assertThat(columnNames(rs)).containsExactlyInAnyOrder("a", "b", "c");
}
session.execute(alterTableStatement);
sessionSchemaUpdate(session, alterTableStatement);
updateTxnState();
session.execute("INSERT INTO " + KEYSPACE + ".qp_cleanup (a, b, c, d) VALUES (?, ?, ?, ?);",
@ -358,18 +382,18 @@ public class PreparedStatementsTest extends CQLTester
Assertions.assertThat(columnNames(rs)).containsExactlyInAnyOrder("a", "b", "c");
}
session.execute(dropKsStatement);
sessionSchemaUpdate(session, dropKsStatement);
}
@Test
public void testStatementRePreparationOnReconnect()
public void testStatementRePreparationOnReconnect() throws Throwable
{
Session session = sessionNet(ProtocolVersion.V5);
session.execute("USE " + keyspace());
session.execute(dropKsStatement);
session.execute(createKsStatement);
createTable("CREATE TABLE %s (id int PRIMARY KEY, cid int, val text);");
sessionSchemaUpdate(session, dropKsStatement);
sessionSchemaUpdate(session, createKsStatement);
runAndAwaitNextEpoch(() -> createTable("CREATE TABLE %s (id int PRIMARY KEY, cid int, val text) WITH transactional_mode='unsafe';"));
updateTxnState();
String insertCQL = "INSERT INTO " + currentTable() + " (id, cid, val) VALUES (?, ?, ?)";
@ -414,14 +438,14 @@ public class PreparedStatementsTest extends CQLTester
{
Session session = sessionNet(ProtocolVersion.V5);
session.execute(dropKsStatement);
session.execute(createKsStatement);
sessionSchemaUpdate(session, dropKsStatement);
sessionSchemaUpdate(session, createKsStatement);
String table = "custom_expr_test";
String index = "custom_index";
session.execute(String.format("CREATE TABLE IF NOT EXISTS %s.%s (id int PRIMARY KEY, cid int, val text);",
sessionSchemaUpdate(session, String.format("CREATE TABLE IF NOT EXISTS %s.%s (id int PRIMARY KEY, cid int, val text) WITH transactional_mode='unsafe';",
KEYSPACE, table));
session.execute(String.format("CREATE CUSTOM INDEX %s ON %s.%s(val) USING '%s'",
sessionSchemaUpdate(session, String.format("CREATE CUSTOM INDEX %s ON %s.%s(val) USING '%s'",
index, KEYSPACE, table, StubIndex.class.getName()));
updateTxnState();
@ -460,7 +484,7 @@ public class PreparedStatementsTest extends CQLTester
// Note: this test does not cover all aspects of 10786 (yet) - it was intended to test the
// changes for CASSANDRA-13992.
createTable("CREATE TABLE %s (pk int, v1 int, v2 int, PRIMARY KEY (pk))");
runAndAwaitNextEpoch(() -> createTable("CREATE TABLE %s (pk int, v1 int, v2 int, PRIMARY KEY (pk))"));
execute("INSERT INTO %s (pk, v1, v2) VALUES (1,1,1)");
try (SimpleClient simpleClient = newSimpleClient(ProtocolVersion.BETA.orElse(ProtocolVersion.CURRENT)))
@ -646,7 +670,7 @@ public class PreparedStatementsTest extends CQLTester
{
Session session = sessionNet(version);
session.execute("USE " + keyspace());
createTable("CREATE TABLE %s (pk int, v1 int, v2 int, PRIMARY KEY (pk))");
runAndAwaitNextEpoch(() -> createTable("CREATE TABLE %s (pk int, v1 int, v2 int, PRIMARY KEY (pk))"));
PreparedStatement prepared1 = session.prepare(String.format("UPDATE %s SET v1 = ?, v2 = ? WHERE pk = 1 IF v1 = ?", currentTable()));
PreparedStatement prepared2 = session.prepare(String.format("INSERT INTO %s (pk, v1, v2) VALUES (?, 200, 300) IF NOT EXISTS", currentTable()));
@ -710,7 +734,7 @@ public class PreparedStatementsTest extends CQLTester
{
Session session = sessionNet(version);
session.execute("USE " + keyspace());
createTable("CREATE TABLE %s (pk int, v1 int, v2 int, PRIMARY KEY (pk))");
runAndAwaitNextEpoch(() -> createTable("CREATE TABLE %s (pk int, v1 int, v2 int, PRIMARY KEY (pk))"));
PreparedStatement prepared1 = session.prepare("BEGIN BATCH " +
"UPDATE " + currentTable() + " SET v1 = ? WHERE pk = 1 IF v1 = ?;" +
@ -745,7 +769,7 @@ public class PreparedStatementsTest extends CQLTester
row(false, 1, 10, 20));
assertEquals(rs.getColumnDefinitions().size(), 4);
alterTable("ALTER TABLE %s ADD v3 int;");
runAndAwaitNextEpoch(() -> alterTable("ALTER TABLE %s ADD v3 int;"));
rs = session.execute(prepared2.bind());
assertRowsNet(rs,
@ -777,7 +801,7 @@ public class PreparedStatementsTest extends CQLTester
int maxAttempts = 3;
Session session = sessionNet(version);
session.execute("USE " + keyspace());
createTable("CREATE TABLE %s (pk int, v1 int, v2 int, PRIMARY KEY (pk))");
runAndAwaitNextEpoch(() -> createTable("CREATE TABLE %s (pk int, v1 int, v2 int, PRIMARY KEY (pk)) WITH transactional_mode='full'"));
updateTxnState();
PreparedStatement writeOnly = session.prepare(txn(

View File

@ -1141,6 +1141,8 @@ public class DescribeStatementTest extends CQLTester
" AND memtable_flush_period_in_ms = 0\n" +
" AND min_index_interval = 128\n" +
" AND read_repair = 'BLOCKING'\n" +
" AND transactional_mode = 'off'\n" +
" AND transactional_migration_from = 'none'\n" +
" AND speculative_retry = '99p';";
}

View File

@ -347,6 +347,8 @@ public class SchemaCQLHelperTest extends CQLTester
" AND memtable_flush_period_in_ms = 8\n" +
" AND min_index_interval = 6\n" +
" AND read_repair = 'BLOCKING'\n" +
" AND transactional_mode = 'off'\n" +
" AND transactional_migration_from = 'none'\n" +
" AND speculative_retry = 'ALWAYS';"
));
}

View File

@ -27,14 +27,13 @@ import java.util.Set;
import com.google.common.collect.ImmutableMap;
import org.apache.cassandra.service.accord.AccordFastPath;
import org.apache.cassandra.tcm.ownership.AccordTables;
import org.apache.cassandra.service.consensus.migration.ConsensusMigrationState;
import org.junit.Assert;
import org.junit.Test;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.dht.Murmur3Partitioner;
import org.apache.cassandra.schema.DistributedSchema;
import org.apache.cassandra.service.consensus.migration.ConsensusTableMigrationState.ConsensusMigrationState;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.tcm.membership.Directory;
@ -90,7 +89,6 @@ public class MetaStrategyTest
directory,
tokenMap,
DataPlacements.EMPTY,
AccordTables.EMPTY,
AccordFastPath.EMPTY,
LockedRanges.EMPTY,
InProgressSequences.EMPTY,

View File

@ -587,7 +587,9 @@ public abstract class FuzzTestBase extends CQLTester.InMemory
{
RepairType type = repairTypeGen.next(rs);
PreviewType previewType = previewTypeGen.next(rs);
boolean accordRepair = type == RepairType.FULL && previewType == PreviewType.NONE ? rs.nextBoolean() : false;
// TODO (required - IR) add this back and expand as part of IR integration
// boolean accordRepair = type == RepairType.FULL && previewType == PreviewType.NONE ? rs.nextBoolean() : false;
boolean accordRepair = false;
List<String> args = new ArrayList<>();
args.add(ks);
List<String> tables = tablesGen.next(rs);
@ -1407,8 +1409,6 @@ public abstract class FuzzTestBase extends CQLTester.InMemory
failures.add(new AssertionError(event.getMessage()));
});
}
if (repair.state.options.accordRepair())
AccordService.instance().ensureKeyspaceIsAccordManaged(repair.state.keyspace);
return repair;
}

View File

@ -32,9 +32,6 @@ import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.service.accord.fastpath.FastPathStrategy;
import org.apache.cassandra.service.accord.fastpath.ParameterizedFastPathStrategy;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.tcm.transformations.AddAccordTable;
import static java.lang.String.format;
@ -70,14 +67,9 @@ public class FastPathSchemaTest
KeyspaceMetadata ksm = Schema.instance.getKeyspaceMetadata(KEYSPACE);
Assert.assertSame(FastPathStrategy.simple(), ksm.params.fastPath);
process("CREATE TABLE %s.tbl (k int primary key, v int)", KEYSPACE);
process("CREATE TABLE %s.tbl (k int primary key, v int) WITH transactional_mode='full'", KEYSPACE);
TableMetadata tbm = Schema.instance.getTableMetadata(KEYSPACE, "tbl");
Assert.assertSame(FastPathStrategy.inheritKeyspace(), tbm.params.fastPath);
Epoch epoch = ClusterMetadata.current().epoch;
AddAccordTable.addTable(tbm.id);
Assert.assertEquals(epoch.getEpoch() + 1, ClusterMetadata.current().epoch.getEpoch());
}
@Test
@ -108,12 +100,10 @@ public class FastPathSchemaTest
KeyspaceMetadata ksm = Schema.instance.getKeyspaceMetadata(KEYSPACE);
Assert.assertSame(FastPathStrategy.simple(), ksm.params.fastPath);
process("CREATE TABLE %s.tbl (k int primary key, v int)", KEYSPACE);
process("CREATE TABLE %s.tbl (k int primary key, v int) WITH transactional_mode='full'", KEYSPACE);
TableMetadata tbm = Schema.instance.getTableMetadata(KEYSPACE, "tbl");
Assert.assertSame(FastPathStrategy.inheritKeyspace(), tbm.params.fastPath);
AddAccordTable.addTable(tbm.id);
process("ALTER TABLE %s.tbl WITH fast_path='simple'", KEYSPACE);
tbm = Schema.instance.getTableMetadata(KEYSPACE, "tbl");
Assert.assertSame(FastPathStrategy.simple(), tbm.params.fastPath);

View File

@ -0,0 +1,95 @@
/*
* 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.schema;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.apache.cassandra.ServerTestUtils;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.QueryProcessor;
import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.service.consensus.TransactionalMode;
import org.apache.cassandra.service.consensus.migration.TransactionalMigrationFromMode;
import static java.lang.String.format;
public class TransactionalConfigSchemaTest
{
private static final String KEYSPACE = "ks";
@BeforeClass
public static void setup()
{
DatabaseDescriptor.daemonInitialization();
ServerTestUtils.prepareServer();
SchemaTestUtil.addOrUpdateKeyspace(KeyspaceMetadata.create(KEYSPACE, KeyspaceParams.simple(1), Tables.of()));
}
private static void process(String fmt, Object... objects)
{
QueryProcessor.process(format(fmt, objects), ConsistencyLevel.ANY);
}
private static void assertTransactionalMode(String table, TransactionalMode mode, TransactionalMigrationFromMode migration)
{
TableMetadata metadata = Schema.instance.getTableMetadata(KEYSPACE, table);
Assert.assertEquals(mode, metadata.params.transactionalMode);
Assert.assertEquals(migration, metadata.params.transactionalMigrationFrom);
}
// if a table is created with an accord transactional mode, it skips having to migrate
@Test
public void newTableSkipsMigration()
{
String table = "new_table";
process("CREATE TABLE ks.%s (k int primary key, v int) WITH transactional_mode='%s'", table, TransactionalMode.full);
assertTransactionalMode(table, TransactionalMode.full, TransactionalMigrationFromMode.none);
}
// if an existing table is set to an accord transactional mode, it should be set to migrating
@Test
public void existingTableMigration()
{
String table = "existing_table";
process("CREATE TABLE ks.%s (k int primary key, v int)", table);
assertTransactionalMode(table, TransactionalMode.off, TransactionalMigrationFromMode.none);
process("ALTER TABLE ks.%s WITH transactional_mode='%s'", table, TransactionalMode.full);
assertTransactionalMode(table, TransactionalMode.full, TransactionalMigrationFromMode.off);
}
// changing transactional mode with an incomplete migration should fail, unless the migration mode is explicitly updated
@Test
public void incompleteMigrationFailure()
{
String table = "incomplete_table";
process("CREATE TABLE ks.%s (k int primary key, v int)", table);
process("ALTER TABLE ks.%s WITH transactional_mode='%s'", table, TransactionalMode.full);
assertTransactionalMode(table, TransactionalMode.full, TransactionalMigrationFromMode.off);
process("ALTER TABLE ks.%s WITH transactional_mode='%s'", table, TransactionalMode.off);
assertTransactionalMode(table, TransactionalMode.off, TransactionalMigrationFromMode.full);
// explicitly setting the migration mode should work
process("ALTER TABLE ks.%s WITH transactional_mode='%s' AND transactional_migration_from='%s'",
table, TransactionalMode.off, TransactionalMigrationFromMode.none);
assertTransactionalMode(table, TransactionalMode.off, TransactionalMigrationFromMode.none);
}
}

View File

@ -57,9 +57,11 @@ import org.apache.cassandra.schema.KeyspaceParams;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.service.accord.api.PartitionKey;
import org.apache.cassandra.service.accord.serializers.CommandSerializers;
import org.apache.cassandra.service.consensus.TransactionalMode;
import org.apache.cassandra.utils.Pair;
import static accord.local.Status.Durability.Majority;
@ -82,7 +84,9 @@ public class AccordCommandStoreTest
{
SchemaLoader.prepareServer();
SchemaLoader.createKeyspace("ks", KeyspaceParams.simple(1),
parse("CREATE TABLE tbl (k int, c int, v int, primary key (k, c))", "ks"));
parse("CREATE TABLE tbl (k int, c int, v int, primary key (k, c)) WITH transactional_mode='full'", "ks"));
TableMetadata tbl = Schema.instance.getTableMetadata("ks", "tbl");
Assert.assertEquals(TransactionalMode.full, tbl.params.transactionalMode);
StorageService.instance.initServer();
}

View File

@ -73,7 +73,7 @@ public class AccordCommandTest
{
SchemaLoader.prepareServer();
SchemaLoader.createKeyspace("ks", KeyspaceParams.simple(1),
parse("CREATE TABLE tbl (k int, c int, v int, primary key (k, c))", "ks"));
parse("CREATE TABLE tbl (k int, c int, v int, primary key (k, c)) WITH transactional_mode='full'", "ks"));
StorageService.instance.initServer();
}

View File

@ -163,7 +163,7 @@ public class AccordConfigurationServiceTest
ServerTestUtils.daemonInitialization();
SchemaLoader.prepareServer();
SchemaLoader.createKeyspace("ks", KeyspaceParams.simple(1),
parse("CREATE TABLE tbl (k int, c int, v int, primary key (k, c))", "ks"));
parse("CREATE TABLE tbl (k int, c int, v int, primary key (k, c)) WITH transactional_mode='full'", "ks"));
}
@Before

View File

@ -49,7 +49,7 @@ public class AccordReadRepairTest extends AccordTestBase
@BeforeClass
public static void setupClass() throws IOException
{
AccordTestBase.setupCluster(builder -> builder.appendConfig(config -> config.set("lwt_strategy", "accord").set("non_serial_write_strategy", "mixed")), 2);
AccordTestBase.setupCluster(builder -> builder, 2);
SHARED_CLUSTER.schemaChange("CREATE TYPE " + KEYSPACE + ".person (height int, age int)");
}
@ -94,9 +94,9 @@ public class AccordReadRepairTest extends AccordTestBase
void testReadRepair(Function<Cluster, Object[][]> accordTxn, Object[][] expected) throws Exception
{
test("CREATE TABLE " + qualifiedTableName + " (k int, c int, v1 int, v2 int, PRIMARY KEY ((k), c));",
test("CREATE TABLE " + qualifiedTableName + " (k int, c int, v1 int, v2 int, PRIMARY KEY ((k), c)) WITH transactional_mode='unsafe_writes';",
cluster -> {
Filter mutationFilter = cluster.filters().verbs(Verb.MUTATION_REQ.id).drop().on();
Filter mutationFilter = cluster.filters().verbs(Verb.MUTATION_REQ.id).to(2).drop().on();
cluster.filters().verbs(Verb.HINT_REQ.id, Verb.HINT_RSP.id).drop().on();
cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, c, v1, v2) VALUES (1, 1, 1, 1) USING TIMESTAMP 42;", ConsistencyLevel.ONE);
mutationFilter.off();

View File

@ -83,7 +83,7 @@ public class AccordTopologyTest
{
DatabaseDescriptor.daemonInitialization();
DatabaseDescriptor.setPartitionerUnsafe(Murmur3Partitioner.instance);
TableMetadata table = parse("CREATE TABLE tbl (k int, c int, v int, primary key (k, c))", "ks").build();
TableMetadata table = parse("CREATE TABLE tbl (k int, c int, v int, primary key (k, c)) WITH transactional_mode='full'", "ks").build();
tableId = table.id;
keyspace = KeyspaceMetadata.create("ks", KeyspaceParams.simple(3), Tables.of(table));
}
@ -170,7 +170,7 @@ public class AccordTopologyTest
Assert.assertEquals(partitioner.getMaximumTokenForSplitting(), ranges.get(2).right);
ClusterMetadata metadata = configureCluster(ranges, Keyspaces.of(keyspace));
Topology topology = AccordTopology.createAccordTopology(metadata, ks -> true);
Topology topology = AccordTopology.createAccordTopology(metadata);
Topology expected = new Topology(1,
new Shard(AccordTopology.minRange(tableId, ranges.get(0).right), NODE_LIST, NODE_SET),
new Shard(AccordTopology.range(tableId, ranges.get(1)), NODE_LIST, NODE_SET),
@ -188,7 +188,7 @@ public class AccordTopologyTest
range(100, -100));
ClusterMetadata metadata = configureCluster(ranges, Keyspaces.of(keyspace));
Topology topology = AccordTopology.createAccordTopology(metadata, ks -> true);
Topology topology = AccordTopology.createAccordTopology(metadata);
Topology expected = new Topology(1,
new Shard(AccordTopology.minRange(tableId, ranges.get(0).left), NODE_LIST, NODE_SET),
new Shard(AccordTopology.range(tableId, ranges.get(0)), NODE_LIST, NODE_SET),
@ -205,7 +205,7 @@ public class AccordTopologyTest
range(-100, 100),
range(token(100), partitioner.getMaximumTokenForSplitting()));
ClusterMetadata metadata = configureCluster(ranges, Keyspaces.of(keyspace));
Topology topology = AccordTopology.createAccordTopology(metadata, ks -> true);
Topology topology = AccordTopology.createAccordTopology(metadata);
Topology expected = new Topology(1,
new Shard(AccordTopology.minRange(tableId, ranges.get(0).right), NODE_LIST, NODE_SET),
new Shard(AccordTopology.range(tableId, ranges.get(1)), NODE_LIST, NODE_SET),
@ -213,7 +213,7 @@ public class AccordTopologyTest
new Shard(AccordTopology.maxRange(tableId, ranges.get(2).right), NODE_LIST, NODE_SET));
Assert.assertEquals(expected, topology);
topology = AccordTopology.createAccordTopology(metadata.transformer().withFastPathStatusSince(new Id(1), AccordFastPath.Status.UNAVAILABLE, 1, 1).build().metadata, ks -> true);
topology = AccordTopology.createAccordTopology(metadata.transformer().withFastPathStatusSince(new Id(1), AccordFastPath.Status.UNAVAILABLE, 1, 1).build().metadata);
Set<Node.Id> fastPath = new HashSet<>(NODE_SET);
fastPath.remove(new Node.Id(1));
@ -236,7 +236,7 @@ public class AccordTopologyTest
range(-100, 100),
range(token(100), partitioner.getMaximumTokenForSplitting()));
ClusterMetadata metadata = configureCluster(ranges, Keyspaces.of(keyspace));
Topology topology = AccordTopology.createAccordTopology(metadata, ks -> true);
Topology topology = AccordTopology.createAccordTopology(metadata);
Topology expected = new Topology(1,
new Shard(AccordTopology.minRange(tableId, ranges.get(0).right), NODE_LIST, NODE_SET),
new Shard(AccordTopology.range(tableId, ranges.get(1)), NODE_LIST, NODE_SET),
@ -248,7 +248,7 @@ public class AccordTopologyTest
.withFastPathStatusSince(new Id(1), AccordFastPath.Status.UNAVAILABLE, 1, 1)
.withFastPathStatusSince(new Id(2), AccordFastPath.Status.UNAVAILABLE, 1, 1)
.build().metadata;
topology = AccordTopology.createAccordTopology(metadata, ks -> true);
topology = AccordTopology.createAccordTopology(metadata);
Set<Node.Id> fastPath = new HashSet<>(NODE_SET);
fastPath.remove(new Node.Id(1));

View File

@ -45,8 +45,8 @@ public class AccordKeyTest
{
SchemaLoader.prepareServer();
SchemaLoader.createKeyspace("ks", KeyspaceParams.simple(1),
parse("CREATE TABLE tbl1 (k int, c int, v int, primary key (k, c))", "ks").id(TABLE1),
parse("CREATE TABLE tbl2 (k int, c int, v int, primary key (k, c))", "ks").id(TABLE2));
parse("CREATE TABLE tbl1 (k int, c int, v int, primary key (k, c)) WITH transactional_mode='full'", "ks").id(TABLE1),
parse("CREATE TABLE tbl2 (k int, c int, v int, primary key (k, c)) WITH transactional_mode='full'", "ks").id(TABLE2));
}

View File

@ -79,7 +79,7 @@ public class AsyncLoaderTest
{
SchemaLoader.prepareServer();
SchemaLoader.createKeyspace("ks", KeyspaceParams.simple(1),
parse("CREATE TABLE tbl (k int, c int, v int, primary key (k, c))", "ks"));
parse("CREATE TABLE tbl (k int, c int, v int, primary key (k, c)) WITH transactional_mode='full'", "ks"));
StorageService.instance.initServer();
}

View File

@ -107,7 +107,7 @@ public class AsyncOperationTest
{
SchemaLoader.prepareServer();
SchemaLoader.createKeyspace("ks", KeyspaceParams.simple(1),
parse("CREATE TABLE tbl (k int, c int, v int, primary key (k, c))", "ks"));
parse("CREATE TABLE tbl (k int, c int, v int, primary key (k, c)) WITH transactional_mode='full'", "ks"));
StorageService.instance.initServer();
}

View File

@ -40,7 +40,7 @@ public class CommandSerializersTest
{
SchemaLoader.prepareServer();
SchemaLoader.createKeyspace("ks", KeyspaceParams.simple(1),
parse("CREATE TABLE tbl (k int, c int, v int, primary key (k, c))", "ks"));
parse("CREATE TABLE tbl (k int, c int, v int, primary key (k, c)) WITH transactional_mode='full'", "ks"));
}

View File

@ -92,7 +92,7 @@ public class CommandsForKeySerializerTest
// need to create the accord test table as generating random txn is not currently supported
SchemaLoader.prepareServer();
SchemaLoader.createKeyspace("ks", KeyspaceParams.simple(1),
parse("CREATE TABLE tbl (k int, c int, v int, primary key (k, c))", "ks"));
parse("CREATE TABLE tbl (k int, c int, v int, primary key (k, c)) WITH transactional_mode='full'", "ks"));
StorageService.instance.initServer();
}

View File

@ -36,7 +36,7 @@ public class AccordUpdateTest
{
SchemaLoader.prepareServer();
SchemaLoader.createKeyspace("ks", KeyspaceParams.simple(1),
parse("CREATE TABLE tbl (k int, c int, v int, primary key (k, c))", "ks"));
parse("CREATE TABLE tbl (k int, c int, v int, primary key (k, c)) WITH transactional_mode='full'", "ks"));
}

View File

@ -297,8 +297,6 @@ public class ClusterMetadataTransformationTest
return metadata.lockedRanges;
else if (key == IN_PROGRESS_SEQUENCES)
return metadata.inProgressSequences;
else if (key == ACCORD_TABLES)
return metadata.accordTables;
else if (key == ACCORD_FAST_PATH)
return metadata.accordFastPath;
else if (key == CONSENSUS_MIGRATION_STATE)