From 2b2c6decfafc6235ad537e72073fab2fd4467e2f Mon Sep 17 00:00:00 2001 From: Jacek Lewandowski Date: Thu, 30 Sep 2021 09:50:27 +0200 Subject: [PATCH] Refactor schema management to allow for schema source pluggability Patch by Jacek Lewandowski, reviewed by Alex Petrov for CASSANDRA-17044. --- .../auth/AuthSchemaChangeListener.java | 33 +- .../cassandra/batchlog/BatchlogManager.java | 2 +- .../cassandra/cache/AutoSavingCache.java | 5 - .../apache/cassandra/cql3/QueryProcessor.java | 59 +- .../cql3/statements/DescribeStatement.java | 2 +- .../schema/AlterSchemaStatement.java | 10 +- .../schema/AlterTableStatement.java | 4 +- .../schema/CreateTableStatement.java | 1 + .../cassandra/db/ColumnFamilyStore.java | 18 +- .../org/apache/cassandra/db/Keyspace.java | 35 +- .../apache/cassandra/db/SchemaCQLHelper.java | 9 +- .../cassandra/db/SizeEstimatesRecorder.java | 7 +- .../apache/cassandra/db/SystemKeyspace.java | 19 +- .../AbstractCommitLogSegmentManager.java | 12 +- .../cassandra/db/commitlog/CommitLog.java | 2 +- .../db/compaction/CompactionManager.java | 2 +- .../cassandra/db/guardrails/Guardrails.java | 2 +- .../apache/cassandra/dht/BootStrapper.java | 18 +- .../org/apache/cassandra/gms/Gossiper.java | 14 +- .../index/SecondaryIndexManager.java | 5 +- .../io/sstable/CQLSSTableWriter.java | 29 +- .../cassandra/metrics/TableMetrics.java | 2 +- .../org/apache/cassandra/net/Message.java | 12 + .../cassandra/net/MessagingService.java | 11 +- .../net/MessagingServiceMBeanImpl.java | 8 +- src/java/org/apache/cassandra/net/Verb.java | 6 +- .../consistent/admin/SchemaArgsParser.java | 2 +- .../schema/DefaultSchemaUpdateHandler.java | 283 +++++++ .../DefaultSchemaUpdateHandlerFactory.java | 36 + .../org/apache/cassandra/schema/Diff.java | 11 + .../cassandra/schema/DistributedSchema.java | 97 +++ .../cassandra/schema/KeyspaceMetadata.java | 19 + .../apache/cassandra/schema/Keyspaces.java | 59 +- .../schema/MigrationCoordinator.java | 333 ++++---- .../cassandra/schema/MigrationManager.java | 368 --------- .../schema/OfflineSchemaUpdateHandler.java | 93 +++ .../org/apache/cassandra/schema/Schema.java | 775 +++++++----------- .../schema/SchemaChangeListener.java | 70 +- .../schema/SchemaChangeNotifier.java | 207 +++++ .../cassandra/schema/SchemaDiagnostics.java | 10 +- .../apache/cassandra/schema/SchemaEvent.java | 51 +- .../cassandra/schema/SchemaKeyspace.java | 41 +- .../schema/SchemaMigrationDiagnostics.java | 83 -- .../schema/SchemaMigrationEvent.java | 109 --- .../schema/SchemaMutationsSerializer.java | 60 ++ .../schema/SchemaPullVerbHandler.java | 19 +- .../schema/SchemaPushVerbHandler.java | 19 +- .../schema/SchemaTransformation.java | 42 +- .../schema/SchemaTransformations.java | 210 +++++ .../cassandra/schema/SchemaUpdateHandler.java | 76 ++ .../schema/SchemaUpdateHandlerFactory.java | 35 + .../SchemaUpdateHandlerFactoryProvider.java | 60 ++ .../org/apache/cassandra/schema/TableId.java | 4 +- .../schema/TableMetadataRefCache.java | 152 ++++ .../org/apache/cassandra/schema/Types.java | 5 + .../cassandra/service/CassandraDaemon.java | 4 +- .../PendingRangeCalculatorService.java | 4 +- .../cassandra/service/StorageService.java | 162 ++-- .../tools/SSTableExpiredBlockers.java | 2 +- .../cassandra/tools/SSTableLevelResetter.java | 2 +- .../tools/SSTableOfflineRelevel.java | 2 +- .../tools/StandaloneSSTableUtil.java | 2 +- .../cassandra/tools/StandaloneScrubber.java | 2 +- .../cassandra/tools/StandaloneSplitter.java | 2 +- .../cassandra/tools/StandaloneUpgrader.java | 2 +- .../cassandra/tools/StandaloneVerifier.java | 2 +- .../nodetool/stats/TableStatsHolder.java | 2 +- .../apache/cassandra/transport/Server.java | 87 +- .../apache/cassandra/utils/Collectors3.java | 38 +- .../apache/cassandra/utils/FBUtilities.java | 2 +- .../distributed/action/GossipHelper.java | 8 +- .../cassandra/distributed/impl/Instance.java | 6 +- .../test/metric/TableMetricTest.java | 2 +- .../distributed/test/ring/BootstrapTest.java | 25 + .../test/microbench/BatchStatementBench.java | 5 +- .../test/microbench/MutationBench.java | 5 +- .../OnInstanceSyncSchemaForBootstrap.java | 6 +- .../org/apache/cassandra/SchemaLoader.java | 14 +- .../org/apache/cassandra/cql3/CQLTester.java | 3 +- .../cassandra/cql3/KeyCacheCqlTest.java | 7 +- .../apache/cassandra/cql3/ViewSchemaTest.java | 5 +- .../validation/entities/TupleTypeTest.java | 1 - .../cql3/validation/entities/UFTest.java | 3 +- .../operations/CompactStorageTest.java | 14 +- .../operations/CompactTableTest.java | 4 +- .../apache/cassandra/db/CounterCacheTest.java | 7 +- .../org/apache/cassandra/db/KeyspaceTest.java | 9 - .../cassandra/db/RangeTombstoneTest.java | 12 +- .../apache/cassandra/db/ReadCommandTest.java | 6 +- .../org/apache/cassandra/db/RowCacheTest.java | 8 +- .../cassandra/db/SchemaCQLHelperTest.java | 8 +- .../cassandra/db/SecondaryIndexTest.java | 4 +- .../cassandra/db/SystemKeyspaceTest.java | 12 +- .../cassandra/db/commitlog/CommitLogTest.java | 4 +- .../db/commitlog/CommitLogUpgradeTest.java | 6 +- .../db/compaction/CompactionTaskTest.java | 2 +- .../db/compaction/CompactionsBytemanTest.java | 3 +- .../db/compaction/CompactionsTest.java | 8 +- .../db/compaction/TTLExpiryTest.java | 10 +- ...StreamConcurrentComponentMutationTest.java | 6 +- .../cassandra/db/view/ViewUtilsTest.java | 40 +- .../cassandra/dht/BootStrapperTest.java | 2 +- .../org/apache/cassandra/hints/HintTest.java | 8 +- .../cassandra/hints/HintsReaderTest.java | 4 +- .../sasi/disk/PerSSTableIndexWriterTest.java | 8 +- .../sstable/CQLSSTableWriterClientTest.java | 3 +- .../io/sstable/IndexSummaryManagerTest.java | 22 +- .../IndexSummaryRedistributionTest.java | 4 +- .../AssureSufficientLiveNodesTest.java | 6 +- .../cassandra/locator/SimpleStrategyTest.java | 5 +- .../schema/MigrationCoordinatorTest.java | 366 ++++++--- .../schema/MigrationManagerDropKSTest.java | 3 +- .../schema/MigrationManagerTest.java | 64 +- .../cassandra/schema/SchemaKeyspaceTest.java | 57 +- .../schema/SchemaMutationsSerializerTest.java | 56 ++ .../apache/cassandra/schema/SchemaTest.java | 22 +- .../cassandra/schema/SchemaTestUtil.java | 146 ++++ .../service/LeaveAndBootstrapTest.java | 2 +- .../apache/cassandra/service/MoveTest.java | 8 +- .../cassandra/service/OptionalTasksTest.java | 16 +- .../service/PartitionDenylistTest.java | 3 +- .../service/StorageServiceServerTest.java | 41 +- .../paxos/uncommitted/PaxosRowsTest.java | 1 - .../reads/repair/AbstractReadRepairTest.java | 4 +- .../service/reads/repair/ReadRepairTest.java | 4 +- .../transport/ClientNotificiationsTest.java | 9 +- .../triggers/TriggersSchemaTest.java | 20 +- .../cassandra/utils/CassandraGenerators.java | 2 +- .../io/sstable/StressCQLSSTableWriter.java | 14 +- .../cassandra/stress/CompactionStress.java | 1 - 130 files changed, 3188 insertions(+), 1937 deletions(-) create mode 100644 src/java/org/apache/cassandra/schema/DefaultSchemaUpdateHandler.java create mode 100644 src/java/org/apache/cassandra/schema/DefaultSchemaUpdateHandlerFactory.java create mode 100644 src/java/org/apache/cassandra/schema/DistributedSchema.java delete mode 100644 src/java/org/apache/cassandra/schema/MigrationManager.java create mode 100644 src/java/org/apache/cassandra/schema/OfflineSchemaUpdateHandler.java create mode 100644 src/java/org/apache/cassandra/schema/SchemaChangeNotifier.java delete mode 100644 src/java/org/apache/cassandra/schema/SchemaMigrationDiagnostics.java delete mode 100644 src/java/org/apache/cassandra/schema/SchemaMigrationEvent.java create mode 100644 src/java/org/apache/cassandra/schema/SchemaMutationsSerializer.java create mode 100644 src/java/org/apache/cassandra/schema/SchemaTransformations.java create mode 100644 src/java/org/apache/cassandra/schema/SchemaUpdateHandler.java create mode 100644 src/java/org/apache/cassandra/schema/SchemaUpdateHandlerFactory.java create mode 100644 src/java/org/apache/cassandra/schema/SchemaUpdateHandlerFactoryProvider.java create mode 100644 src/java/org/apache/cassandra/schema/TableMetadataRefCache.java create mode 100644 test/unit/org/apache/cassandra/schema/SchemaMutationsSerializerTest.java create mode 100644 test/unit/org/apache/cassandra/schema/SchemaTestUtil.java diff --git a/src/java/org/apache/cassandra/auth/AuthSchemaChangeListener.java b/src/java/org/apache/cassandra/auth/AuthSchemaChangeListener.java index 217eb1c4de..634d8f3de6 100644 --- a/src/java/org/apache/cassandra/auth/AuthSchemaChangeListener.java +++ b/src/java/org/apache/cassandra/auth/AuthSchemaChangeListener.java @@ -17,38 +17,43 @@ */ package org.apache.cassandra.auth; -import java.util.List; - import org.apache.cassandra.config.DatabaseDescriptor; -import org.apache.cassandra.db.marshal.AbstractType; +import org.apache.cassandra.cql3.functions.UDAggregate; +import org.apache.cassandra.cql3.functions.UDFunction; +import org.apache.cassandra.schema.KeyspaceMetadata; import org.apache.cassandra.schema.SchemaChangeListener; +import org.apache.cassandra.schema.TableMetadata; /** * SchemaChangeListener implementation that cleans up permissions on dropped resources. */ -public class AuthSchemaChangeListener extends SchemaChangeListener +public class AuthSchemaChangeListener implements SchemaChangeListener { - public void onDropKeyspace(String ksName) + @Override + public void onDropKeyspace(KeyspaceMetadata keyspace) { - DatabaseDescriptor.getAuthorizer().revokeAllOn(DataResource.keyspace(ksName)); - DatabaseDescriptor.getAuthorizer().revokeAllOn(DataResource.allTables(ksName)); - DatabaseDescriptor.getAuthorizer().revokeAllOn(FunctionResource.keyspace(ksName)); + DatabaseDescriptor.getAuthorizer().revokeAllOn(DataResource.keyspace(keyspace.name)); + DatabaseDescriptor.getAuthorizer().revokeAllOn(DataResource.allTables(keyspace.name)); + DatabaseDescriptor.getAuthorizer().revokeAllOn(FunctionResource.keyspace(keyspace.name)); } - public void onDropTable(String ksName, String cfName) + @Override + public void onDropTable(TableMetadata table) { - DatabaseDescriptor.getAuthorizer().revokeAllOn(DataResource.table(ksName, cfName)); + DatabaseDescriptor.getAuthorizer().revokeAllOn(DataResource.table(table.keyspace, table.name)); } - public void onDropFunction(String ksName, String functionName, List> argTypes) + @Override + public void onDropFunction(UDFunction function) { DatabaseDescriptor.getAuthorizer() - .revokeAllOn(FunctionResource.function(ksName, functionName, argTypes)); + .revokeAllOn(FunctionResource.function(function.name().keyspace, function.name().name, function.argTypes())); } - public void onDropAggregate(String ksName, String aggregateName, List> argTypes) + @Override + public void onDropAggregate(UDAggregate aggregate) { DatabaseDescriptor.getAuthorizer() - .revokeAllOn(FunctionResource.function(ksName, aggregateName, argTypes)); + .revokeAllOn(FunctionResource.function(aggregate.name().keyspace, aggregate.name().name, aggregate.argTypes())); } } diff --git a/src/java/org/apache/cassandra/batchlog/BatchlogManager.java b/src/java/org/apache/cassandra/batchlog/BatchlogManager.java index 8897e374c2..ba0f3a6091 100644 --- a/src/java/org/apache/cassandra/batchlog/BatchlogManager.java +++ b/src/java/org/apache/cassandra/batchlog/BatchlogManager.java @@ -112,7 +112,7 @@ public class BatchlogManager implements BatchlogManagerMBean MBeanWrapper.instance.registerMBean(this, MBEAN_NAME); batchlogTasks.scheduleWithFixedDelay(this::replayFailedBatches, - StorageService.RING_DELAY, + StorageService.RING_DELAY_MILLIS, REPLAY_INTERVAL, MILLISECONDS); } diff --git a/src/java/org/apache/cassandra/cache/AutoSavingCache.java b/src/java/org/apache/cassandra/cache/AutoSavingCache.java index f6172ae524..1f383ec564 100644 --- a/src/java/org/apache/cassandra/cache/AutoSavingCache.java +++ b/src/java/org/apache/cassandra/cache/AutoSavingCache.java @@ -347,11 +347,6 @@ public class AutoSavingCache extends InstrumentingCache> argTypes) + @Override + public void onCreateFunction(UDFunction function) { - onCreateFunctionInternal(ksName, functionName, argTypes); + onCreateFunctionInternal(function.name().keyspace, function.name().name, function.argTypes()); } - public void onCreateAggregate(String ksName, String aggregateName, List> argTypes) + @Override + public void onCreateAggregate(UDAggregate aggregate) { - onCreateFunctionInternal(ksName, aggregateName, argTypes); + onCreateFunctionInternal(aggregate.name().keyspace, aggregate.name().name, aggregate.argTypes()); } private static void onCreateFunctionInternal(String ksName, String functionName, List> argTypes) @@ -1019,51 +1025,58 @@ public class QueryProcessor implements QueryHandler removeInvalidPreparedStatementsForFunction(ksName, functionName); } - public void onAlterTable(String ksName, String cfName, boolean affectsStatements) + @Override + public void onAlterTable(TableMetadata before, TableMetadata after, boolean affectsStatements) { - logger.trace("Column definitions for {}.{} changed, invalidating related prepared statements", ksName, cfName); + logger.trace("Column definitions for {}.{} changed, invalidating related prepared statements", before.keyspace, before.name); if (affectsStatements) - removeInvalidPreparedStatements(ksName, cfName); + removeInvalidPreparedStatements(before.keyspace, before.name); } - public void onAlterFunction(String ksName, String functionName, List> argTypes) + @Override + public void onAlterFunction(UDFunction before, UDFunction after) { // Updating a function may imply we've changed the body of the function, so we need to invalid statements so that // the new definition is picked (the function is resolved at preparation time). // TODO: if the function has multiple overload, we could invalidate only the statement refering to the overload // that was updated. This requires a few changes however and probably doesn't matter much in practice. - removeInvalidPreparedStatementsForFunction(ksName, functionName); + removeInvalidPreparedStatementsForFunction(before.name().keyspace, before.name().name); } - public void onAlterAggregate(String ksName, String aggregateName, List> argTypes) + @Override + public void onAlterAggregate(UDAggregate before, UDAggregate after) { // Updating a function may imply we've changed the body of the function, so we need to invalid statements so that // the new definition is picked (the function is resolved at preparation time). // TODO: if the function has multiple overload, we could invalidate only the statement refering to the overload // that was updated. This requires a few changes however and probably doesn't matter much in practice. - removeInvalidPreparedStatementsForFunction(ksName, aggregateName); + removeInvalidPreparedStatementsForFunction(before.name().keyspace, before.name().name); } - public void onDropKeyspace(String ksName) + @Override + public void onDropKeyspace(KeyspaceMetadata keyspace) { - logger.trace("Keyspace {} was dropped, invalidating related prepared statements", ksName); - removeInvalidPreparedStatements(ksName, null); + logger.trace("Keyspace {} was dropped, invalidating related prepared statements", keyspace.name); + removeInvalidPreparedStatements(keyspace.name, null); } - public void onDropTable(String ksName, String cfName) + @Override + public void onDropTable(TableMetadata table) { - logger.trace("Table {}.{} was dropped, invalidating related prepared statements", ksName, cfName); - removeInvalidPreparedStatements(ksName, cfName); + logger.trace("Table {}.{} was dropped, invalidating related prepared statements", table.keyspace, table.name); + removeInvalidPreparedStatements(table.keyspace, table.name); } - public void onDropFunction(String ksName, String functionName, List> argTypes) + @Override + public void onDropFunction(UDFunction function) { - removeInvalidPreparedStatementsForFunction(ksName, functionName); + removeInvalidPreparedStatementsForFunction(function.name().keyspace, function.name().name); } - public void onDropAggregate(String ksName, String aggregateName, List> argTypes) + @Override + public void onDropAggregate(UDAggregate aggregate) { - removeInvalidPreparedStatementsForFunction(ksName, aggregateName); + removeInvalidPreparedStatementsForFunction(aggregate.name().keyspace, aggregate.name().name); } } } diff --git a/src/java/org/apache/cassandra/cql3/statements/DescribeStatement.java b/src/java/org/apache/cassandra/cql3/statements/DescribeStatement.java index 16671dd38d..b1f576e41f 100644 --- a/src/java/org/apache/cassandra/cql3/statements/DescribeStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/DescribeStatement.java @@ -132,7 +132,7 @@ public abstract class DescribeStatement extends CQLStatement.Raw implements C @Override public ResultMessage executeLocally(QueryState state, QueryOptions options) { - Keyspaces keyspaces = Schema.instance.snapshot(); + Keyspaces keyspaces = Schema.instance.distributedAndLocalKeyspaces(); UUID schemaVersion = Schema.instance.getVersion(); keyspaces = Keyspaces.builder() diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/AlterSchemaStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/AlterSchemaStatement.java index 3b40c68dbd..0c411243b4 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/AlterSchemaStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/AlterSchemaStatement.java @@ -105,11 +105,11 @@ abstract public class AlterSchemaStatement implements CQLStatement.SingleKeyspac validateKeyspaceName(); - KeyspacesDiff diff = MigrationManager.announce(this, locally); + SchemaTransformationResult result = Schema.instance.transform(this, locally); - clientWarnings(diff).forEach(ClientWarn.instance::warn); + clientWarnings(result.diff).forEach(ClientWarn.instance::warn); - if (diff.isEmpty()) + if (result.diff.isEmpty()) return new ResultMessage.Void(); /* @@ -121,9 +121,9 @@ abstract public class AlterSchemaStatement implements CQLStatement.SingleKeyspac */ AuthenticatedUser user = state.getClientState().getUser(); if (null != user && !user.isAnonymous()) - createdResources(diff).forEach(r -> grantPermissionsOnResource(r, user)); + createdResources(result.diff).forEach(r -> grantPermissionsOnResource(r, user)); - return new ResultMessage.SchemaChange(schemaChangeEvent(diff)); + return new ResultMessage.SchemaChange(schemaChangeEvent(result.diff)); } private void validateKeyspaceName() diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java index 386dbaea12..61cac8a591 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java @@ -85,7 +85,7 @@ public abstract class AlterTableStatement extends AlterSchemaStatement this.tableName = tableName; } - public Keyspaces apply(Keyspaces schema) throws UnknownHostException + public Keyspaces apply(Keyspaces schema) { KeyspaceMetadata keyspace = schema.getNullable(keyspaceName); @@ -123,7 +123,7 @@ public abstract class AlterTableStatement extends AlterSchemaStatement return format("%s (%s, %s)", getClass().getSimpleName(), keyspaceName, tableName); } - abstract KeyspaceMetadata apply(KeyspaceMetadata keyspace, TableMetadata table) throws UnknownHostException; + abstract KeyspaceMetadata apply(KeyspaceMetadata keyspace, TableMetadata table); /** * ALTER TABLE ALTER TYPE ; diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateTableStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateTableStatement.java index 44a6042a84..8ab8383576 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/CreateTableStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateTableStatement.java @@ -140,6 +140,7 @@ public final class CreateTableStatement extends AlterSchemaStatement { int totalUserTables = Schema.instance.getUserKeyspaces() .stream() + .map(ksm -> ksm.name) .map(Keyspace::open) .mapToInt(keyspace -> keyspace.getColumnFamilyStores().size()) .sum(); diff --git a/src/java/org/apache/cassandra/db/ColumnFamilyStore.java b/src/java/org/apache/cassandra/db/ColumnFamilyStore.java index 542bf0bfa4..b2577906f4 100644 --- a/src/java/org/apache/cassandra/db/ColumnFamilyStore.java +++ b/src/java/org/apache/cassandra/db/ColumnFamilyStore.java @@ -575,10 +575,15 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean /** call when dropping or renaming a CF. Performs mbean housekeeping and invalidates CFS to other operations */ public void invalidate() { - invalidate(true); + invalidate(true, true); } public void invalidate(boolean expectMBean) + { + invalidate(expectMBean, true); + } + + public void invalidate(boolean expectMBean, boolean dropData) { // disable and cancel in-progress compactions before invalidating valid = false; @@ -600,9 +605,12 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean compactionStrategyManager.shutdown(); SystemKeyspace.removeTruncationRecord(metadata.id); - data.dropSSTables(); - LifecycleTransaction.waitForDeletions(); - indexManager.dropAllIndexes(); + if (dropData) + { + data.dropSSTables(); + LifecycleTransaction.waitForDeletions(); + } + indexManager.dropAllIndexes(dropData); invalidateCaches(); } @@ -2021,7 +2029,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean try (PrintStream out = new PrintStream(new FileOutputStreamPlus(schemaFile))) { SchemaCQLHelper.reCreateStatementsForSchemaCql(metadata(), - keyspace.getMetadata().types) + keyspace.getMetadata()) .forEach(out::println); } } diff --git a/src/java/org/apache/cassandra/db/Keyspace.java b/src/java/org/apache/cassandra/db/Keyspace.java index 1457b89a69..34b8298f82 100644 --- a/src/java/org/apache/cassandra/db/Keyspace.java +++ b/src/java/org/apache/cassandra/db/Keyspace.java @@ -129,9 +129,20 @@ public class Keyspace initialized = true; } + /** + * Never use it in production code. + * + * Useful when creating a fake Schema so that it does not manage Keyspace instances (and CFS) + */ + @VisibleForTesting + public static void unsetInitialized() + { + initialized = false; + } + public static Keyspace open(String keyspaceName) { - assert initialized || SchemaConstants.isLocalSystemKeyspace(keyspaceName); + assert initialized || SchemaConstants.isLocalSystemKeyspace(keyspaceName) : "Initialized: " + initialized; return open(keyspaceName, Schema.instance, true); } @@ -141,8 +152,7 @@ public class Keyspace return open(keyspaceName, Schema.instance, false); } - @VisibleForTesting - static Keyspace open(String keyspaceName, SchemaProvider schema, boolean loadSSTables) + public static Keyspace open(String keyspaceName, SchemaProvider schema, boolean loadSSTables) { return schema.maybeAddKeyspaceInstance(keyspaceName, () -> new Keyspace(keyspaceName, schema, loadSSTables)); } @@ -372,33 +382,32 @@ public class Keyspace replicationParams = ksm.params.replication; } - // best invoked on the compaction mananger. - public void dropCf(TableId tableId) + // best invoked on the compaction manager. + public void dropCf(TableId tableId, boolean dropData) { - assert columnFamilyStores.containsKey(tableId); ColumnFamilyStore cfs = columnFamilyStores.remove(tableId); if (cfs == null) return; cfs.onTableDropped(); - unloadCf(cfs); + unloadCf(cfs, dropData); } /** * Unloads all column family stores and releases metrics. */ - public void unload() + public void unload(boolean dropData) { for (ColumnFamilyStore cfs : getColumnFamilyStores()) - unloadCf(cfs); + unloadCf(cfs, dropData); metric.release(); } // disassociate a cfs from this keyspace instance. - private void unloadCf(ColumnFamilyStore cfs) + private void unloadCf(ColumnFamilyStore cfs, boolean dropData) { cfs.forceBlockingFlush(); - cfs.invalidate(); + cfs.invalidate(true, dropData); } /** @@ -755,12 +764,12 @@ public class Keyspace public static Iterable nonSystem() { - return Iterables.transform(Schema.instance.getNonSystemKeyspaces(), Keyspace::open); + return Iterables.transform(Schema.instance.getNonSystemKeyspaces().names(), Keyspace::open); } public static Iterable nonLocalStrategy() { - return Iterables.transform(Schema.instance.getNonLocalStrategyKeyspaces(), Keyspace::open); + return Iterables.transform(Schema.instance.getNonLocalStrategyKeyspaces().names(), Keyspace::open); } public static Iterable system() diff --git a/src/java/org/apache/cassandra/db/SchemaCQLHelper.java b/src/java/org/apache/cassandra/db/SchemaCQLHelper.java index ca9ef25e61..b959ebcece 100644 --- a/src/java/org/apache/cassandra/db/SchemaCQLHelper.java +++ b/src/java/org/apache/cassandra/db/SchemaCQLHelper.java @@ -42,12 +42,12 @@ public class SchemaCQLHelper /** * Generates the DDL statement for a {@code schema.cql} snapshot file. */ - public static Stream reCreateStatementsForSchemaCql(TableMetadata metadata, Types types) + public static Stream reCreateStatementsForSchemaCql(TableMetadata metadata, KeyspaceMetadata keyspaceMetadata) { // Types come first, as table can't be created without them - Stream udts = SchemaCQLHelper.getUserTypesAsCQL(metadata, types, true); + Stream udts = SchemaCQLHelper.getUserTypesAsCQL(metadata, keyspaceMetadata.types, true); - Stream tableMatadata = Stream.of(SchemaCQLHelper.getTableMetadataAsCQL(metadata)); + Stream tableMatadata = Stream.of(SchemaCQLHelper.getTableMetadataAsCQL(metadata, keyspaceMetadata)); Stream indexes = SchemaCQLHelper.getIndexesAsCQL(metadata, true); return Stream.of(udts, tableMatadata, indexes).flatMap(Function.identity()); @@ -60,11 +60,10 @@ public class SchemaCQLHelper * that will not contain everything needed for user types. */ @VisibleForTesting - public static String getTableMetadataAsCQL(TableMetadata metadata) + public static String getTableMetadataAsCQL(TableMetadata metadata, KeyspaceMetadata keyspaceMetadata) { if (metadata.isView()) { - KeyspaceMetadata keyspaceMetadata = Schema.instance.getKeyspaceMetadata(metadata.keyspace); ViewMetadata viewMetadata = keyspaceMetadata.views.get(metadata.name).orElse(null); assert viewMetadata != null; /* diff --git a/src/java/org/apache/cassandra/db/SizeEstimatesRecorder.java b/src/java/org/apache/cassandra/db/SizeEstimatesRecorder.java index c5e9653225..cbd21da0f4 100644 --- a/src/java/org/apache/cassandra/db/SizeEstimatesRecorder.java +++ b/src/java/org/apache/cassandra/db/SizeEstimatesRecorder.java @@ -32,6 +32,7 @@ import org.apache.cassandra.io.sstable.format.SSTableReader; import org.apache.cassandra.locator.TokenMetadata; import org.apache.cassandra.schema.Schema; import org.apache.cassandra.schema.SchemaChangeListener; +import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.service.StorageService; import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.Pair; @@ -48,7 +49,7 @@ import static org.apache.cassandra.utils.Clock.Global.nanoTime; * * See CASSANDRA-7688. */ -public class SizeEstimatesRecorder extends SchemaChangeListener implements Runnable +public class SizeEstimatesRecorder implements SchemaChangeListener, Runnable { private static final Logger logger = LoggerFactory.getLogger(SizeEstimatesRecorder.class); @@ -177,8 +178,8 @@ public class SizeEstimatesRecorder extends SchemaChangeListener implements Runna } @Override - public void onDropTable(String keyspace, String table) + public void onDropTable(TableMetadata table) { - SystemKeyspace.clearEstimates(keyspace, table); + SystemKeyspace.clearEstimates(table.keyspace, table.name); } } diff --git a/src/java/org/apache/cassandra/db/SystemKeyspace.java b/src/java/org/apache/cassandra/db/SystemKeyspace.java index e837aa51f3..ef4e2c6ca2 100644 --- a/src/java/org/apache/cassandra/db/SystemKeyspace.java +++ b/src/java/org/apache/cassandra/db/SystemKeyspace.java @@ -491,11 +491,6 @@ public final class SystemKeyspace DECOMMISSIONED } - public static void finishStartup() - { - Schema.instance.saveSystemKeyspace(); - } - public static void persistLocalMetadata() { String req = "INSERT INTO system.%s (" + @@ -1174,6 +1169,20 @@ public final class SystemKeyspace return hostId; } + /** + * Gets the schema version or null if missing + */ + public static UUID getSchemaVersion() + { + String req = "SELECT schema_version FROM system.%s WHERE key='%s'"; + UntypedResultSet result = executeInternal(format(req, LOCAL, LOCAL)); + + if (!result.isEmpty() && result.one().has("schema_version")) + return result.one().getUUID("schema_version"); + + return null; + } + /** * Gets the stored rack for the local node, or null if none have been set yet. */ diff --git a/src/java/org/apache/cassandra/db/commitlog/AbstractCommitLogSegmentManager.java b/src/java/org/apache/cassandra/db/commitlog/AbstractCommitLogSegmentManager.java index de2922496c..a1eb7bc777 100644 --- a/src/java/org/apache/cassandra/db/commitlog/AbstractCommitLogSegmentManager.java +++ b/src/java/org/apache/cassandra/db/commitlog/AbstractCommitLogSegmentManager.java @@ -202,7 +202,7 @@ public abstract class AbstractCommitLogSegmentManager if (flushingSize + unused >= 0) break; } - flushDataFrom(segmentsToRecycle, false); + flushDataFrom(segmentsToRecycle, Collections.emptyList(), false); } } @@ -294,7 +294,7 @@ public abstract class AbstractCommitLogSegmentManager * This is necessary to avoid resurrecting data during replay if a user creates a new table with * the same name and ID. See CASSANDRA-16986 for more details. */ - void forceRecycleAll(Iterable droppedTables) + void forceRecycleAll(Collection droppedTables) { List segmentsToRecycle = new ArrayList<>(activeSegments); CommitLogSegment last = segmentsToRecycle.get(segmentsToRecycle.size() - 1); @@ -308,7 +308,7 @@ public abstract class AbstractCommitLogSegmentManager Keyspace.writeOrder.awaitNewBarrier(); // flush and wait for all CFs that are dirty in segments up-to and including 'last' - Future future = flushDataFrom(segmentsToRecycle, true); + Future future = flushDataFrom(segmentsToRecycle, droppedTables, true); try { future.get(); @@ -394,7 +394,7 @@ public abstract class AbstractCommitLogSegmentManager * * @return a Future that will finish when all the flushes are complete. */ - private Future flushDataFrom(List segments, boolean force) + private Future flushDataFrom(List segments, Collection droppedTables, boolean force) { if (segments.isEmpty()) return ImmediateFuture.success(null); @@ -407,7 +407,9 @@ public abstract class AbstractCommitLogSegmentManager { for (TableId dirtyTableId : segment.getDirtyTableIds()) { - TableMetadata metadata = Schema.instance.getTableMetadata(dirtyTableId); + TableMetadata metadata = droppedTables.contains(dirtyTableId) + ? null + : Schema.instance.getTableMetadata(dirtyTableId); if (metadata == null) { // even though we remove the schema entry before a final flush when dropping a CF, diff --git a/src/java/org/apache/cassandra/db/commitlog/CommitLog.java b/src/java/org/apache/cassandra/db/commitlog/CommitLog.java index 03f328dff6..d243e0d023 100644 --- a/src/java/org/apache/cassandra/db/commitlog/CommitLog.java +++ b/src/java/org/apache/cassandra/db/commitlog/CommitLog.java @@ -233,7 +233,7 @@ public class CommitLog implements CommitLogMBean /** * Flushes all dirty CFs, waiting for them to free and recycle any segments they were retaining */ - public void forceRecycleAllSegments(Iterable droppedTables) + public void forceRecycleAllSegments(Collection droppedTables) { segmentManager.forceRecycleAll(droppedTables); } diff --git a/src/java/org/apache/cassandra/db/compaction/CompactionManager.java b/src/java/org/apache/cassandra/db/compaction/CompactionManager.java index 15922376f1..47ed3d5e11 100644 --- a/src/java/org/apache/cassandra/db/compaction/CompactionManager.java +++ b/src/java/org/apache/cassandra/db/compaction/CompactionManager.java @@ -1137,7 +1137,7 @@ public class CompactionManager implements CompactionManagerMBean /* Used in tests. */ public void disableAutoCompaction() { - for (String ksname : Schema.instance.getNonSystemKeyspaces()) + for (String ksname : Schema.instance.getNonSystemKeyspaces().names()) { for (ColumnFamilyStore cfs : Keyspace.open(ksname).getColumnFamilyStores()) cfs.disableAutoCompaction(); diff --git a/src/java/org/apache/cassandra/db/guardrails/Guardrails.java b/src/java/org/apache/cassandra/db/guardrails/Guardrails.java index fa310e346d..13ac27c406 100644 --- a/src/java/org/apache/cassandra/db/guardrails/Guardrails.java +++ b/src/java/org/apache/cassandra/db/guardrails/Guardrails.java @@ -175,7 +175,7 @@ public final class Guardrails implements GuardrailsMBean */ public static boolean enabled(ClientState state) { - return CONFIG_PROVIDER.getOrCreate(state).getEnabled() && DatabaseDescriptor.isDaemonInitialized(); + return DatabaseDescriptor.isDaemonInitialized() && CONFIG_PROVIDER.getOrCreate(state).getEnabled(); } @Override diff --git a/src/java/org/apache/cassandra/dht/BootStrapper.java b/src/java/org/apache/cassandra/dht/BootStrapper.java index 9575f6ce6d..9b017c6124 100644 --- a/src/java/org/apache/cassandra/dht/BootStrapper.java +++ b/src/java/org/apache/cassandra/dht/BootStrapper.java @@ -73,7 +73,7 @@ public class BootStrapper extends ProgressEventNotifierSupport stateStore, true, DatabaseDescriptor.getStreamingConnectionsPerHost()); - final List nonLocalStrategyKeyspaces = Schema.instance.getNonLocalStrategyKeyspaces(); + final Collection nonLocalStrategyKeyspaces = Schema.instance.getNonLocalStrategyKeyspaces().names(); if (nonLocalStrategyKeyspaces.isEmpty()) logger.debug("Schema does not contain any non-local keyspaces to stream on bootstrap"); for (String keyspaceName : nonLocalStrategyKeyspaces) @@ -153,7 +153,7 @@ public class BootStrapper extends ProgressEventNotifierSupport * otherwise, if allocationKeyspace is specified use the token allocation algorithm to generate suitable tokens * else choose num_tokens tokens at random */ - public static Collection getBootstrapTokens(final TokenMetadata metadata, InetAddressAndPort address, long schemaWaitDelay) throws ConfigurationException + public static Collection getBootstrapTokens(final TokenMetadata metadata, InetAddressAndPort address, long schemaTimeoutMillis, long ringTimeoutMillis) throws ConfigurationException { String allocationKeyspace = DatabaseDescriptor.getAllocateTokensForKeyspace(); Integer allocationLocalRf = DatabaseDescriptor.getAllocateTokensForLocalRf(); @@ -174,10 +174,10 @@ public class BootStrapper extends ProgressEventNotifierSupport throw new ConfigurationException("num_tokens must be >= 1"); if (allocationKeyspace != null) - return allocateTokens(metadata, address, allocationKeyspace, numTokens, schemaWaitDelay); + return allocateTokens(metadata, address, allocationKeyspace, numTokens, schemaTimeoutMillis, ringTimeoutMillis); if (allocationLocalRf != null) - return allocateTokens(metadata, address, allocationLocalRf, numTokens, schemaWaitDelay); + return allocateTokens(metadata, address, allocationLocalRf, numTokens, schemaTimeoutMillis, ringTimeoutMillis); if (numTokens == 1) logger.warn("Picking random token for a single vnode. You should probably add more vnodes and/or use the automatic token allocation mechanism."); @@ -206,9 +206,10 @@ public class BootStrapper extends ProgressEventNotifierSupport InetAddressAndPort address, String allocationKeyspace, int numTokens, - long schemaWaitDelay) + long schemaTimeoutMillis, + long ringTimeoutMillis) { - StorageService.instance.waitForSchema(schemaWaitDelay); + StorageService.instance.waitForSchema(schemaTimeoutMillis, ringTimeoutMillis); if (!FBUtilities.getBroadcastAddressAndPort().equals(InetAddressAndPort.getLoopbackAddress())) Gossiper.waitToSettle(); @@ -227,9 +228,10 @@ public class BootStrapper extends ProgressEventNotifierSupport InetAddressAndPort address, int rf, int numTokens, - long schemaWaitDelay) + long schemaTimeoutMillis, + long ringTimeoutMillis) { - StorageService.instance.waitForSchema(schemaWaitDelay); + StorageService.instance.waitForSchema(schemaTimeoutMillis, ringTimeoutMillis); if (!FBUtilities.getBroadcastAddressAndPort().equals(InetAddressAndPort.getLoopbackAddress())) Gossiper.waitToSettle(); diff --git a/src/java/org/apache/cassandra/gms/Gossiper.java b/src/java/org/apache/cassandra/gms/Gossiper.java index 7ee36a4083..05a4f219a7 100644 --- a/src/java/org/apache/cassandra/gms/Gossiper.java +++ b/src/java/org/apache/cassandra/gms/Gossiper.java @@ -129,7 +129,7 @@ public class Gossiper implements IFailureDetectionEventListener, GossiperMBean private volatile ScheduledFuture scheduledGossipTask; private static final ReentrantLock taskLock = new ReentrantLock(); public final static int intervalInMillis = 1000; - public final static int QUARANTINE_DELAY = GOSSIPER_QUARANTINE_DELAY.getInt(StorageService.RING_DELAY * 2); + public final static int QUARANTINE_DELAY = GOSSIPER_QUARANTINE_DELAY.getInt(StorageService.RING_DELAY_MILLIS * 2); private static final Logger logger = LoggerFactory.getLogger(Gossiper.class); private static final NoSpamLogger noSpamLogger = NoSpamLogger.getLogger(logger, 15L, TimeUnit.MINUTES); @@ -780,8 +780,8 @@ public class Gossiper implements IFailureDetectionEventListener, GossiperMBean // remember this node's generation int generation = epState.getHeartBeatState().getGeneration(); logger.info("Removing host: {}", hostId); - logger.info("Sleeping for {}ms to ensure {} does not change", StorageService.RING_DELAY, endpoint); - Uninterruptibles.sleepUninterruptibly(StorageService.RING_DELAY, TimeUnit.MILLISECONDS); + logger.info("Sleeping for {}ms to ensure {} does not change", StorageService.RING_DELAY_MILLIS, endpoint); + Uninterruptibles.sleepUninterruptibly(StorageService.RING_DELAY_MILLIS, TimeUnit.MILLISECONDS); // make sure it did not change epState = endpointStateMap.get(endpoint); if (epState.getHeartBeatState().getGeneration() != generation) @@ -849,8 +849,8 @@ public class Gossiper implements IFailureDetectionEventListener, GossiperMBean { int generation = epState.getHeartBeatState().getGeneration(); int heartbeat = epState.getHeartBeatState().getHeartBeatVersion(); - logger.info("Sleeping for {}ms to ensure {} does not change", StorageService.RING_DELAY, endpoint); - Uninterruptibles.sleepUninterruptibly(StorageService.RING_DELAY, TimeUnit.MILLISECONDS); + logger.info("Sleeping for {}ms to ensure {} does not change", StorageService.RING_DELAY_MILLIS, endpoint); + Uninterruptibles.sleepUninterruptibly(StorageService.RING_DELAY_MILLIS, TimeUnit.MILLISECONDS); // make sure it did not change EndpointState newState = endpointStateMap.get(endpoint); if (newState == null) @@ -1652,7 +1652,7 @@ public class Gossiper implements IFailureDetectionEventListener, GossiperMBean } // notify that an application state has changed - private void doOnChangeNotifications(InetAddressAndPort addr, ApplicationState state, VersionedValue value) + public void doOnChangeNotifications(InetAddressAndPort addr, ApplicationState state, VersionedValue value) { for (IEndpointStateChangeSubscriber subscriber : subscribers) { @@ -1864,7 +1864,7 @@ public class Gossiper implements IFailureDetectionEventListener, GossiperMBean boolean isSeed = DatabaseDescriptor.getSeeds().contains(getBroadcastAddressAndPort()); // We double RING_DELAY if we're not a seed to increase chance of successful startup during a full cluster bounce, // giving the seeds a chance to startup before we fail the shadow round - int shadowRoundDelay = isSeed ? StorageService.RING_DELAY : StorageService.RING_DELAY * 2; + int shadowRoundDelay = isSeed ? StorageService.RING_DELAY_MILLIS : StorageService.RING_DELAY_MILLIS * 2; seedsInShadowRound.clear(); endpointShadowStateMap.clear(); // send a completely empty syn diff --git a/src/java/org/apache/cassandra/index/SecondaryIndexManager.java b/src/java/org/apache/cassandra/index/SecondaryIndexManager.java index c296880586..623b264b0e 100644 --- a/src/java/org/apache/cassandra/index/SecondaryIndexManager.java +++ b/src/java/org/apache/cassandra/index/SecondaryIndexManager.java @@ -780,10 +780,11 @@ public class SecondaryIndexManager implements IndexRegistry, INotificationConsum /** * Remove all indexes */ - public void dropAllIndexes() + public void dropAllIndexes(boolean dropData) { markAllIndexesRemoved(); - invalidateAllIndexesBlocking(); + if (dropData) + invalidateAllIndexesBlocking(); } @VisibleForTesting diff --git a/src/java/org/apache/cassandra/io/sstable/CQLSSTableWriter.java b/src/java/org/apache/cassandra/io/sstable/CQLSSTableWriter.java index eab47c4289..204bdd9ded 100644 --- a/src/java/org/apache/cassandra/io/sstable/CQLSSTableWriter.java +++ b/src/java/org/apache/cassandra/io/sstable/CQLSSTableWriter.java @@ -29,6 +29,9 @@ import java.util.SortedSet; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import com.google.common.base.Preconditions; +import com.google.common.collect.Sets; + import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.statements.schema.CreateTableStatement; import org.apache.cassandra.cql3.statements.schema.CreateTypeStatement; @@ -42,7 +45,6 @@ import org.apache.cassandra.cql3.functions.types.UserType; import org.apache.cassandra.cql3.statements.ModificationStatement; import org.apache.cassandra.cql3.statements.UpdateStatement; import org.apache.cassandra.db.Clustering; -import org.apache.cassandra.db.SystemKeyspace; import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.dht.IPartitioner; import org.apache.cassandra.dht.Murmur3Partitioner; @@ -101,6 +103,7 @@ public class CQLSSTableWriter implements Closeable static { + System.setProperty("cassandra.schema.force_load_local_keyspaces", "true"); DatabaseDescriptor.clientInitialization(false); // Partitioner is not set in client mode. if (DatabaseDescriptor.getPartitioner() == null) @@ -514,24 +517,19 @@ public class CQLSSTableWriter implements Closeable if (insertStatement == null) throw new IllegalStateException("No insert statement specified, you should provide an insert statement through using()"); + Preconditions.checkState(Sets.difference(SchemaConstants.LOCAL_SYSTEM_KEYSPACE_NAMES, Schema.instance.getKeyspaces()).isEmpty(), + "Local keyspaces were not loaded. If this is running as a client, please make sure to add %s=true system property.", Schema.FORCE_LOAD_LOCAL_KEYSPACES_PROP); synchronized (CQLSSTableWriter.class) { - if (Schema.instance.getKeyspaceMetadata(SchemaConstants.SCHEMA_KEYSPACE_NAME) == null) - Schema.instance.load(Schema.getSystemKeyspaceMetadata()); - if (Schema.instance.getKeyspaceMetadata(SchemaConstants.SYSTEM_KEYSPACE_NAME) == null) - Schema.instance.load(SystemKeyspace.metadata()); String keyspaceName = schemaStatement.keyspace(); - if (Schema.instance.getKeyspaceMetadata(keyspaceName) == null) - { - Schema.instance.load(KeyspaceMetadata.create(keyspaceName, - KeyspaceParams.simple(1), - Tables.none(), - Views.none(), - Types.none(), - Functions.none())); - } + Schema.instance.transform(SchemaTransformations.addKeyspace(KeyspaceMetadata.create(keyspaceName, + KeyspaceParams.simple(1), + Tables.none(), + Views.none(), + Types.none(), + Functions.none()), true)); KeyspaceMetadata ksm = Schema.instance.getKeyspaceMetadata(keyspaceName); @@ -539,8 +537,9 @@ public class CQLSSTableWriter implements Closeable if (tableMetadata == null) { Types types = createTypes(keyspaceName); + Schema.instance.transform(SchemaTransformations.addTypes(types, true)); tableMetadata = createTable(types); - Schema.instance.load(ksm.withSwapped(ksm.tables.with(tableMetadata)).withSwapped(types)); + Schema.instance.transform(SchemaTransformations.addTable(tableMetadata, true)); } UpdateStatement preparedInsert = prepareInsert(); diff --git a/src/java/org/apache/cassandra/metrics/TableMetrics.java b/src/java/org/apache/cassandra/metrics/TableMetrics.java index 426f34dd12..c99e8a1b0e 100644 --- a/src/java/org/apache/cassandra/metrics/TableMetrics.java +++ b/src/java/org/apache/cassandra/metrics/TableMetrics.java @@ -279,7 +279,7 @@ public class TableMetrics { long total = 0; long filtered = 0; - for (String keyspace : Schema.instance.getNonSystemKeyspaces()) + for (String keyspace : Schema.instance.getNonSystemKeyspaces().names()) { Keyspace k = Schema.instance.getKeyspaceInstance(keyspace); diff --git a/src/java/org/apache/cassandra/net/Message.java b/src/java/org/apache/cassandra/net/Message.java index fc0e5c60cd..472c5aacde 100644 --- a/src/java/org/apache/cassandra/net/Message.java +++ b/src/java/org/apache/cassandra/net/Message.java @@ -257,6 +257,18 @@ public class Message return outWithParam(0, verb, payload, null, null); } + /** + * Used by the {@code MultiRangeReadCommand} to split multi-range responses from a replica + * into single-range responses. + */ + public static Message remoteResponse(InetAddressAndPort from, Verb verb, T payload) + { + assert verb.isResponse(); + long createdAtNanos = approxTime.now(); + long expiresAtNanos = verb.expiresAtNanos(createdAtNanos); + return new Message<>(new Header(0, verb, from, createdAtNanos, expiresAtNanos, 0, NO_PARAMS), payload); + } + /** Builds a response Message with provided payload, and all the right fields inferred from request Message */ public Message responseWith(T payload) { diff --git a/src/java/org/apache/cassandra/net/MessagingService.java b/src/java/org/apache/cassandra/net/MessagingService.java index ce2fdecfc8..ea019fd8fb 100644 --- a/src/java/org/apache/cassandra/net/MessagingService.java +++ b/src/java/org/apache/cassandra/net/MessagingService.java @@ -40,6 +40,7 @@ import org.apache.cassandra.db.SystemKeyspace; import org.apache.cassandra.exceptions.RequestFailureReason; import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.locator.Replica; +import org.apache.cassandra.metrics.MessagingMetrics; import org.apache.cassandra.service.AbstractWriteResponseHandler; import org.apache.cassandra.utils.ExecutorUtils; import org.apache.cassandra.utils.FBUtilities; @@ -198,7 +199,7 @@ import static org.apache.cassandra.utils.Throwables.maybeFail; * implemented in {@link org.apache.cassandra.db.virtual.InternodeInboundTable} and * {@link org.apache.cassandra.db.virtual.InternodeOutboundTable} respectively. */ -public final class MessagingService extends MessagingServiceMBeanImpl +public class MessagingService extends MessagingServiceMBeanImpl { private static final Logger logger = LoggerFactory.getLogger(MessagingService.class); @@ -263,7 +264,13 @@ public final class MessagingService extends MessagingServiceMBeanImpl @VisibleForTesting MessagingService(boolean testOnly) { - super(testOnly); + this(testOnly, new EndpointMessagingVersions(), new MessagingMetrics()); + } + + @VisibleForTesting + MessagingService(boolean testOnly, EndpointMessagingVersions versions, MessagingMetrics metrics) + { + super(testOnly, versions, metrics); OutboundConnections.scheduleUnusedConnectionMonitoring(this, ScheduledExecutors.scheduledTasks, 1L, TimeUnit.HOURS); } diff --git a/src/java/org/apache/cassandra/net/MessagingServiceMBeanImpl.java b/src/java/org/apache/cassandra/net/MessagingServiceMBeanImpl.java index a633e539bb..b77fa8396e 100644 --- a/src/java/org/apache/cassandra/net/MessagingServiceMBeanImpl.java +++ b/src/java/org/apache/cassandra/net/MessagingServiceMBeanImpl.java @@ -41,11 +41,13 @@ public class MessagingServiceMBeanImpl implements MessagingServiceMBean public final ConcurrentMap channelManagers = new ConcurrentHashMap<>(); public final ConcurrentMap messageHandlers = new ConcurrentHashMap<>(); - public final EndpointMessagingVersions versions = new EndpointMessagingVersions(); - public final MessagingMetrics metrics = new MessagingMetrics(); + public final EndpointMessagingVersions versions; + public final MessagingMetrics metrics; - MessagingServiceMBeanImpl(boolean testOnly) + public MessagingServiceMBeanImpl(boolean testOnly, EndpointMessagingVersions versions, MessagingMetrics metrics) { + this.versions = versions; + this.metrics = metrics; if (!testOnly) { MBeanWrapper.instance.registerMBean(this, MBEAN_NAME); diff --git a/src/java/org/apache/cassandra/net/Verb.java b/src/java/org/apache/cassandra/net/Verb.java index a43372e22d..0818d4194d 100644 --- a/src/java/org/apache/cassandra/net/Verb.java +++ b/src/java/org/apache/cassandra/net/Verb.java @@ -71,6 +71,7 @@ import org.apache.cassandra.repair.messages.SyncResponse; import org.apache.cassandra.repair.messages.SyncRequest; import org.apache.cassandra.repair.messages.ValidationResponse; import org.apache.cassandra.repair.messages.ValidationRequest; +import org.apache.cassandra.schema.SchemaMutationsSerializer; import org.apache.cassandra.schema.SchemaPullVerbHandler; import org.apache.cassandra.schema.SchemaPushVerbHandler; import org.apache.cassandra.schema.SchemaVersionVerbHandler; @@ -102,7 +103,6 @@ import static org.apache.cassandra.concurrent.Stage.*; import static org.apache.cassandra.net.VerbTimeouts.*; import static org.apache.cassandra.net.Verb.Kind.*; import static org.apache.cassandra.net.Verb.Priority.*; -import static org.apache.cassandra.schema.MigrationManager.MigrationsSerializer; /** * Note that priorities except P0 are presently unused. P0 corresponds to urgent, i.e. what used to be the "Gossip" connection. @@ -150,8 +150,8 @@ public enum Verb // P1 because messages can be arbitrarily large or aren't crucial SCHEMA_PUSH_RSP (98, P1, rpcTimeout, MIGRATION, () -> NoPayload.serializer, () -> ResponseVerbHandler.instance ), - SCHEMA_PUSH_REQ (18, P1, rpcTimeout, MIGRATION, () -> MigrationsSerializer.instance, () -> SchemaPushVerbHandler.instance, SCHEMA_PUSH_RSP ), - SCHEMA_PULL_RSP (88, P1, rpcTimeout, MIGRATION, () -> MigrationsSerializer.instance, () -> ResponseVerbHandler.instance ), + SCHEMA_PUSH_REQ (18, P1, rpcTimeout, MIGRATION, () -> SchemaMutationsSerializer.instance, () -> SchemaPushVerbHandler.instance, SCHEMA_PUSH_RSP ), + SCHEMA_PULL_RSP (88, P1, rpcTimeout, MIGRATION, () -> SchemaMutationsSerializer.instance, () -> ResponseVerbHandler.instance ), SCHEMA_PULL_REQ (28, P1, rpcTimeout, MIGRATION, () -> NoPayload.serializer, () -> SchemaPullVerbHandler.instance, SCHEMA_PULL_RSP ), SCHEMA_VERSION_RSP (80, P1, rpcTimeout, MIGRATION, () -> UUIDSerializer.serializer, () -> ResponseVerbHandler.instance ), SCHEMA_VERSION_REQ (20, P1, rpcTimeout, MIGRATION, () -> NoPayload.serializer, () -> SchemaVersionVerbHandler.instance, SCHEMA_VERSION_RSP ), diff --git a/src/java/org/apache/cassandra/repair/consistent/admin/SchemaArgsParser.java b/src/java/org/apache/cassandra/repair/consistent/admin/SchemaArgsParser.java index 67c024447f..bfcc8cdae7 100644 --- a/src/java/org/apache/cassandra/repair/consistent/admin/SchemaArgsParser.java +++ b/src/java/org/apache/cassandra/repair/consistent/admin/SchemaArgsParser.java @@ -73,7 +73,7 @@ public class SchemaArgsParser implements Iterable if (schemaArgs.isEmpty()) { // iterate over everything - Iterator ksNames = Schema.instance.getNonLocalStrategyKeyspaces().iterator(); + Iterator ksNames = Schema.instance.getNonLocalStrategyKeyspaces().names().iterator(); return new AbstractIterator() { diff --git a/src/java/org/apache/cassandra/schema/DefaultSchemaUpdateHandler.java b/src/java/org/apache/cassandra/schema/DefaultSchemaUpdateHandler.java new file mode 100644 index 0000000000..437f48918d --- /dev/null +++ b/src/java/org/apache/cassandra/schema/DefaultSchemaUpdateHandler.java @@ -0,0 +1,283 @@ +/* + * 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 java.time.Duration; +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import com.google.common.annotations.VisibleForTesting; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.cassandra.concurrent.ScheduledExecutors; +import org.apache.cassandra.concurrent.Stage; +import org.apache.cassandra.config.CassandraRelevantProperties; +import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.db.Mutation; +import org.apache.cassandra.gms.ApplicationState; +import org.apache.cassandra.gms.EndpointState; +import org.apache.cassandra.gms.Gossiper; +import org.apache.cassandra.gms.IEndpointStateChangeSubscriber; +import org.apache.cassandra.gms.VersionedValue; +import org.apache.cassandra.locator.InetAddressAndPort; +import org.apache.cassandra.net.MessagingService; +import org.apache.cassandra.schema.SchemaTransformation.SchemaTransformationResult; +import org.apache.cassandra.service.StorageService; +import org.apache.cassandra.utils.FBUtilities; +import org.apache.cassandra.utils.Pair; +import org.apache.cassandra.utils.concurrent.ImmediateFuture; + +import static org.apache.cassandra.schema.MigrationCoordinator.MAX_OUTSTANDING_VERSION_REQUESTS; + +public class DefaultSchemaUpdateHandler implements SchemaUpdateHandler, IEndpointStateChangeSubscriber +{ + private static final Logger logger = LoggerFactory.getLogger(DefaultSchemaUpdateHandler.class); + + @VisibleForTesting + final MigrationCoordinator migrationCoordinator; + + private final boolean requireSchemas; + private final BiConsumer updateCallback; + private volatile DistributedSchema schema = DistributedSchema.EMPTY; + + private MigrationCoordinator createMigrationCoordinator(MessagingService messagingService) + { + return new MigrationCoordinator(messagingService, + Stage.MIGRATION.executor(), + ScheduledExecutors.scheduledTasks, + MAX_OUTSTANDING_VERSION_REQUESTS, + Gossiper.instance, + () -> schema.getVersion(), + (from, mutations) -> applyMutations(mutations)); + } + + public DefaultSchemaUpdateHandler(BiConsumer updateCallback) + { + this(null, MessagingService.instance(), !CassandraRelevantProperties.BOOTSTRAP_SKIP_SCHEMA_CHECK.getBoolean(), updateCallback); + } + + public DefaultSchemaUpdateHandler(MigrationCoordinator migrationCoordinator, + MessagingService messagingService, + boolean requireSchemas, + BiConsumer updateCallback) + { + this.requireSchemas = requireSchemas; + this.updateCallback = updateCallback; + this.migrationCoordinator = migrationCoordinator == null ? createMigrationCoordinator(messagingService) : migrationCoordinator; + Gossiper.instance.register(this); + SchemaPushVerbHandler.instance.register(msg -> applyMutations(msg.payload)); + SchemaPullVerbHandler.instance.register(msg -> messagingService.send(msg.responseWith(getSchemaMutations()), msg.from())); + } + + public synchronized void start() + { + if (StorageService.instance.isReplacing()) + onRemove(DatabaseDescriptor.getReplaceAddress()); + + SchemaKeyspace.saveSystemKeyspacesSchema(); + + migrationCoordinator.start(); + } + + @Override + public boolean waitUntilReady(Duration timeout) + { + logger.debug("Waiting for schema to be ready (max {})", timeout); + boolean schemasReceived = migrationCoordinator.awaitSchemaRequests(timeout.toMillis()); + + if (schemasReceived) + return true; + + logger.warn("There are nodes in the cluster with a different schema version than us, from which we did not merge schemas: " + + "our version: ({}), outstanding versions -> endpoints: {}. Use -D{}}=true to ignore this, " + + "-D{}= to skip specific endpoints, or -D{}= to skip specific schema versions", + Schema.instance.getVersion(), + migrationCoordinator.outstandingVersions(), + CassandraRelevantProperties.BOOTSTRAP_SKIP_SCHEMA_CHECK.getKey(), + MigrationCoordinator.IGNORED_ENDPOINTS_PROP, MigrationCoordinator.IGNORED_VERSIONS_PROP); + + if (requireSchemas) + { + logger.error("Didn't receive schemas for all known versions within the {}. Use -D{}=true to skip this check.", + timeout, CassandraRelevantProperties.BOOTSTRAP_SKIP_SCHEMA_CHECK.getKey()); + + return false; + } + + return true; + } + + @Override + public void onRemove(InetAddressAndPort endpoint) + { + migrationCoordinator.removeAndIgnoreEndpoint(endpoint); + } + + @Override + public void onChange(InetAddressAndPort endpoint, ApplicationState state, VersionedValue value) + { + if (state == ApplicationState.SCHEMA) + { + EndpointState epState = Gossiper.instance.getEndpointStateForEndpoint(endpoint); + if (epState != null && !Gossiper.instance.isDeadState(epState) && StorageService.instance.getTokenMetadata().isMember(endpoint)) + { + migrationCoordinator.reportEndpointVersion(endpoint, UUID.fromString(value.value)); + } + } + } + + @Override + public void onJoin(InetAddressAndPort endpoint, EndpointState epState) + { + // no-op + } + + @Override + public void beforeChange(InetAddressAndPort endpoint, EndpointState currentState, ApplicationState newStateKey, VersionedValue newValue) + { + // no-op + } + + @Override + public void onAlive(InetAddressAndPort endpoint, EndpointState state) + { + // no-op + } + + @Override + public void onDead(InetAddressAndPort endpoint, EndpointState state) + { + // no-op + } + + @Override + public void onRestart(InetAddressAndPort endpoint, EndpointState state) + { + // no-op + } + + private synchronized SchemaTransformationResult applyMutations(Collection schemaMutations) + { + // fetch the current state of schema for the affected keyspaces only + DistributedSchema before = schema; + + // apply the schema mutations + SchemaKeyspace.applyChanges(schemaMutations); + + // only compare the keyspaces affected by this set of schema mutations + Set affectedKeyspaces = SchemaKeyspace.affectedKeyspaces(schemaMutations); + + // apply the schema mutations and fetch the new versions of the altered keyspaces + Keyspaces updatedKeyspaces = SchemaKeyspace.fetchKeyspaces(affectedKeyspaces); + Set removedKeyspaces = affectedKeyspaces.stream().filter(ks -> !updatedKeyspaces.containsKeyspace(ks)).collect(Collectors.toSet()); + Keyspaces afterKeyspaces = before.getKeyspaces().withAddedOrReplaced(updatedKeyspaces).without(removedKeyspaces); + + Keyspaces.KeyspacesDiff diff = Keyspaces.diff(before.getKeyspaces(), afterKeyspaces); + UUID version = SchemaKeyspace.calculateSchemaDigest(); + DistributedSchema after = new DistributedSchema(afterKeyspaces, version); + SchemaTransformationResult update = new SchemaTransformationResult(before, after, diff); + + updateSchema(update, false); + return update; + } + + @Override + public synchronized SchemaTransformationResult apply(SchemaTransformation transformation, boolean local) + { + DistributedSchema before = schema; + Keyspaces afterKeyspaces = transformation.apply(before.getKeyspaces()); + Keyspaces.KeyspacesDiff diff = Keyspaces.diff(before.getKeyspaces(), afterKeyspaces); + + if (diff.isEmpty()) + return new SchemaTransformationResult(before, before, diff); + + Collection mutations = SchemaKeyspace.convertSchemaDiffToMutations(diff, transformation.fixedTimestampMicros().orElse(FBUtilities.timestampMicros())); + SchemaKeyspace.applyChanges(mutations); + + DistributedSchema after = new DistributedSchema(afterKeyspaces, SchemaKeyspace.calculateSchemaDigest()); + SchemaTransformationResult update = new SchemaTransformationResult(before, after, diff); + + updateSchema(update, local); + if (!local) + { + migrationCoordinator.executor.submit(() -> { + Pair, Set> endpoints = migrationCoordinator.pushSchemaMutations(mutations); + SchemaAnnouncementDiagnostics.schemaTransformationAnnounced(endpoints.left(), endpoints.right(), transformation); + }); + } + + return update; + } + + private void updateSchema(SchemaTransformationResult update, boolean local) + { + this.schema = update.after; + logger.debug("Schema updated: {}", update); + updateCallback.accept(update, true); + if (!local) + { + migrationCoordinator.announce(update.after.getVersion()); + } + } + + private synchronized SchemaTransformationResult reload() + { + DistributedSchema before = this.schema; + DistributedSchema after = new DistributedSchema(SchemaKeyspace.fetchNonSystemKeyspaces(), SchemaKeyspace.calculateSchemaDigest()); + Keyspaces.KeyspacesDiff diff = Keyspaces.diff(before.getKeyspaces(), after.getKeyspaces()); + SchemaTransformationResult update = new SchemaTransformationResult(before, after, diff); + + updateSchema(update, false); + return update; + } + + @Override + public SchemaTransformationResult reset(boolean local) + { + return local + ? reload() + : migrationCoordinator.pullSchemaFromAnyNode() + .flatMap(mutations -> ImmediateFuture.success(applyMutations(mutations))) + .awaitThrowUncheckedOnInterrupt() + .getNow(); + } + + @Override + public synchronized void clear() + { + SchemaKeyspace.truncate(); + this.schema = DistributedSchema.EMPTY; + } + + private synchronized Collection getSchemaMutations() + { + return SchemaKeyspace.convertSchemaToMutations(); + } + + public Map> getOutstandingSchemaVersions() + { + return migrationCoordinator.outstandingVersions(); + } +} diff --git a/src/java/org/apache/cassandra/schema/DefaultSchemaUpdateHandlerFactory.java b/src/java/org/apache/cassandra/schema/DefaultSchemaUpdateHandlerFactory.java new file mode 100644 index 0000000000..2303663492 --- /dev/null +++ b/src/java/org/apache/cassandra/schema/DefaultSchemaUpdateHandlerFactory.java @@ -0,0 +1,36 @@ +/* + * 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 java.util.function.BiConsumer; + +import org.apache.cassandra.schema.SchemaTransformation.SchemaTransformationResult; + +public class DefaultSchemaUpdateHandlerFactory implements SchemaUpdateHandlerFactory +{ + public static final SchemaUpdateHandlerFactory instance = new DefaultSchemaUpdateHandlerFactory(); + + @Override + public SchemaUpdateHandler getSchemaUpdateHandler(boolean online, BiConsumer updateSchemaCallback) + { + return online + ? new DefaultSchemaUpdateHandler(updateSchemaCallback) + : new OfflineSchemaUpdateHandler(updateSchemaCallback); + } +} diff --git a/src/java/org/apache/cassandra/schema/Diff.java b/src/java/org/apache/cassandra/schema/Diff.java index 7112c85ba2..dd26c7b0a4 100644 --- a/src/java/org/apache/cassandra/schema/Diff.java +++ b/src/java/org/apache/cassandra/schema/Diff.java @@ -61,4 +61,15 @@ public class Diff return String.format("%s -> %s (%s)", before, after, kind); } } + + @Override + public String toString() + { + return "Diff{" + + "created=" + created + + ", dropped=" + dropped + + ", altered=" + altered + + '}'; + } + } diff --git a/src/java/org/apache/cassandra/schema/DistributedSchema.java b/src/java/org/apache/cassandra/schema/DistributedSchema.java new file mode 100644 index 0000000000..4fed9bb1e8 --- /dev/null +++ b/src/java/org/apache/cassandra/schema/DistributedSchema.java @@ -0,0 +1,97 @@ +/* + * 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 java.util.Objects; +import java.util.UUID; + +import com.google.common.base.Preconditions; + +/** + * Immutable snapshot of the current schema along with its version. + */ +public class DistributedSchema +{ + public static final DistributedSchema EMPTY = new DistributedSchema(Keyspaces.none(), SchemaConstants.emptyVersion); + + private final Keyspaces keyspaces; + private final UUID version; + + public DistributedSchema(Keyspaces keyspaces, UUID version) + { + Objects.requireNonNull(keyspaces); + Objects.requireNonNull(version); + this.keyspaces = keyspaces; + this.version = version; + validate(); + } + + public Keyspaces getKeyspaces() + { + return keyspaces; + } + + public boolean isEmpty() + { + return SchemaConstants.emptyVersion.equals(version); + } + + public UUID getVersion() + { + return version; + } + + /** + * Converts the given schema version to a string. Returns {@code unknown}, if {@code version} is {@code null} + * or {@code "(empty)"}, if {@code version} refers to an {@link SchemaConstants#emptyVersion empty) schema. + */ + public static String schemaVersionToString(UUID version) + { + return version == null + ? "unknown" + : SchemaConstants.emptyVersion.equals(version) + ? "(empty)" + : version.toString(); + } + + @Override + public boolean equals(Object o) + { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + DistributedSchema schema = (DistributedSchema) o; + return keyspaces.equals(schema.keyspaces) && version.equals(schema.version); + } + + @Override + public int hashCode() + { + return Objects.hash(keyspaces, version); + } + + private void validate() + { + keyspaces.forEach(ksm -> { + ksm.tables.forEach(tm -> Preconditions.checkArgument(tm.keyspace.equals(ksm.name), "Table %s metadata points to keyspace %s while defined in keyspace %s", tm.name, tm.keyspace, ksm.name)); + ksm.views.forEach(vm -> Preconditions.checkArgument(vm.keyspace().equals(ksm.name), "View %s metadata points to keyspace %s while defined in keyspace %s", vm.name(), vm.keyspace(), ksm.name)); + ksm.types.forEach(ut -> Preconditions.checkArgument(ut.keyspace.equals(ksm.name), "Type %s points to keyspace %s while defined in keyspace %s", ut.name, ut.keyspace, ksm.name)); + ksm.functions.forEach(f -> Preconditions.checkArgument(f.name().keyspace.equals(ksm.name), "Function %s points to keyspace %s while defined in keyspace %s", f.name().name, f.name().keyspace, ksm.name)); + }); + } +} diff --git a/src/java/org/apache/cassandra/schema/KeyspaceMetadata.java b/src/java/org/apache/cassandra/schema/KeyspaceMetadata.java index a029168789..7978854e31 100644 --- a/src/java/org/apache/cassandra/schema/KeyspaceMetadata.java +++ b/src/java/org/apache/cassandra/schema/KeyspaceMetadata.java @@ -119,6 +119,11 @@ public final class KeyspaceMetadata implements SchemaElement return new KeyspaceMetadata(name, kind, params, tables, views, types, functions); } + public KeyspaceMetadata empty() + { + return new KeyspaceMetadata(this.name, this.kind, this.params, Tables.none(), Views.none(), Types.none(), Functions.none()); + } + public boolean isVirtual() { return kind == Kind.VIRTUAL; @@ -391,5 +396,19 @@ public final class KeyspaceMetadata implements SchemaElement return Optional.of(new KeyspaceDiff(before, after, tables, views, types, udfs, udas)); } + + @Override + public String toString() + { + return "KeyspaceDiff{" + + "before=" + before + + ", after=" + after + + ", tables=" + tables + + ", views=" + views + + ", types=" + types + + ", udfs=" + udfs + + ", udas=" + udas + + '}'; + } } } diff --git a/src/java/org/apache/cassandra/schema/Keyspaces.java b/src/java/org/apache/cassandra/schema/Keyspaces.java index 1938d93030..e9bd92c7c5 100644 --- a/src/java/org/apache/cassandra/schema/Keyspaces.java +++ b/src/java/org/apache/cassandra/schema/Keyspaces.java @@ -17,9 +17,9 @@ */ package org.apache.cassandra.schema; +import java.util.Collection; import java.util.Iterator; import java.util.Optional; -import java.util.Set; import java.util.function.Predicate; import java.util.stream.Stream; @@ -67,7 +67,7 @@ public final class Keyspaces implements Iterable return keyspaces.values().stream(); } - public Set names() + public ImmutableSet names() { return keyspaces.keySet(); } @@ -124,6 +124,11 @@ public final class Keyspaces implements Iterable return filter(k -> k != keyspace); } + public Keyspaces without(Collection names) + { + return filter(k -> !names.contains(k.name)); + } + public Keyspaces withAddedOrUpdated(KeyspaceMetadata keyspace) { return builder().add(Iterables.filter(this, k -> !k.name.equals(keyspace.name))) @@ -131,6 +136,37 @@ public final class Keyspaces implements Iterable .build(); } + /** + * Returns a new {@link Keyspaces} equivalent to this one, but with the provided keyspace metadata either added (if + * this {@link Keyspaces} does not have that keyspace), or replaced by the provided definition. + * + *

Note that if this contains the provided keyspace, its pre-existing definition is discarded and completely + * replaced with the newly provided one. See {@link #withAddedOrUpdated(KeyspaceMetadata)} if you wish the provided + * definition to be "merged" with the existing one instead. + * + * @param keyspace the keyspace metadata to add, or replace the existing definition with. + * @return the newly created object. + */ + public Keyspaces withAddedOrReplaced(KeyspaceMetadata keyspace) + { + return builder().add(Iterables.filter(this, k -> !k.name.equals(keyspace.name))) + .add(keyspace) + .build(); + } + + /** + * Calls {@link #withAddedOrReplaced(KeyspaceMetadata)} on all the keyspaces of the provided {@link Keyspaces}. + * + * @param keyspaces the keyspaces to add, or replace if existing. + * @return the newly created object. + */ + public Keyspaces withAddedOrReplaced(Keyspaces keyspaces) + { + return builder().add(Iterables.filter(this, k -> !keyspaces.containsKeyspace(k.name))) + .add(keyspaces) + .build(); + } + public void validate() { keyspaces.values().forEach(KeyspaceMetadata::validate); @@ -154,6 +190,11 @@ public final class Keyspaces implements Iterable return keyspaces.values().toString(); } + public int size() + { + return keyspaces.size(); + } + public static final class Builder { private final ImmutableMap.Builder keyspaces = new ImmutableMap.Builder<>(); @@ -192,14 +233,14 @@ public final class Keyspaces implements Iterable } } - static KeyspacesDiff diff(Keyspaces before, Keyspaces after) + public static KeyspacesDiff diff(Keyspaces before, Keyspaces after) { return KeyspacesDiff.diff(before, after); } public static final class KeyspacesDiff { - static final KeyspacesDiff NONE = new KeyspacesDiff(Keyspaces.none(), Keyspaces.none(), ImmutableList.of()); + public static final KeyspacesDiff NONE = new KeyspacesDiff(Keyspaces.none(), Keyspaces.none(), ImmutableList.of()); public final Keyspaces created; public final Keyspaces dropped; @@ -235,5 +276,15 @@ public final class Keyspaces implements Iterable { return created.isEmpty() && dropped.isEmpty() && altered.isEmpty(); } + + @Override + public String toString() + { + return "KeyspacesDiff{" + + "created=" + created + + ", dropped=" + dropped + + ", altered=" + altered + + '}'; + } } } diff --git a/src/java/org/apache/cassandra/schema/MigrationCoordinator.java b/src/java/org/apache/cassandra/schema/MigrationCoordinator.java index 399f917cb7..54da2ed783 100644 --- a/src/java/org/apache/cassandra/schema/MigrationCoordinator.java +++ b/src/java/org/apache/cassandra/schema/MigrationCoordinator.java @@ -23,36 +23,38 @@ import java.net.UnknownHostException; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.UUID; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.LongSupplier; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiConsumer; +import java.util.function.Supplier; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; -import org.apache.cassandra.concurrent.FutureTask; -import org.apache.cassandra.config.CassandraRelevantProperties; -import org.apache.cassandra.utils.Simulate; -import org.apache.cassandra.utils.concurrent.Future; -import org.apache.cassandra.utils.concurrent.ImmediateFuture; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.apache.cassandra.concurrent.ExecutorPlus; +import org.apache.cassandra.concurrent.FutureTask; import org.apache.cassandra.concurrent.ScheduledExecutors; -import org.apache.cassandra.concurrent.Stage; +import org.apache.cassandra.config.CassandraRelevantProperties; import org.apache.cassandra.db.Mutation; import org.apache.cassandra.exceptions.RequestFailureReason; import org.apache.cassandra.gms.ApplicationState; import org.apache.cassandra.gms.EndpointState; -import org.apache.cassandra.gms.FailureDetector; import org.apache.cassandra.gms.Gossiper; import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.net.Message; @@ -60,32 +62,36 @@ import org.apache.cassandra.net.MessagingService; import org.apache.cassandra.net.NoPayload; import org.apache.cassandra.net.RequestCallback; import org.apache.cassandra.net.Verb; +import org.apache.cassandra.service.StorageService; import org.apache.cassandra.utils.FBUtilities; +import org.apache.cassandra.utils.Pair; +import org.apache.cassandra.utils.Simulate; +import org.apache.cassandra.utils.concurrent.AsyncPromise; +import org.apache.cassandra.utils.concurrent.Future; +import org.apache.cassandra.utils.concurrent.ImmediateFuture; import org.apache.cassandra.utils.concurrent.WaitQueue; +import static org.apache.cassandra.net.Verb.SCHEMA_PUSH_REQ; import static org.apache.cassandra.utils.Clock.Global.nanoTime; import static org.apache.cassandra.utils.Simulate.With.MONITORS; import static org.apache.cassandra.utils.concurrent.WaitQueue.newWaitQueue; +/** + * Migration coordinator is responsible for tracking schema versions on various nodes and, if needed, synchronize the + * schema. It performs periodic checks and if there is a schema version mismatch between the current node and the other + * node, it pulls the schema and applies the changes locally through the callback. + * + * It works in close cooperation with {@link DefaultSchemaUpdateHandler} which is responsible for maintaining local + * schema metadata stored in {@link SchemaKeyspace}. + */ @Simulate(with = MONITORS) public class MigrationCoordinator { private static final Logger logger = LoggerFactory.getLogger(MigrationCoordinator.class); private static final Future FINISHED_FUTURE = ImmediateFuture.success(null); - private static LongSupplier getUptimeFn = () -> ManagementFactory.getRuntimeMXBean().getUptime(); - - @VisibleForTesting - public static void setUptimeFn(LongSupplier supplier) - { - getUptimeFn = supplier; - } - - private static final int MIGRATION_DELAY_IN_MS = CassandraRelevantProperties.MIGRATION_DELAY.getInt(); - private static final int MAX_OUTSTANDING_VERSION_REQUESTS = 3; - - public static final MigrationCoordinator instance = new MigrationCoordinator(); + public static final int MAX_OUTSTANDING_VERSION_REQUESTS = 3; public static final String IGNORED_VERSIONS_PROP = "cassandra.skip_schema_check_for_versions"; public static final String IGNORED_ENDPOINTS_PROP = "cassandra.skip_schema_check_for_endpoints"; @@ -169,44 +175,68 @@ public class MigrationCoordinator private final Map versionInfo = new HashMap<>(); private final Map endpointVersions = new HashMap<>(); - private final AtomicInteger inflightTasks = new AtomicInteger(); private final Set ignoredEndpoints = getIgnoredEndpoints(); + private final ScheduledExecutorService periodicCheckExecutor; + private final MessagingService messagingService; + private final AtomicReference> periodicPullTask = new AtomicReference<>(); + private final int maxOutstandingVersionRequests; + private final Gossiper gossiper; + private final Supplier schemaVersion; + private final BiConsumer> schemaUpdateCallback; - public void start() + final ExecutorPlus executor; + + /** + * Creates but does not start migration coordinator instance. + * @param messagingService messaging service instance used to communicate with other nodes for pulling schema + * and pushing changes + * @param periodicCheckExecutor executor on which the periodic checks are scheduled + */ + MigrationCoordinator(MessagingService messagingService, + ExecutorPlus executor, + ScheduledExecutorService periodicCheckExecutor, + int maxOutstandingVersionRequests, + Gossiper gossiper, + Supplier schemaVersionSupplier, + BiConsumer> schemaUpdateCallback) { - ScheduledExecutors.scheduledTasks.scheduleWithFixedDelay(this::pullUnreceivedSchemaVersions, 1, 1, TimeUnit.MINUTES); + this.messagingService = messagingService; + this.executor = executor; + this.periodicCheckExecutor = periodicCheckExecutor; + this.maxOutstandingVersionRequests = maxOutstandingVersionRequests; + this.gossiper = gossiper; + this.schemaVersion = schemaVersionSupplier; + this.schemaUpdateCallback = schemaUpdateCallback; } - public synchronized void reset() + void start() { - versionInfo.clear(); + announce(schemaVersion.get()); + periodicPullTask.updateAndGet(curTask -> curTask == null + ? periodicCheckExecutor.scheduleWithFixedDelay(this::pullUnreceivedSchemaVersions, 1, 1, TimeUnit.MINUTES) + : curTask); } - synchronized List> pullUnreceivedSchemaVersions() + private synchronized void pullUnreceivedSchemaVersions() { - List> futures = new ArrayList<>(); for (VersionInfo info : versionInfo.values()) { if (info.wasReceived() || info.outstandingRequests.size() > 0) continue; - Future future = maybePullSchema(info); - if (future != null && future != FINISHED_FUTURE) - futures.add(future); + maybePullSchema(info); } - - return futures; } - synchronized Future maybePullSchema(VersionInfo info) + private synchronized Future maybePullSchema(VersionInfo info) { if (info.endpoints.isEmpty() || info.wasReceived() || !shouldPullSchema(info.version)) return FINISHED_FUTURE; - if (info.outstandingRequests.size() >= getMaxOutstandingVersionRequests()) + if (info.outstandingRequests.size() >= maxOutstandingVersionRequests) return FINISHED_FUTURE; - for (int i=0, isize=info.requestQueue.size(); i> outstandingVersions() + synchronized Map> outstandingVersions() { HashMap> map = new HashMap<>(); for (VersionInfo info : versionInfo.values()) @@ -237,58 +267,38 @@ public class MigrationCoordinator } @VisibleForTesting - protected VersionInfo getVersionInfoUnsafe(UUID version) + VersionInfo getVersionInfoUnsafe(UUID version) { return versionInfo.get(version); } - @VisibleForTesting - protected int getMaxOutstandingVersionRequests() + private boolean shouldPullSchema(UUID version) { - return MAX_OUTSTANDING_VERSION_REQUESTS; - } - - @VisibleForTesting - protected boolean isAlive(InetAddressAndPort endpoint) - { - return FailureDetector.instance.isAlive(endpoint); - } - - @VisibleForTesting - protected boolean shouldPullSchema(UUID version) - { - if (Schema.instance.getVersion() == null) + UUID localSchemaVersion = schemaVersion.get(); + if (localSchemaVersion == null) { logger.debug("Not pulling schema for version {}, because local schama version is not known yet", version); return false; } - if (Schema.instance.isSameVersion(version)) + if (localSchemaVersion.equals(version)) { logger.debug("Not pulling schema for version {}, because schema versions match: " + "local={}, remote={}", version, - Schema.schemaVersionToString(Schema.instance.getVersion()), - Schema.schemaVersionToString(version)); + DistributedSchema.schemaVersionToString(localSchemaVersion), + DistributedSchema.schemaVersionToString(version)); return false; } return true; } - // Since 3.0.14 protocol contains only a CASSANDRA-13004 bugfix, it is safe to accept schema changes - // from both 3.0 and 3.0.14. - private static boolean is30Compatible(int version) - { - return version == MessagingService.current_version || version == MessagingService.VERSION_3014; - } - - @VisibleForTesting - protected boolean shouldPullFromEndpoint(InetAddressAndPort endpoint) + private boolean shouldPullFromEndpoint(InetAddressAndPort endpoint) { if (endpoint.equals(FBUtilities.getBroadcastAddressAndPort())) return false; - EndpointState state = Gossiper.instance.getEndpointStateForEndpoint(endpoint); + EndpointState state = gossiper.getEndpointStateForEndpoint(endpoint); if (state == null) return false; @@ -302,19 +312,19 @@ public class MigrationCoordinator return false; } - if (!MessagingService.instance().versions.knows(endpoint)) + if (!messagingService.versions.knows(endpoint)) { logger.debug("Not pulling schema from {} because their messaging version is unknown", endpoint); return false; } - if (MessagingService.instance().versions.getRaw(endpoint) != MessagingService.current_version) + if (messagingService.versions.getRaw(endpoint) != MessagingService.current_version) { logger.debug("Not pulling schema from {} because their schema format is incompatible", endpoint); return false; } - if (Gossiper.instance.isGossipOnlyMember(endpoint)) + if (gossiper.isGossipOnlyMember(endpoint)) { logger.debug("Not pulling schema from {} because it's a gossip only member", endpoint); return false; @@ -322,39 +332,33 @@ public class MigrationCoordinator return true; } - @VisibleForTesting - protected boolean shouldPullImmediately(InetAddressAndPort endpoint, UUID version) + private boolean shouldPullImmediately(InetAddressAndPort endpoint, UUID version) { - if (Schema.instance.isEmpty() || getUptimeFn.getAsLong() < MIGRATION_DELAY_IN_MS) + UUID localSchemaVersion = schemaVersion.get(); + if (SchemaConstants.emptyVersion.equals(localSchemaVersion) || ManagementFactory.getRuntimeMXBean().getUptime() < MIGRATION_DELAY_IN_MS) { // If we think we may be bootstrapping or have recently started, submit MigrationTask immediately logger.debug("Immediately submitting migration task for {}, " + "schema versions: local={}, remote={}", endpoint, - Schema.schemaVersionToString(Schema.instance.getVersion()), - Schema.schemaVersionToString(version)); + DistributedSchema.schemaVersionToString(localSchemaVersion), + DistributedSchema.schemaVersionToString(version)); return true; } return false; } - @VisibleForTesting - protected boolean isLocalVersion(UUID version) - { - return Schema.instance.isSameVersion(version); - } - /** * If a previous schema update brought our version the same as the incoming schema, don't apply it */ - synchronized boolean shouldApplySchemaFor(VersionInfo info) + private synchronized boolean shouldApplySchemaFor(VersionInfo info) { if (info.wasReceived()) return false; - return !isLocalVersion(info.version); + return !Objects.equals(schemaVersion.get(), info.version); } - public synchronized Future reportEndpointVersion(InetAddressAndPort endpoint, UUID version) + synchronized Future reportEndpointVersion(InetAddressAndPort endpoint, UUID version) { if (ignoredEndpoints.contains(endpoint) || IGNORED_VERSIONS.contains(version)) { @@ -368,7 +372,7 @@ public class MigrationCoordinator return FINISHED_FUTURE; VersionInfo info = versionInfo.computeIfAbsent(version, VersionInfo::new); - if (isLocalVersion(version)) + if (Objects.equals(schemaVersion.get(), version)) info.markReceived(); info.endpoints.add(endpoint); info.requestQueue.addFirst(endpoint); @@ -379,19 +383,6 @@ public class MigrationCoordinator return maybePullSchema(info); } - public Future reportEndpointVersion(InetAddressAndPort endpoint, EndpointState state) - { - if (state == null) - return FINISHED_FUTURE; - - UUID version = state.getSchemaVersion(); - - if (version == null) - return FINISHED_FUTURE; - - return reportEndpointVersion(endpoint, version); - } - private synchronized void removeEndpointFromVersion(InetAddressAndPort endpoint, UUID version) { if (version == null) @@ -410,7 +401,7 @@ public class MigrationCoordinator } } - public synchronized void removeAndIgnoreEndpoint(InetAddressAndPort endpoint) + synchronized void removeAndIgnoreEndpoint(InetAddressAndPort endpoint) { Preconditions.checkArgument(endpoint != null); ignoredEndpoints.add(endpoint); @@ -421,38 +412,75 @@ public class MigrationCoordinator } } - Future scheduleSchemaPull(InetAddressAndPort endpoint, VersionInfo info) + private Future scheduleSchemaPull(InetAddressAndPort endpoint, VersionInfo info) { - FutureTask task = new FutureTask<>(() -> pullSchema(new Callback(endpoint, info))); + FutureTask task = new FutureTask<>(() -> pullSchema(endpoint, new Callback(endpoint, info))); + if (shouldPullImmediately(endpoint, info.version)) - { submitToMigrationIfNotShutdown(task); - } else - { - ScheduledExecutors.nonPeriodicTasks.schedule(()->submitToMigrationIfNotShutdown(task), MIGRATION_DELAY_IN_MS, TimeUnit.MILLISECONDS); - } + ScheduledExecutors.nonPeriodicTasks.schedule(() -> submitToMigrationIfNotShutdown(task), MIGRATION_DELAY_IN_MS, TimeUnit.MILLISECONDS); + return task; } - private static Future submitToMigrationIfNotShutdown(Runnable task) + private Future> pullSchemaFrom(InetAddressAndPort endpoint) { - if (Stage.MIGRATION.executor().isShutdown() || Stage.MIGRATION.executor().isTerminated()) + AsyncPromise> result = new AsyncPromise<>(); + return submitToMigrationIfNotShutdown(() -> pullSchema(endpoint, new RequestCallback>() + { + @Override + public void onResponse(Message> msg) + { + result.setSuccess(msg.payload); + } + + @Override + public void onFailure(InetAddressAndPort from, RequestFailureReason failureReason) + { + result.setFailure(new RuntimeException("Failed to get schema from " + from + ". The failure reason was: " + failureReason)); + } + + @Override + public boolean invokeOnFailure() + { + return true; + } + })).flatMap(ignored -> result); + } + + Future> pullSchemaFromAnyNode() + { + Optional endpoint = gossiper.getLiveMembers() + .stream() + .filter(this::shouldPullFromEndpoint) + .findFirst(); + + return endpoint.map(this::pullSchemaFrom).orElse(ImmediateFuture.success(Collections.emptyList())); + } + + + void announce(UUID schemaVersion) + { + if (gossiper.isEnabled()) + gossiper.addLocalApplicationState(ApplicationState.SCHEMA, StorageService.instance.valueFactory.schema(schemaVersion)); + SchemaDiagnostics.versionAnnounced(Schema.instance); + } + + private Future submitToMigrationIfNotShutdown(Runnable task) + { + if (executor.isShutdown() || executor.isTerminated()) { logger.info("Skipped scheduled pulling schema from other nodes: the MIGRATION executor service has been shutdown."); - return null; + return ImmediateFuture.success(null); } else - return Stage.MIGRATION.submit(task); + { + return executor.submit(task, null); + } } - @VisibleForTesting - protected void mergeSchemaFrom(InetAddressAndPort endpoint, Collection mutations) - { - Schema.instance.mergeAndAnnounceVersion(mutations); - } - - class Callback implements RequestCallback> + private class Callback implements RequestCallback> { final InetAddressAndPort endpoint; final VersionInfo info; @@ -486,7 +514,7 @@ public class MigrationCoordinator { try { - mergeSchemaFrom(endpoint, mutations); + schemaUpdateCallback.accept(endpoint, mutations); } catch (Exception e) { @@ -504,40 +532,38 @@ public class MigrationCoordinator } } - private void pullSchema(Callback callback) + private void pullSchema(InetAddressAndPort endpoint, RequestCallback> callback) { - if (!isAlive(callback.endpoint)) + if (!gossiper.isAlive(endpoint)) { - logger.warn("Can't send schema pull request: node {} is down.", callback.endpoint); - callback.fail(); + logger.warn("Can't send schema pull request: node {} is down.", endpoint); + callback.onFailure(endpoint, RequestFailureReason.UNKNOWN); return; } // There is a chance that quite some time could have passed between now and the MM#maybeScheduleSchemaPull(), // potentially enough for the endpoint node to restart - which is an issue if it does restart upgraded, with // a higher major. - if (!shouldPullFromEndpoint(callback.endpoint)) + if (!shouldPullFromEndpoint(endpoint)) { - logger.info("Skipped sending a migration request: node {} has a higher major version now.", callback.endpoint); - callback.fail(); + logger.info("Skipped sending a migration request: node {} has a higher major version now.", endpoint); + callback.onFailure(endpoint, RequestFailureReason.UNKNOWN); return; } - logger.debug("Requesting schema from {}", callback.endpoint); - sendMigrationMessage(callback); + logger.debug("Requesting schema from {}", endpoint); + sendMigrationMessage(endpoint, callback); } - protected void sendMigrationMessage(Callback callback) + private void sendMigrationMessage(InetAddressAndPort endpoint, RequestCallback> callback) { - inflightTasks.getAndIncrement(); - Message message = Message.out(Verb.SCHEMA_PULL_REQ, NoPayload.noPayload); - logger.info("Sending schema pull request to {}", callback.endpoint); - MessagingService.instance().sendWithCallback(message, callback.endpoint, callback); + Message message = Message.out(Verb.SCHEMA_PULL_REQ, NoPayload.noPayload); + logger.info("Sending schema pull request to {}", endpoint); + messagingService.sendWithCallback(message, endpoint, callback); } private synchronized Future pullComplete(InetAddressAndPort endpoint, VersionInfo info, boolean wasSuccessful) { - inflightTasks.decrementAndGet(); if (wasSuccessful) info.markReceived(); @@ -546,25 +572,18 @@ public class MigrationCoordinator return maybePullSchema(info); } - public int getInflightTasks() - { - return inflightTasks.get(); - } - /** * Wait until we've received schema responses for all versions we're aware of * @param waitMillis * @return true if response for all schemas were received, false if we timed out waiting */ - public boolean awaitSchemaRequests(long waitMillis) + boolean awaitSchemaRequests(long waitMillis) { if (!FBUtilities.getBroadcastAddressAndPort().equals(InetAddressAndPort.getLoopbackAddress())) Gossiper.waitToSettle(); if (versionInfo.isEmpty()) - { logger.debug("Nothing in versionInfo - so no schemas to wait for"); - } List signalList = null; try @@ -593,4 +612,36 @@ public class MigrationCoordinator signalList.forEach(WaitQueue.Signal::cancel); } } + + Pair, Set> pushSchemaMutations(Collection schemaMutations) + { + logger.debug("Pushing schema mutations: {}", schemaMutations); + Set schemaDestinationEndpoints = new HashSet<>(); + Set schemaEndpointsIgnored = new HashSet<>(); + Message> message = Message.out(SCHEMA_PUSH_REQ, schemaMutations); + for (InetAddressAndPort endpoint : gossiper.getLiveMembers()) + { + if (shouldPushSchemaTo(endpoint)) + { + logger.debug("Pushing schema mutations to {}: {}", endpoint, schemaMutations); + messagingService.send(message, endpoint); + schemaDestinationEndpoints.add(endpoint); + } + else + { + schemaEndpointsIgnored.add(endpoint); + } + } + + return Pair.create(schemaDestinationEndpoints, schemaEndpointsIgnored); + } + + private boolean shouldPushSchemaTo(InetAddressAndPort endpoint) + { + // only push schema to nodes with known and equal versions + return !endpoint.equals(FBUtilities.getBroadcastAddressAndPort()) + && messagingService.versions.knows(endpoint) + && messagingService.versions.getRaw(endpoint) == MessagingService.current_version; + } + } \ No newline at end of file diff --git a/src/java/org/apache/cassandra/schema/MigrationManager.java b/src/java/org/apache/cassandra/schema/MigrationManager.java deleted file mode 100644 index 8e485e084a..0000000000 --- a/src/java/org/apache/cassandra/schema/MigrationManager.java +++ /dev/null @@ -1,368 +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.schema; - -import java.io.IOException; -import java.util.*; -import java.lang.management.ManagementFactory; -import java.util.function.LongSupplier; - -import com.google.common.annotations.VisibleForTesting; - -import org.apache.cassandra.utils.Simulate; -import org.apache.cassandra.utils.concurrent.Future; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.apache.cassandra.db.*; -import org.apache.cassandra.exceptions.AlreadyExistsException; -import org.apache.cassandra.exceptions.ConfigurationException; -import org.apache.cassandra.gms.*; -import org.apache.cassandra.io.IVersionedSerializer; -import org.apache.cassandra.io.util.DataInputPlus; -import org.apache.cassandra.io.util.DataOutputPlus; -import org.apache.cassandra.locator.InetAddressAndPort; -import org.apache.cassandra.net.Message; -import org.apache.cassandra.net.MessagingService; -import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff; -import org.apache.cassandra.utils.FBUtilities; - -import static org.apache.cassandra.concurrent.Stage.MIGRATION; -import static org.apache.cassandra.net.Verb.SCHEMA_PUSH_REQ; -import static org.apache.cassandra.utils.Simulate.With.GLOBAL_CLOCK; - -@Simulate(with = GLOBAL_CLOCK) -public class MigrationManager -{ - private static final Logger logger = LoggerFactory.getLogger(MigrationManager.class); - - public static final MigrationManager instance = new MigrationManager(); - - private static LongSupplier getUptimeFn = () -> ManagementFactory.getRuntimeMXBean().getUptime(); - - @VisibleForTesting - public static void setUptimeFn(LongSupplier supplier) - { - getUptimeFn = supplier; - } - - private static final int MIGRATION_DELAY_IN_MS = 60000; - - private static final int MIGRATION_TASK_WAIT_IN_SECONDS = Integer.parseInt(System.getProperty("cassandra.migration_task_wait_in_seconds", "1")); - - private MigrationManager() {} - - private static boolean shouldPushSchemaTo(InetAddressAndPort endpoint) - { - // only push schema to nodes with known and equal versions - return !endpoint.equals(FBUtilities.getBroadcastAddressAndPort()) - && MessagingService.instance().versions.knows(endpoint) - && MessagingService.instance().versions.getRaw(endpoint) == MessagingService.current_version; - } - - public static void announceNewKeyspace(KeyspaceMetadata ksm) throws ConfigurationException - { - announceNewKeyspace(ksm, false); - } - - public static void announceNewKeyspace(KeyspaceMetadata ksm, boolean announceLocally) throws ConfigurationException - { - announceNewKeyspace(ksm, FBUtilities.timestampMicros(), announceLocally); - } - - public static void announceNewKeyspace(KeyspaceMetadata ksm, long timestamp, boolean announceLocally) throws ConfigurationException - { - ksm.validate(); - - if (Schema.instance.getKeyspaceMetadata(ksm.name) != null) - throw new AlreadyExistsException(ksm.name); - - logger.info("Create new Keyspace: {}", ksm); - announce(SchemaKeyspace.makeCreateKeyspaceMutation(ksm, timestamp), announceLocally); - } - - public static void announceNewTable(TableMetadata cfm) - { - announceNewTable(cfm, true, FBUtilities.timestampMicros()); - } - - private static void announceNewTable(TableMetadata cfm, boolean throwOnDuplicate, long timestamp) - { - cfm.validate(); - - KeyspaceMetadata ksm = Schema.instance.getKeyspaceMetadata(cfm.keyspace); - if (ksm == null) - throw new ConfigurationException(String.format("Cannot add table '%s' to non existing keyspace '%s'.", cfm.name, cfm.keyspace)); - // If we have a table or a view which has the same name, we can't add a new one - else if (throwOnDuplicate && ksm.getTableOrViewNullable(cfm.name) != null) - throw new AlreadyExistsException(cfm.keyspace, cfm.name); - - logger.info("Create new table: {}", cfm); - announce(SchemaKeyspace.makeCreateTableMutation(ksm, cfm, timestamp), false); - } - - static void announceKeyspaceUpdate(KeyspaceMetadata ksm) - { - ksm.validate(); - - KeyspaceMetadata oldKsm = Schema.instance.getKeyspaceMetadata(ksm.name); - if (oldKsm == null) - throw new ConfigurationException(String.format("Cannot update non existing keyspace '%s'.", ksm.name)); - - logger.info("Update Keyspace '{}' From {} To {}", ksm.name, oldKsm, ksm); - announce(SchemaKeyspace.makeCreateKeyspaceMutation(ksm.name, ksm.params, FBUtilities.timestampMicros()), false); - } - - public static void announceTableUpdate(TableMetadata tm) - { - announceTableUpdate(tm, false); - } - - public static void announceTableUpdate(TableMetadata updated, boolean announceLocally) - { - updated.validate(); - - TableMetadata current = Schema.instance.getTableMetadata(updated.keyspace, updated.name); - if (current == null) - throw new ConfigurationException(String.format("Cannot update non existing table '%s' in keyspace '%s'.", updated.name, updated.keyspace)); - KeyspaceMetadata ksm = Schema.instance.getKeyspaceMetadata(current.keyspace); - - updated.validateCompatibility(current); - - long timestamp = FBUtilities.timestampMicros(); - - logger.info("Update table '{}/{}' From {} To {}", current.keyspace, current.name, current, updated); - Mutation.SimpleBuilder builder = SchemaKeyspace.makeUpdateTableMutation(ksm, current, updated, timestamp); - - announce(builder, announceLocally); - } - - static void announceKeyspaceDrop(String ksName) - { - KeyspaceMetadata oldKsm = Schema.instance.getKeyspaceMetadata(ksName); - if (oldKsm == null) - throw new ConfigurationException(String.format("Cannot drop non existing keyspace '%s'.", ksName)); - - logger.info("Drop Keyspace '{}'", oldKsm.name); - announce(SchemaKeyspace.makeDropKeyspaceMutation(oldKsm, FBUtilities.timestampMicros()), false); - } - - public static void announceTableDrop(String ksName, String cfName, boolean announceLocally) - { - TableMetadata tm = Schema.instance.getTableMetadata(ksName, cfName); - if (tm == null) - throw new ConfigurationException(String.format("Cannot drop non existing table '%s' in keyspace '%s'.", cfName, ksName)); - KeyspaceMetadata ksm = Schema.instance.getKeyspaceMetadata(ksName); - - logger.info("Drop table '{}/{}'", tm.keyspace, tm.name); - announce(SchemaKeyspace.makeDropTableMutation(ksm, tm, FBUtilities.timestampMicros()), announceLocally); - } - - /** - * actively announce a new version to active hosts via rpc - * @param schema The schema mutation to be applied - */ - private static void announce(Mutation.SimpleBuilder schema, boolean announceLocally) - { - List mutations = Collections.singletonList(schema.build()); - - if (announceLocally) - Schema.instance.merge(mutations); - else - announce(mutations); - } - - public static void announce(Mutation change) - { - announce(Collections.singleton(change)); - } - - public static void announce(Collection schema) - { - Future f = announceWithoutPush(schema); - - Set schemaDestinationEndpoints = new HashSet<>(); - Set schemaEndpointsIgnored = new HashSet<>(); - Message> message = Message.out(SCHEMA_PUSH_REQ, schema); - for (InetAddressAndPort endpoint : Gossiper.instance.getLiveMembers()) - { - if (shouldPushSchemaTo(endpoint)) - { - MessagingService.instance().send(message, endpoint); - schemaDestinationEndpoints.add(endpoint); - } - else - { - schemaEndpointsIgnored.add(endpoint); - } - } - - SchemaAnnouncementDiagnostics.schemaMutationsAnnounced(schemaDestinationEndpoints, schemaEndpointsIgnored); - FBUtilities.waitOnFuture(f); - } - - public static Future announceWithoutPush(Collection schema) - { - return MIGRATION.submit(() -> Schema.instance.mergeAndAnnounceVersion(schema)); - } - - public static KeyspacesDiff announce(SchemaTransformation transformation, boolean locally) - { - long now = FBUtilities.timestampMicros(); - - Future future = - MIGRATION.submit(() -> Schema.instance.transform(transformation, locally, now)); - - Schema.TransformationResult result = future.syncUninterruptibly().getNow(); - if (!result.success) - throw result.exception; - - if (locally || result.diff.isEmpty()) - return result.diff; - - Set schemaDestinationEndpoints = new HashSet<>(); - Set schemaEndpointsIgnored = new HashSet<>(); - Message> message = Message.out(SCHEMA_PUSH_REQ, result.mutations); - for (InetAddressAndPort endpoint : Gossiper.instance.getLiveMembers()) - { - if (shouldPushSchemaTo(endpoint)) - { - MessagingService.instance().send(message, endpoint); - schemaDestinationEndpoints.add(endpoint); - } - else - { - schemaEndpointsIgnored.add(endpoint); - } - } - - SchemaAnnouncementDiagnostics.schemaTransformationAnnounced(schemaDestinationEndpoints, schemaEndpointsIgnored, - transformation); - - return result.diff; - } - - /** - * Clear all locally stored schema information and reset schema to initial state. - * Called by user (via JMX) who wants to get rid of schema disagreement. - */ - public static void resetLocalSchema() - { - logger.info("Starting local schema reset..."); - - logger.debug("Truncating schema tables..."); - - SchemaMigrationDiagnostics.resetLocalSchema(); - - Schema.instance.truncateSchemaKeyspace(); - - logger.debug("Clearing local schema keyspace definitions..."); - - Schema.instance.clear(); - - Set liveEndpoints = Gossiper.instance.getLiveMembers(); - liveEndpoints.remove(FBUtilities.getBroadcastAddressAndPort()); - - // force migration if there are nodes around - for (InetAddressAndPort node : liveEndpoints) - { - EndpointState state = Gossiper.instance.getEndpointStateForEndpoint(node); - Future pull = MigrationCoordinator.instance.reportEndpointVersion(node, state); - if (pull != null) - FBUtilities.waitOnFuture(pull); - } - - logger.info("Local schema reset is complete."); - } - - /** - * We have a set of non-local, distributed system keyspaces, e.g. system_traces, system_auth, etc. - * (see {@link SchemaConstants#REPLICATED_SYSTEM_KEYSPACE_NAMES}), that need to be created on cluster initialisation, - * and later evolved on major upgrades (sometimes minor too). This method compares the current known definitions - * of the tables (if the keyspace exists) to the expected, most modern ones expected by the running version of C*; - * if any changes have been detected, a schema Mutation will be created which, when applied, should make - * cluster's view of that keyspace aligned with the expected modern definition. - * - * @param keyspace the expected modern definition of the keyspace - * @param generation timestamp to use for the table changes in the schema mutation - * - * @return empty Optional if the current definition is up to date, or an Optional with the Mutation that would - * bring the schema in line with the expected definition. - */ - public static Optional evolveSystemKeyspace(KeyspaceMetadata keyspace, long generation) - { - Mutation.SimpleBuilder builder = null; - - KeyspaceMetadata definedKeyspace = Schema.instance.getKeyspaceMetadata(keyspace.name); - Tables definedTables = null == definedKeyspace ? Tables.none() : definedKeyspace.tables; - - for (TableMetadata table : keyspace.tables) - { - if (table.equals(definedTables.getNullable(table.name))) - continue; - - if (null == builder) - { - // for the keyspace definition itself (name, replication, durability) always use generation 0; - // this ensures that any changes made to replication by the user will never be overwritten. - builder = SchemaKeyspace.makeCreateKeyspaceMutation(keyspace.name, keyspace.params, 0); - - // now set the timestamp to generation, so the tables have the expected timestamp - builder.timestamp(generation); - } - - // for table definitions always use the provided generation; these tables, unlike their containing - // keyspaces, are *NOT* meant to be altered by the user; if their definitions need to change, - // the schema must be updated in code, and the appropriate generation must be bumped. - SchemaKeyspace.addTableToSchemaMutation(table, true, builder); - } - - return builder == null ? Optional.empty() : Optional.of(builder.build()); - } - - public static class MigrationsSerializer implements IVersionedSerializer> - { - public static MigrationsSerializer instance = new MigrationsSerializer(); - - public void serialize(Collection schema, DataOutputPlus out, int version) throws IOException - { - out.writeInt(schema.size()); - for (Mutation mutation : schema) - Mutation.serializer.serialize(mutation, out, version); - } - - public Collection deserialize(DataInputPlus in, int version) throws IOException - { - int count = in.readInt(); - Collection schema = new ArrayList<>(count); - - for (int i = 0; i < count; i++) - schema.add(Mutation.serializer.deserialize(in, version)); - - return schema; - } - - public long serializedSize(Collection schema, int version) - { - int size = TypeSizes.sizeof(schema.size()); - for (Mutation mutation : schema) - size += mutation.serializedSize(version); - return size; - } - } -} diff --git a/src/java/org/apache/cassandra/schema/OfflineSchemaUpdateHandler.java b/src/java/org/apache/cassandra/schema/OfflineSchemaUpdateHandler.java new file mode 100644 index 0000000000..9d1020883b --- /dev/null +++ b/src/java/org/apache/cassandra/schema/OfflineSchemaUpdateHandler.java @@ -0,0 +1,93 @@ +/* + * 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 java.time.Duration; +import java.util.UUID; +import java.util.function.BiConsumer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.cassandra.schema.SchemaTransformation.SchemaTransformationResult; +import org.apache.cassandra.utils.ByteArrayUtil; + +/** + * Update handler which works only in memory. It does not load or save the schema anywhere. It is used in client mode + * applications. + */ +public class OfflineSchemaUpdateHandler implements SchemaUpdateHandler +{ + private static final Logger logger = LoggerFactory.getLogger(OfflineSchemaUpdateHandler.class); + + private final BiConsumer updateCallback; + + private volatile DistributedSchema schema = DistributedSchema.EMPTY; + + public OfflineSchemaUpdateHandler(BiConsumer updateCallback) + { + this.updateCallback = updateCallback; + } + + @Override + public void start() + { + // no-op + } + + @Override + public boolean waitUntilReady(Duration timeout) + { + return true; + } + + @Override + public synchronized SchemaTransformationResult apply(SchemaTransformation transformation, boolean local) + { + DistributedSchema before = schema; + Keyspaces afterKeyspaces = transformation.apply(before.getKeyspaces()); + Keyspaces.KeyspacesDiff diff = Keyspaces.diff(before.getKeyspaces(), afterKeyspaces); + + if (diff.isEmpty()) + return new SchemaTransformationResult(before, before, diff); + + DistributedSchema after = new DistributedSchema(afterKeyspaces, UUID.nameUUIDFromBytes(ByteArrayUtil.bytes(afterKeyspaces.hashCode()))); + SchemaTransformationResult update = new SchemaTransformationResult(before, after, diff); + this.schema = after; + logger.debug("Schema updated: {}", update); + updateCallback.accept(update, true); + + return update; + } + + @Override + public SchemaTransformationResult reset(boolean local) + { + if (!local) + throw new UnsupportedOperationException(); + + return apply(ignored -> SchemaKeyspace.fetchNonSystemKeyspaces(), local); + } + + @Override + public synchronized void clear() + { + this.schema = DistributedSchema.EMPTY; + } +} diff --git a/src/java/org/apache/cassandra/schema/Schema.java b/src/java/org/apache/cassandra/schema/Schema.java index 9c0e6c7665..5f21dbbb63 100644 --- a/src/java/org/apache/cassandra/schema/Schema.java +++ b/src/java/org/apache/cassandra/schema/Schema.java @@ -17,124 +17,128 @@ */ package org.apache.cassandra.schema; -import java.net.UnknownHostException; +import java.time.Duration; import java.util.*; -import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Supplier; -import java.util.stream.Collectors; -import com.google.common.collect.ImmutableList; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.MapDifference; -import com.google.common.collect.Sets; +import org.apache.commons.lang3.ObjectUtils; -import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.functions.*; import org.apache.cassandra.db.*; import org.apache.cassandra.db.marshal.AbstractType; -import org.apache.cassandra.db.marshal.UserType; import org.apache.cassandra.db.virtual.VirtualKeyspaceRegistry; import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.exceptions.InvalidRequestException; -import org.apache.cassandra.gms.ApplicationState; -import org.apache.cassandra.gms.Gossiper; import org.apache.cassandra.io.sstable.Descriptor; +import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.locator.LocalStrategy; import org.apache.cassandra.schema.KeyspaceMetadata.KeyspaceDiff; import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff; +import org.apache.cassandra.schema.SchemaTransformation.SchemaTransformationResult; import org.apache.cassandra.service.PendingRangeCalculatorService; -import org.apache.cassandra.service.StorageService; -import org.apache.cassandra.utils.Pair; import org.apache.cassandra.utils.concurrent.LoadingMap; -import org.cliffc.high_scale_lib.NonBlockingHashMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import static java.lang.String.format; import static com.google.common.collect.Iterables.size; +import static org.apache.cassandra.config.DatabaseDescriptor.isDaemonInitialized; +import static org.apache.cassandra.config.DatabaseDescriptor.isToolInitialized; +/** + * Manages shared schema, keyspace instances and table metadata refs. Provides methods to initialize, modify and query + * both the shared and local schema, as well as to register listeners. + *

+ * This class should be the only entity used to query and manage schema. Internal details should not be access in + * production code (would be great if they were not accessed in the test code as well). + *

+ * TL;DR: All modifications are made using the implementation of {@link SchemaUpdateHandler} obtained from the provided + * factory. After each modification, the internally managed table metadata refs and keyspaces instances are updated and + * notifications are sent to the registered listeners. + * When the schema change is applied by the update handler (regardless it is initiated locally or received from outside), + * the registered callback is executed which performs the remaining updates for tables metadata refs and keyspace + * instances (see {@link #mergeAndUpdateVersion(SchemaTransformationResult, boolean)}). + */ public class Schema implements SchemaProvider { + private static final Logger logger = LoggerFactory.getLogger(Schema.class); + + public static final String FORCE_LOAD_LOCAL_KEYSPACES_PROP = "cassandra.schema.force_load_local_keyspaces"; + private static final boolean FORCE_LOAD_LOCAL_KEYSPACES = Boolean.getBoolean(FORCE_LOAD_LOCAL_KEYSPACES_PROP); + public static final Schema instance = new Schema(); - private volatile Keyspaces keyspaces = Keyspaces.none(); + private volatile Keyspaces distributedKeyspaces = Keyspaces.none(); - // UUID -> mutable metadata ref map. We have to update these in place every time a table changes. - private final Map metadataRefs = new NonBlockingHashMap<>(); + private final Keyspaces localKeyspaces; - // (keyspace name, index name) -> mutable metadata ref map. We have to update these in place every time an index changes. - private final Map, TableMetadataRef> indexMetadataRefs = new NonBlockingHashMap<>(); + private volatile TableMetadataRefCache tableMetadataRefCache = TableMetadataRefCache.EMPTY; // Keyspace objects, one per keyspace. Only one instance should ever exist for any given keyspace. + // We operate on loading map because we need to achieve atomic initialization with at-most-once semantics for + // loadFunction. Although it seems that this is a valid case for using ConcurrentHashMap.computeIfAbsent, + // we should not use it because we have no knowledge about the loadFunction and in fact that load function may + // do some nested calls to maybeAddKeyspaceInstance, also using different threads, and in a blocking manner. + // This may lead to a deadlock. The documentation of ConcurrentHashMap says that manipulating other keys inside + // the lambda passed to the computeIfAbsent method is prohibited. private final LoadingMap keyspaceInstances = new LoadingMap<>(); - private volatile UUID version; + private volatile UUID version = SchemaConstants.emptyVersion; - private final List changeListeners = new CopyOnWriteArrayList<>(); + private final SchemaChangeNotifier schemaChangeNotifier = new SchemaChangeNotifier(); + + public final SchemaUpdateHandler updateHandler; + + private final boolean online; /** * Initialize empty schema object and load the hardcoded system tables */ private Schema() { - if (DatabaseDescriptor.isDaemonInitialized() || DatabaseDescriptor.isToolInitialized()) - { - load(SchemaKeyspace.metadata()); - load(SystemKeyspace.metadata()); - } + this.online = isDaemonInitialized(); + this.localKeyspaces = (FORCE_LOAD_LOCAL_KEYSPACES || isDaemonInitialized() || isToolInitialized()) + ? Keyspaces.of(SchemaKeyspace.metadata(), SystemKeyspace.metadata()) + : Keyspaces.none(); + + this.localKeyspaces.forEach(this::loadNew); + this.updateHandler = SchemaUpdateHandlerFactoryProvider.instance.get().getSchemaUpdateHandler(online, this::mergeAndUpdateVersion); + } + + @VisibleForTesting + public Schema(boolean online, Keyspaces localKeyspaces, SchemaUpdateHandler updateHandler) + { + this.online = online; + this.localKeyspaces = localKeyspaces; + this.updateHandler = updateHandler; + } + + public void startSync() + { + logger.debug("Starting update handler"); + updateHandler.start(); + } + + public boolean waitUntilReady(Duration timeout) + { + logger.debug("Waiting for update handler to be ready..."); + return updateHandler.waitUntilReady(timeout); } /** - * Add entries to system_schema.* for the hardcoded system keyspaces - * - * See CASSANDRA-16856/16996. Make sure schema pulls are synchronized to prevent concurrent schema pull/writes - */ - public synchronized void saveSystemKeyspace() - { - SchemaKeyspace.saveSystemKeyspacesSchema(); - } - - /** - * See CASSANDRA-16856/16996. Make sure schema pulls are synchronized to prevent concurrent schema pull/writes - */ - public synchronized void truncateSchemaKeyspace() - { - SchemaKeyspace.truncate(); - } - - /** - * See CASSANDRA-16856/16996. Make sure schema pulls are synchronized to prevent concurrent schema pull/writes - */ - public synchronized Collection schemaKeyspaceAsMutations() - { - return SchemaKeyspace.convertSchemaToMutations(); - } - - public static KeyspaceMetadata getSystemKeyspaceMetadata() - { - return SchemaKeyspace.metadata(); - } - - /** - * load keyspace (keyspace) definitions, but do not initialize the keyspace instances. - * Schema version may be updated as the result. + * Load keyspaces definitions from local storage, see {@link SchemaUpdateHandler#reset(boolean)}. */ public void loadFromDisk() { - loadFromDisk(true); - } - - /** - * Load schema definitions from disk. - * - * @param updateVersion true if schema version needs to be updated - */ - public void loadFromDisk(boolean updateVersion) - { - SchemaDiagnostics.schemataLoading(this); - SchemaKeyspace.fetchNonSystemKeyspaces().forEach(this::load); - if (updateVersion) - updateVersion(); - SchemaDiagnostics.schemataLoaded(this); + SchemaDiagnostics.schemaLoading(this); + updateHandler.reset(true); + SchemaDiagnostics.schemaLoaded(this); } /** @@ -142,31 +146,27 @@ public class Schema implements SchemaProvider * * @param ksm The metadata about keyspace */ - synchronized public void load(KeyspaceMetadata ksm) + private synchronized void load(KeyspaceMetadata ksm) { - KeyspaceMetadata previous = keyspaces.getNullable(ksm.name); + Preconditions.checkArgument(!SchemaConstants.isLocalSystemKeyspace(ksm.name)); + KeyspaceMetadata previous = distributedKeyspaces.getNullable(ksm.name); if (previous == null) loadNew(ksm); else reload(previous, ksm); - keyspaces = keyspaces.withAddedOrUpdated(ksm); + distributedKeyspaces = distributedKeyspaces.withAddedOrUpdated(ksm); } - private void loadNew(KeyspaceMetadata ksm) + private synchronized void loadNew(KeyspaceMetadata ksm) { - ksm.tablesAndViews() - .forEach(metadata -> metadataRefs.put(metadata.id, new TableMetadataRef(metadata))); - - ksm.tables - .indexTables() - .forEach((name, metadata) -> indexMetadataRefs.put(Pair.create(ksm.name, name), new TableMetadataRef(metadata))); + this.tableMetadataRefCache = tableMetadataRefCache.withNewRefs(ksm); SchemaDiagnostics.metadataInitialized(this, ksm); } - private void reload(KeyspaceMetadata previous, KeyspaceMetadata updated) + private synchronized void reload(KeyspaceMetadata previous, KeyspaceMetadata updated) { Keyspace keyspace = getKeyspaceInstance(updated.name); if (null != keyspace) @@ -177,44 +177,20 @@ public class Schema implements SchemaProvider MapDifference indexesDiff = previous.tables.indexesDiff(updated.tables); - // clean up after removed entries - tablesDiff.dropped.forEach(table -> metadataRefs.remove(table.id)); - viewsDiff.dropped.forEach(view -> metadataRefs.remove(view.metadata.id)); - - indexesDiff.entriesOnlyOnLeft() - .values() - .forEach(indexTable -> indexMetadataRefs.remove(Pair.create(indexTable.keyspace, indexTable.indexName().get()))); - - // load up new entries - tablesDiff.created.forEach(table -> metadataRefs.put(table.id, new TableMetadataRef(table))); - viewsDiff.created.forEach(view -> metadataRefs.put(view.metadata.id, new TableMetadataRef(view.metadata))); - - indexesDiff.entriesOnlyOnRight() - .values() - .forEach(indexTable -> indexMetadataRefs.put(Pair.create(indexTable.keyspace, indexTable.indexName().get()), new TableMetadataRef(indexTable))); - - // refresh refs to updated ones - tablesDiff.altered.forEach(diff -> metadataRefs.get(diff.after.id).set(diff.after)); - viewsDiff.altered.forEach(diff -> metadataRefs.get(diff.after.metadata.id).set(diff.after.metadata)); - - indexesDiff.entriesDiffering() - .values() - .stream() - .map(MapDifference.ValueDifference::rightValue) - .forEach(indexTable -> indexMetadataRefs.get(Pair.create(indexTable.keyspace, indexTable.indexName().get())).set(indexTable)); + this.tableMetadataRefCache = tableMetadataRefCache.withUpdatedRefs(previous, updated); SchemaDiagnostics.metadataReloaded(this, previous, updated, tablesDiff, viewsDiff, indexesDiff); } public void registerListener(SchemaChangeListener listener) { - changeListeners.add(listener); + schemaChangeNotifier.registerListener(listener); } @SuppressWarnings("unused") public void unregisterListener(SchemaChangeListener listener) { - changeListeners.remove(listener); + schemaChangeNotifier.unregisterListener(listener); } /** @@ -241,8 +217,8 @@ public class Schema implements SchemaProvider return null; return instance.hasColumnFamilyStore(metadata.id) - ? instance.getColumnFamilyStore(metadata.id) - : null; + ? instance.getColumnFamilyStore(metadata.id) + : null; } @Override @@ -251,11 +227,11 @@ public class Schema implements SchemaProvider return keyspaceInstances.blockingLoadIfAbsent(keyspaceName, loadFunction); } - public Keyspace maybeRemoveKeyspaceInstance(String keyspaceName) + public Keyspace maybeRemoveKeyspaceInstance(String keyspaceName, boolean dropData) { try { - return keyspaceInstances.blockingUnloadIfPresent(keyspaceName, Keyspace::unload); + return keyspaceInstances.blockingUnloadIfPresent(keyspaceName, keyspace -> keyspace.unload(dropData)); } catch (LoadingMap.UnloadExecutionException e) { @@ -263,9 +239,23 @@ public class Schema implements SchemaProvider } } + /** + * @deprecated use {@link #distributedAndLocalKeyspaces()} + */ + @Deprecated public Keyspaces snapshot() { - return keyspaces; + return distributedAndLocalKeyspaces(); + } + + public Keyspaces distributedAndLocalKeyspaces() + { + return Keyspaces.builder().add(localKeyspaces).add(distributedKeyspaces).build(); + } + + public Keyspaces distributedKeyspaces() + { + return distributedKeyspaces; } /** @@ -274,12 +264,11 @@ public class Schema implements SchemaProvider */ public int largestGcgs() { - int gcgs = Integer.MIN_VALUE; - for (TableMetadataRef tableMetadataRef : metadataRefs.values()) - { - gcgs = Math.max(gcgs, tableMetadataRef.get().params.gcGraceSeconds); - } - return gcgs; + return distributedAndLocalKeyspaces().stream() + .flatMap(ksm -> ksm.tables.stream()) + .mapToInt(tm -> tm.params.gcGraceSeconds) + .max() + .orElse(Integer.MIN_VALUE); } /** @@ -287,30 +276,24 @@ public class Schema implements SchemaProvider * * @param ksm The keyspace definition to remove */ - synchronized void unload(KeyspaceMetadata ksm) + private synchronized void unload(KeyspaceMetadata ksm) { - keyspaces = keyspaces.without(ksm.name); + distributedKeyspaces = distributedKeyspaces.without(ksm.name); - ksm.tablesAndViews() - .forEach(t -> metadataRefs.remove(t.id)); - - ksm.tables - .indexTables() - .keySet() - .forEach(name -> indexMetadataRefs.remove(Pair.create(ksm.name, name))); + this.tableMetadataRefCache = tableMetadataRefCache.withRemovedRefs(ksm); SchemaDiagnostics.metadataRemoved(this, ksm); } public int getNumberOfTables() { - return keyspaces.stream().mapToInt(k -> size(k.tablesAndViews())).sum(); + return distributedAndLocalKeyspaces().stream().mapToInt(k -> size(k.tablesAndViews())).sum(); } public ViewMetadata getView(String keyspaceName, String viewName) { assert keyspaceName != null; - KeyspaceMetadata ksm = keyspaces.getNullable(keyspaceName); + KeyspaceMetadata ksm = distributedAndLocalKeyspaces().getNullable(keyspaceName); return (ksm == null) ? null : ksm.views.getNullable(viewName); } @@ -325,65 +308,64 @@ public class Schema implements SchemaProvider public KeyspaceMetadata getKeyspaceMetadata(String keyspaceName) { assert keyspaceName != null; - KeyspaceMetadata keyspace = keyspaces.getNullable(keyspaceName); + KeyspaceMetadata keyspace = distributedAndLocalKeyspaces().getNullable(keyspaceName); return null != keyspace ? keyspace : VirtualKeyspaceRegistry.instance.getKeyspaceMetadataNullable(keyspaceName); } - private Set getNonSystemKeyspacesSet() + /** + * Returns all non-local keyspaces, that is, all but {@link SchemaConstants#LOCAL_SYSTEM_KEYSPACE_NAMES} + * or virtual keyspaces. + * @deprecated use {@link #distributedKeyspaces()} + */ + @Deprecated + public Keyspaces getNonSystemKeyspaces() { - return Sets.difference(keyspaces.names(), SchemaConstants.LOCAL_SYSTEM_KEYSPACE_NAMES); + return distributedKeyspaces; } /** - * @return collection of the non-system keyspaces (note that this count as system only the - * non replicated keyspaces, so keyspace like system_traces which are replicated are actually - * returned. See getUserKeyspace() below if you don't want those) + * Returns all non-local keyspaces whose replication strategy is not {@link LocalStrategy}. */ - public ImmutableList getNonSystemKeyspaces() + public Keyspaces getNonLocalStrategyKeyspaces() { - return ImmutableList.copyOf(getNonSystemKeyspacesSet()); + return distributedKeyspaces.filter(keyspace -> keyspace.params.replication.klass != LocalStrategy.class); } /** - * @return a collection of keyspaces that do not use LocalStrategy for replication + * Returns user keyspaces, that is all but {@link SchemaConstants#LOCAL_SYSTEM_KEYSPACE_NAMES}, + * {@link SchemaConstants#REPLICATED_SYSTEM_KEYSPACE_NAMES} or virtual keyspaces. */ - public List getNonLocalStrategyKeyspaces() + public Keyspaces getUserKeyspaces() { - return keyspaces.stream() - .filter(keyspace -> keyspace.params.replication.klass != LocalStrategy.class) - .map(keyspace -> keyspace.name) - .collect(Collectors.toList()); - } - - /** - * @return collection of the user defined keyspaces - */ - public List getUserKeyspaces() - { - return ImmutableList.copyOf(Sets.difference(getNonSystemKeyspacesSet(), SchemaConstants.REPLICATED_SYSTEM_KEYSPACE_NAMES)); + return distributedKeyspaces.without(SchemaConstants.REPLICATED_SYSTEM_KEYSPACE_NAMES); } /** * Get metadata about keyspace inner ColumnFamilies * * @param keyspaceName The name of the keyspace - * * @return metadata about ColumnFamilies the belong to the given keyspace */ public Iterable getTablesAndViews(String keyspaceName) { - assert keyspaceName != null; - KeyspaceMetadata ksm = keyspaces.getNullable(keyspaceName); - assert ksm != null; + Preconditions.checkNotNull(keyspaceName); + KeyspaceMetadata ksm = ObjectUtils.getFirstNonNull(() -> distributedKeyspaces.getNullable(keyspaceName), + () -> localKeyspaces.getNullable(keyspaceName)); + Preconditions.checkNotNull(ksm, "Keyspace %s not found", keyspaceName); return ksm.tablesAndViews(); } /** * @return collection of the all keyspace names registered in the system (system and non-system) */ - public Set getKeyspaces() + public ImmutableSet getKeyspaces() { - return keyspaces.names(); + return distributedAndLocalKeyspaces().names(); + } + + public Keyspaces getLocalKeyspaces() + { + return localKeyspaces; } /* TableMetadata/Ref query/control methods */ @@ -398,33 +380,24 @@ public class Schema implements SchemaProvider @Override public TableMetadataRef getTableMetadataRef(String keyspace, String table) { - TableMetadata tm = getTableMetadata(keyspace, table); - return tm == null - ? null - : metadataRefs.get(tm.id); + return tableMetadataRefCache.getTableMetadataRef(keyspace, table); } public TableMetadataRef getIndexTableMetadataRef(String keyspace, String index) { - return indexMetadataRefs.get(Pair.create(keyspace, index)); - } - - Map, TableMetadataRef> getIndexTableMetadataRefs() - { - return indexMetadataRefs; + return tableMetadataRefCache.getIndexTableMetadataRef(keyspace, index); } /** * Get Table metadata by its identifier * * @param id table or view identifier - * * @return metadata about Table or View */ @Override public TableMetadataRef getTableMetadataRef(TableId id) { - return metadataRefs.get(id); + return tableMetadataRefCache.getTableMetadataRef(id); } @Override @@ -433,19 +406,13 @@ public class Schema implements SchemaProvider return getTableMetadataRef(descriptor.ksname, descriptor.cfname); } - Map getTableMetadataRefs() - { - return metadataRefs; - } - /** * Given a keyspace name and table name, get the table * meta data. If the keyspace name or table name is not valid * this function returns null. * * @param keyspace The keyspace name - * @param table The table name - * + * @param table The table name * @return TableMetadata object or null if it wasn't found */ public TableMetadata getTableMetadata(String keyspace, String table) @@ -455,15 +422,16 @@ public class Schema implements SchemaProvider KeyspaceMetadata ksm = getKeyspaceMetadata(keyspace); return ksm == null - ? null - : ksm.getTableOrViewNullable(table); + ? null + : ksm.getTableOrViewNullable(table); } @Override public TableMetadata getTableMetadata(TableId id) { - TableMetadata table = keyspaces.getTableOrViewNullable(id); - return null != table ? table : VirtualKeyspaceRegistry.instance.getTableMetadataNullable(id); + return ObjectUtils.getFirstNonNull(() -> distributedKeyspaces.getTableOrViewNullable(id), + () -> localKeyspaces.getTableOrViewNullable(id), + () -> VirtualKeyspaceRegistry.instance.getTableMetadataNullable(id)); } public TableMetadata validateTable(String keyspaceName, String tableName) @@ -503,14 +471,14 @@ public class Schema implements SchemaProvider KeyspaceMetadata ksm = getKeyspaceMetadata(name.keyspace); return ksm == null - ? Collections.emptyList() - : ksm.functions.get(name); + ? Collections.emptyList() + : ksm.functions.get(name); } /** * Find the function with the specified name * - * @param name fully qualified function name + * @param name fully qualified function name * @param argTypes function argument types * @return an empty {@link Optional} if the keyspace or the function name are not found; * a non-empty optional of {@link Function} otherwise @@ -522,8 +490,8 @@ public class Schema implements SchemaProvider KeyspaceMetadata ksm = getKeyspaceMetadata(name.keyspace); return ksm == null - ? Optional.empty() - : ksm.functions.find(name, argTypes); + ? Optional.empty() + : ksm.functions.find(name, argTypes); } /* Version control */ @@ -555,198 +523,156 @@ public class Schema implements SchemaProvider /** * Read schema from system keyspace and calculate MD5 digest of every row, resulting digest * will be converted into UUID which would act as content-based version of the schema. - * + * * See CASSANDRA-16856/16996. Make sure schema pulls are synchronized to prevent concurrent schema pull/writes */ - public synchronized void updateVersion() + private synchronized void updateVersion(UUID version) { - version = SchemaKeyspace.calculateSchemaDigest(); - SystemKeyspace.updateSchemaVersion(version); + this.version = version; SchemaDiagnostics.versionUpdated(this); } - /* - * Like updateVersion, but also announces via gossip - */ - public void updateVersionAndAnnounce() - { - updateVersion(); - passiveAnnounceVersion(); - } - - /** - * Announce my version passively over gossip. - * Used to notify nodes as they arrive in the cluster. - */ - private void passiveAnnounceVersion() - { - Gossiper.instance.addLocalApplicationState(ApplicationState.SCHEMA, StorageService.instance.valueFactory.schema(version)); - SchemaDiagnostics.versionAnnounced(this); - } - /** * Clear all KS/CF metadata and reset version. */ public synchronized void clear() { - getNonSystemKeyspaces().forEach(k -> unload(getKeyspaceMetadata(k))); - updateVersionAndAnnounce(); - SchemaDiagnostics.schemataCleared(this); + distributedKeyspaces.forEach(this::unload); + updateVersion(SchemaConstants.emptyVersion); + SchemaDiagnostics.schemaCleared(this); + } + + /** + * When we receive {@link SchemaTransformationResult} in a callback invocation, the transformation result includes + * pre-transformation and post-transformation schema metadata and versions, and a diff between them. Basically + * we expect that the local image of the schema metadata ({@link #distributedKeyspaces}) and version ({@link #version}) + * are the same as pre-transformation. However, it might not always be true because some changes might not be + * applied completely due to some errors. This methods is to emit warning in such case and recalculate diff so that + * it contains the changes between the local schema image ({@link #distributedKeyspaces} and the post-transformation + * schema. That recalculation allows the following updates in the callback to recover the schema. + * + * @param result the incoming transformation result + * @return recalculated transformation result if needed, otherwise the provided incoming result + */ + private synchronized SchemaTransformationResult localDiff(SchemaTransformationResult result) + { + Keyspaces localBefore = distributedKeyspaces; + UUID localVersion = version; + boolean needNewDiff = false; + + if (!Objects.equals(localBefore, result.before.getKeyspaces())) + { + logger.info("Schema was different to what we expected: {}", Keyspaces.diff(result.before.getKeyspaces(), localBefore)); + needNewDiff = true; + } + + if (!Objects.equals(localVersion, result.before.getVersion())) + { + logger.info("Schema version was different to what we expected: {} != {}", result.before.getVersion(), localVersion); + needNewDiff = true; + } + + if (needNewDiff) + return new SchemaTransformationResult(new DistributedSchema(localBefore, localVersion), + result.after, + Keyspaces.diff(localBefore, result.after.getKeyspaces())); + + return result; } /* * Reload schema from local disk. Useful if a user made changes to schema tables by hand, or has suspicion that * in-memory representation got out of sync somehow with what's on disk. */ - public synchronized void reloadSchemaAndAnnounceVersion() + public void reloadSchemaAndAnnounceVersion() { - Keyspaces before = keyspaces.filter(k -> !SchemaConstants.isLocalSystemKeyspace(k.name)); - Keyspaces after = SchemaKeyspace.fetchNonSystemKeyspaces(); - merge(Keyspaces.diff(before, after)); - updateVersionAndAnnounce(); + updateHandler.reset(true); } /** * Merge remote schema in form of mutations with local and mutate ks/cf metadata objects * (which also involves fs operations on add/drop ks/cf) * - * @param mutations the schema changes to apply - * * @throws ConfigurationException If one of metadata attributes has invalid value */ - public synchronized void mergeAndAnnounceVersion(Collection mutations) + @VisibleForTesting + public synchronized void mergeAndUpdateVersion(SchemaTransformationResult result, boolean dropData) { - merge(mutations); - updateVersionAndAnnounce(); + if (online) + SystemKeyspace.updateSchemaVersion(result.after.getVersion()); + result = localDiff(result); + schemaChangeNotifier.notifyPreChanges(result); + merge(result.diff, dropData); + updateVersion(result.after.getVersion()); + } + + public SchemaTransformationResult transform(SchemaTransformation transformation) + { + return transform(transformation, false); + } + + public SchemaTransformationResult transform(SchemaTransformation transformation, boolean local) + { + return updateHandler.apply(transformation, local); } /** - * See CASSANDRA-16856/16996. Make sure schema pulls are synchronized to prevent concurrent schema pull/writes + * Clear all locally stored schema information and reset schema to initial state. + * Called by user (via JMX) who wants to get rid of schema disagreement. */ - public synchronized TransformationResult transform(SchemaTransformation transformation, boolean locally, long now) throws UnknownHostException + public void resetLocalSchema() { - KeyspacesDiff diff; - try - { - Keyspaces before = keyspaces; - Keyspaces after = transformation.apply(before); - diff = Keyspaces.diff(before, after); - } - catch (RuntimeException e) - { - return new TransformationResult(e); - } + logger.debug("Clearing local schema..."); + updateHandler.clear(); - if (diff.isEmpty()) - return new TransformationResult(diff, Collections.emptyList()); + logger.debug("Clearing local schema keyspace instances..."); + clear(); - Collection mutations = SchemaKeyspace.convertSchemaDiffToMutations(diff, now); - SchemaKeyspace.applyChanges(mutations); - - merge(diff); - updateVersion(); - if (!locally) - passiveAnnounceVersion(); - - return new TransformationResult(diff, mutations); + updateHandler.reset(false); + logger.info("Local schema reset is complete."); } - public static final class TransformationResult + private void merge(KeyspacesDiff diff, boolean removeData) { - public final boolean success; - public final RuntimeException exception; - public final KeyspacesDiff diff; - public final Collection mutations; - - private TransformationResult(boolean success, RuntimeException exception, KeyspacesDiff diff, Collection mutations) - { - this.success = success; - this.exception = exception; - this.diff = diff; - this.mutations = mutations; - } - - TransformationResult(RuntimeException exception) - { - this(false, exception, null, null); - } - - TransformationResult(KeyspacesDiff diff, Collection mutations) - { - this(true, null, diff, mutations); - } - } - - /** - * See CASSANDRA-16856/16996. Make sure schema pulls are synchronized to prevent concurrent schema pull/writes - */ - synchronized void merge(Collection mutations) - { - // only compare the keyspaces affected by this set of schema mutations - Set affectedKeyspaces = SchemaKeyspace.affectedKeyspaces(mutations); - - // fetch the current state of schema for the affected keyspaces only - Keyspaces before = keyspaces.filter(k -> affectedKeyspaces.contains(k.name)); - - // apply the schema mutations - SchemaKeyspace.applyChanges(mutations); - - // apply the schema mutations and fetch the new versions of the altered keyspaces - Keyspaces after = SchemaKeyspace.fetchKeyspaces(affectedKeyspaces); - - merge(Keyspaces.diff(before, after)); - } - - private void merge(KeyspacesDiff diff) - { - diff.dropped.forEach(this::dropKeyspace); + diff.dropped.forEach(keyspace -> dropKeyspace(keyspace, removeData)); diff.created.forEach(this::createKeyspace); - diff.altered.forEach(this::alterKeyspace); + diff.altered.forEach(delta -> alterKeyspace(delta, removeData)); } - private void alterKeyspace(KeyspaceDiff delta) + private void alterKeyspace(KeyspaceDiff delta, boolean dropData) { SchemaDiagnostics.keyspaceAltering(this, delta); - // drop tables and views - delta.views.dropped.forEach(this::dropView); - delta.tables.dropped.forEach(this::dropTable); + boolean initialized = Keyspace.isInitialized(); + + Keyspace keyspace = initialized ? getKeyspaceInstance(delta.before.name) : null; + if (initialized) + { + assert keyspace != null; + assert delta.before.name.equals(delta.after.name); + + // drop tables and views + delta.views.dropped.forEach(v -> dropView(keyspace, v, dropData)); + delta.tables.dropped.forEach(t -> dropTable(keyspace, t, dropData)); + } load(delta.after); - // add tables and views - delta.tables.created.forEach(this::createTable); - delta.views.created.forEach(this::createView); + if (initialized) + { + // add tables and views + delta.tables.created.forEach(t -> createTable(keyspace, t)); + delta.views.created.forEach(v -> createView(keyspace, v)); - // update tables and views - delta.tables.altered.forEach(diff -> alterTable(diff.after)); - delta.views.altered.forEach(diff -> alterView(diff.after)); + // update tables and views + delta.tables.altered.forEach(diff -> alterTable(keyspace, diff.after)); + delta.views.altered.forEach(diff -> alterView(keyspace, diff.after)); - // deal with all added, and altered views - Keyspace.open(delta.after.name).viewManager.reload(true); + // deal with all added, and altered views + Keyspace.open(delta.after.name, this, true).viewManager.reload(true); + } - // notify on everything dropped - delta.udas.dropped.forEach(uda -> notifyDropAggregate((UDAggregate) uda)); - delta.udfs.dropped.forEach(udf -> notifyDropFunction((UDFunction) udf)); - delta.views.dropped.forEach(this::notifyDropView); - delta.tables.dropped.forEach(this::notifyDropTable); - delta.types.dropped.forEach(this::notifyDropType); - - // notify on everything created - delta.types.created.forEach(this::notifyCreateType); - delta.tables.created.forEach(this::notifyCreateTable); - delta.views.created.forEach(this::notifyCreateView); - delta.udfs.created.forEach(udf -> notifyCreateFunction((UDFunction) udf)); - delta.udas.created.forEach(uda -> notifyCreateAggregate((UDAggregate) uda)); - - // notify on everything altered - if (!delta.before.params.equals(delta.after.params)) - notifyAlterKeyspace(delta.before, delta.after); - delta.types.altered.forEach(diff -> notifyAlterType(diff.before, diff.after)); - delta.tables.altered.forEach(diff -> notifyAlterTable(diff.before, diff.after)); - delta.views.altered.forEach(diff -> notifyAlterView(diff.before, diff.after)); - delta.udfs.altered.forEach(diff -> notifyAlterFunction(diff.before, diff.after)); - delta.udas.altered.forEach(diff -> notifyAlterAggregate(diff.before, diff.after)); + schemaChangeNotifier.notifyKeyspaceAltered(delta); SchemaDiagnostics.keyspaceAltered(this, delta); } @@ -754,184 +680,97 @@ public class Schema implements SchemaProvider { SchemaDiagnostics.keyspaceCreating(this, keyspace); load(keyspace); - Keyspace.open(keyspace.name); + if (Keyspace.isInitialized()) + { + Keyspace.open(keyspace.name, this, true); + } - notifyCreateKeyspace(keyspace); - keyspace.types.forEach(this::notifyCreateType); - keyspace.tables.forEach(this::notifyCreateTable); - keyspace.views.forEach(this::notifyCreateView); - keyspace.functions.udfs().forEach(this::notifyCreateFunction); - keyspace.functions.udas().forEach(this::notifyCreateAggregate); + schemaChangeNotifier.notifyKeyspaceCreated(keyspace); SchemaDiagnostics.keyspaceCreated(this, keyspace); // If keyspace has been added, we need to recalculate pending ranges to make sure // we send mutations to the correct set of bootstrapping nodes. Refer CASSANDRA-15433. - if (keyspace.params.replication.klass != LocalStrategy.class) + if (keyspace.params.replication.klass != LocalStrategy.class && Keyspace.isInitialized()) { PendingRangeCalculatorService.calculatePendingRanges(Keyspace.open(keyspace.name).getReplicationStrategy(), keyspace.name); } } - private void dropKeyspace(KeyspaceMetadata keyspace) + private void dropKeyspace(KeyspaceMetadata keyspace, boolean dropData) { - SchemaDiagnostics.keyspaceDroping(this, keyspace); - keyspace.views.forEach(this::dropView); - keyspace.tables.forEach(this::dropTable); + SchemaDiagnostics.keyspaceDropping(this, keyspace); + + boolean initialized = Keyspace.isInitialized(); + Keyspace ks = initialized ? getKeyspaceInstance(keyspace.name) : null; + if (initialized) + { + if (ks == null) + return; + + keyspace.views.forEach(v -> dropView(ks, v, dropData)); + keyspace.tables.forEach(t -> dropTable(ks, t, dropData)); + + // remove the keyspace from the static instances + maybeRemoveKeyspaceInstance(keyspace.name, dropData); + } - // remove the keyspace from the static instances. - maybeRemoveKeyspaceInstance(keyspace.name); unload(keyspace); - Keyspace.writeOrder.awaitNewBarrier(); - keyspace.functions.udas().forEach(this::notifyDropAggregate); - keyspace.functions.udfs().forEach(this::notifyDropFunction); - keyspace.views.forEach(this::notifyDropView); - keyspace.tables.forEach(this::notifyDropTable); - keyspace.types.forEach(this::notifyDropType); - notifyDropKeyspace(keyspace); - SchemaDiagnostics.keyspaceDroped(this, keyspace); + if (initialized) + { + Keyspace.writeOrder.awaitNewBarrier(); + } + + schemaChangeNotifier.notifyKeyspaceDropped(keyspace); + SchemaDiagnostics.keyspaceDropped(this, keyspace); } - private void dropView(ViewMetadata metadata) + private void dropView(Keyspace keyspace, ViewMetadata metadata, boolean dropData) { - Keyspace.open(metadata.keyspace()).viewManager.dropView(metadata.name()); - dropTable(metadata.metadata); + keyspace.viewManager.dropView(metadata.name()); + dropTable(keyspace, metadata.metadata, dropData); } - private void dropTable(TableMetadata metadata) + private void dropTable(Keyspace keyspace, TableMetadata metadata, boolean dropData) { SchemaDiagnostics.tableDropping(this, metadata); - Keyspace.open(metadata.keyspace).dropCf(metadata.id); + keyspace.dropCf(metadata.id, dropData); SchemaDiagnostics.tableDropped(this, metadata); } - private void createTable(TableMetadata table) + private void createTable(Keyspace keyspace, TableMetadata table) { SchemaDiagnostics.tableCreating(this, table); - Keyspace.open(table.keyspace).initCf(metadataRefs.get(table.id), true); + keyspace.initCf(tableMetadataRefCache.getTableMetadataRef(table.id), true); SchemaDiagnostics.tableCreated(this, table); } - private void createView(ViewMetadata view) + private void createView(Keyspace keyspace, ViewMetadata view) { - Keyspace.open(view.keyspace()).initCf(metadataRefs.get(view.metadata.id), true); + SchemaDiagnostics.tableCreating(this, view.metadata); + keyspace.initCf(tableMetadataRefCache.getTableMetadataRef(view.metadata.id), true); + SchemaDiagnostics.tableCreated(this, view.metadata); } - private void alterTable(TableMetadata updated) + private void alterTable(Keyspace keyspace, TableMetadata updated) { SchemaDiagnostics.tableAltering(this, updated); - Keyspace.open(updated.keyspace).getColumnFamilyStore(updated.name).reload(); + keyspace.getColumnFamilyStore(updated.name).reload(); SchemaDiagnostics.tableAltered(this, updated); } - private void alterView(ViewMetadata updated) + private void alterView(Keyspace keyspace, ViewMetadata updated) { - Keyspace.open(updated.keyspace()).getColumnFamilyStore(updated.name()).reload(); + SchemaDiagnostics.tableAltering(this, updated.metadata); + keyspace.getColumnFamilyStore(updated.name()).reload(); + SchemaDiagnostics.tableAltered(this, updated.metadata); } - private void notifyCreateKeyspace(KeyspaceMetadata ksm) + public Map> getOutstandingSchemaVersions() { - changeListeners.forEach(l -> l.onCreateKeyspace(ksm.name)); + return updateHandler instanceof DefaultSchemaUpdateHandler + ? ((DefaultSchemaUpdateHandler) updateHandler).getOutstandingSchemaVersions() + : Collections.emptyMap(); } - private void notifyCreateTable(TableMetadata metadata) - { - changeListeners.forEach(l -> l.onCreateTable(metadata.keyspace, metadata.name)); - } - - private void notifyCreateView(ViewMetadata view) - { - changeListeners.forEach(l -> l.onCreateView(view.keyspace(), view.name())); - } - - private void notifyCreateType(UserType ut) - { - changeListeners.forEach(l -> l.onCreateType(ut.keyspace, ut.getNameAsString())); - } - - private void notifyCreateFunction(UDFunction udf) - { - changeListeners.forEach(l -> l.onCreateFunction(udf.name().keyspace, udf.name().name, udf.argTypes())); - } - - private void notifyCreateAggregate(UDAggregate udf) - { - changeListeners.forEach(l -> l.onCreateAggregate(udf.name().keyspace, udf.name().name, udf.argTypes())); - } - - private void notifyAlterKeyspace(KeyspaceMetadata before, KeyspaceMetadata after) - { - changeListeners.forEach(l -> l.onAlterKeyspace(after.name)); - } - - private void notifyAlterTable(TableMetadata before, TableMetadata after) - { - boolean changeAffectedPreparedStatements = before.changeAffectsPreparedStatements(after); - changeListeners.forEach(l -> l.onAlterTable(after.keyspace, after.name, changeAffectedPreparedStatements)); - } - - private void notifyAlterView(ViewMetadata before, ViewMetadata after) - { - boolean changeAffectedPreparedStatements = before.metadata.changeAffectsPreparedStatements(after.metadata); - changeListeners.forEach(l ->l.onAlterView(after.keyspace(), after.name(), changeAffectedPreparedStatements)); - } - - private void notifyAlterType(UserType before, UserType after) - { - changeListeners.forEach(l -> l.onAlterType(after.keyspace, after.getNameAsString())); - } - - private void notifyAlterFunction(UDFunction before, UDFunction after) - { - changeListeners.forEach(l -> l.onAlterFunction(after.name().keyspace, after.name().name, after.argTypes())); - } - - private void notifyAlterAggregate(UDAggregate before, UDAggregate after) - { - changeListeners.forEach(l -> l.onAlterAggregate(after.name().keyspace, after.name().name, after.argTypes())); - } - - private void notifyDropKeyspace(KeyspaceMetadata ksm) - { - changeListeners.forEach(l -> l.onDropKeyspace(ksm.name)); - } - - private void notifyDropTable(TableMetadata metadata) - { - changeListeners.forEach(l -> l.onDropTable(metadata.keyspace, metadata.name)); - } - - private void notifyDropView(ViewMetadata view) - { - changeListeners.forEach(l -> l.onDropView(view.keyspace(), view.name())); - } - - private void notifyDropType(UserType ut) - { - changeListeners.forEach(l -> l.onDropType(ut.keyspace, ut.getNameAsString())); - } - - private void notifyDropFunction(UDFunction udf) - { - changeListeners.forEach(l -> l.onDropFunction(udf.name().keyspace, udf.name().name, udf.argTypes())); - } - - private void notifyDropAggregate(UDAggregate udf) - { - changeListeners.forEach(l -> l.onDropAggregate(udf.name().keyspace, udf.name().name, udf.argTypes())); - } - - - /** - * Converts the given schema version to a string. Returns {@code unknown}, if {@code version} is {@code null} - * or {@code "(empty)"}, if {@code version} refers to an {@link SchemaConstants#emptyVersion empty) schema. - */ - public static String schemaVersionToString(UUID version) - { - return version == null - ? "unknown" - : SchemaConstants.emptyVersion.equals(version) - ? "(empty)" - : version.toString(); - } } diff --git a/src/java/org/apache/cassandra/schema/SchemaChangeListener.java b/src/java/org/apache/cassandra/schema/SchemaChangeListener.java index 43903090c9..f8661437ce 100644 --- a/src/java/org/apache/cassandra/schema/SchemaChangeListener.java +++ b/src/java/org/apache/cassandra/schema/SchemaChangeListener.java @@ -17,86 +17,94 @@ */ package org.apache.cassandra.schema; -import java.util.List; +import org.apache.cassandra.cql3.functions.UDAggregate; +import org.apache.cassandra.cql3.functions.UDFunction; +import org.apache.cassandra.db.marshal.UserType; -import org.apache.cassandra.db.marshal.AbstractType; - -public abstract class SchemaChangeListener +public interface SchemaChangeListener { - public void onCreateKeyspace(String keyspace) + default void onCreateKeyspace(KeyspaceMetadata keyspace) { } - public void onCreateTable(String keyspace, String table) + default void onCreateTable(TableMetadata table) { } - public void onCreateView(String keyspace, String view) + default void onCreateView(ViewMetadata view) { - onCreateTable(keyspace, view); + onCreateTable(view.metadata); } - public void onCreateType(String keyspace, String type) + default void onCreateType(UserType type) { } - public void onCreateFunction(String keyspace, String function, List> argumentTypes) + default void onCreateFunction(UDFunction function) { } - public void onCreateAggregate(String keyspace, String aggregate, List> argumentTypes) + default void onCreateAggregate(UDAggregate aggregate) { } - public void onAlterKeyspace(String keyspace) + default void onAlterKeyspace(KeyspaceMetadata before, KeyspaceMetadata after) + { + } + + default void onPreAlterTable(TableMetadata before, TableMetadata after) { } // the boolean flag indicates whether the change that triggered this event may have a substantive // impact on statements using the column family. - public void onAlterTable(String keyspace, String table, boolean affectsStatements) + default void onAlterTable(TableMetadata before, TableMetadata after, boolean affectStatements) { } - public void onAlterView(String keyspace, String view, boolean affectsStataments) - { - onAlterTable(keyspace, view, affectsStataments); - } - - public void onAlterType(String keyspace, String type) + default void onPreAlterView(ViewMetadata before, ViewMetadata after) { } - public void onAlterFunction(String keyspace, String function, List> argumentTypes) + default void onAlterView(ViewMetadata before, ViewMetadata after, boolean affectStatements) + { + onAlterTable(before.metadata, after.metadata, affectStatements); + } + + default void onAlterType(UserType before, UserType after) { } - public void onAlterAggregate(String keyspace, String aggregate, List> argumentTypes) + default void onAlterFunction(UDFunction before, UDFunction after) { } - public void onDropKeyspace(String keyspace) + default void onAlterAggregate(UDAggregate before, UDAggregate after) { } - public void onDropTable(String keyspace, String table) + default void onDropKeyspace(KeyspaceMetadata keyspace) { } - public void onDropView(String keyspace, String view) - { - onDropTable(keyspace, view); - } - - public void onDropType(String keyspace, String type) + default void onDropTable(TableMetadata table) { } - public void onDropFunction(String keyspace, String function, List> argumentTypes) + default void onDropView(ViewMetadata view) + { + onDropTable(view.metadata); + } + + default void onDropType(UserType type) { } - public void onDropAggregate(String keyspace, String aggregate, List> argumentTypes) + default void onDropFunction(UDFunction function) + { + } + + default void onDropAggregate(UDAggregate aggregate) { } } diff --git a/src/java/org/apache/cassandra/schema/SchemaChangeNotifier.java b/src/java/org/apache/cassandra/schema/SchemaChangeNotifier.java new file mode 100644 index 0000000000..2cb52167da --- /dev/null +++ b/src/java/org/apache/cassandra/schema/SchemaChangeNotifier.java @@ -0,0 +1,207 @@ +/* + * 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 java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.apache.cassandra.cql3.functions.UDAggregate; +import org.apache.cassandra.cql3.functions.UDFunction; +import org.apache.cassandra.db.marshal.UserType; +import org.apache.cassandra.schema.SchemaTransformation.SchemaTransformationResult; + +/** + * Registers schema change listeners and sends the notifications. The interface of this class just takes the high level + * keyspace metadata changes. It iterates over all keyspaces elements and distributes appropriate notifications about + * changes around those elements (tables, views, types, functions). + */ +public class SchemaChangeNotifier +{ + private final List changeListeners = new CopyOnWriteArrayList<>(); + + public void registerListener(SchemaChangeListener listener) + { + changeListeners.add(listener); + } + + @SuppressWarnings("unused") + public void unregisterListener(SchemaChangeListener listener) + { + changeListeners.remove(listener); + } + + public void notifyKeyspaceCreated(KeyspaceMetadata keyspace) + { + notifyCreateKeyspace(keyspace); + keyspace.types.forEach(this::notifyCreateType); + keyspace.tables.forEach(this::notifyCreateTable); + keyspace.views.forEach(this::notifyCreateView); + keyspace.functions.udfs().forEach(this::notifyCreateFunction); + keyspace.functions.udas().forEach(this::notifyCreateAggregate); + } + + public void notifyKeyspaceAltered(KeyspaceMetadata.KeyspaceDiff delta) + { + // notify on everything dropped + delta.udas.dropped.forEach(uda -> notifyDropAggregate((UDAggregate) uda)); + delta.udfs.dropped.forEach(udf -> notifyDropFunction((UDFunction) udf)); + delta.views.dropped.forEach(this::notifyDropView); + delta.tables.dropped.forEach(this::notifyDropTable); + delta.types.dropped.forEach(this::notifyDropType); + + // notify on everything created + delta.types.created.forEach(this::notifyCreateType); + delta.tables.created.forEach(this::notifyCreateTable); + delta.views.created.forEach(this::notifyCreateView); + delta.udfs.created.forEach(udf -> notifyCreateFunction((UDFunction) udf)); + delta.udas.created.forEach(uda -> notifyCreateAggregate((UDAggregate) uda)); + + // notify on everything altered + if (!delta.before.params.equals(delta.after.params)) + notifyAlterKeyspace(delta.before, delta.after); + delta.types.altered.forEach(diff -> notifyAlterType(diff.before, diff.after)); + delta.tables.altered.forEach(diff -> notifyAlterTable(diff.before, diff.after)); + delta.views.altered.forEach(diff -> notifyAlterView(diff.before, diff.after)); + delta.udfs.altered.forEach(diff -> notifyAlterFunction(diff.before, diff.after)); + delta.udas.altered.forEach(diff -> notifyAlterAggregate(diff.before, diff.after)); + } + + public void notifyKeyspaceDropped(KeyspaceMetadata keyspace) + { + keyspace.functions.udas().forEach(this::notifyDropAggregate); + keyspace.functions.udfs().forEach(this::notifyDropFunction); + keyspace.views.forEach(this::notifyDropView); + keyspace.tables.forEach(this::notifyDropTable); + keyspace.types.forEach(this::notifyDropType); + notifyDropKeyspace(keyspace); + } + + public void notifyPreChanges(SchemaTransformationResult transformationResult) + { + transformationResult.diff.altered.forEach(this::notifyPreAlterKeyspace); + } + + private void notifyPreAlterKeyspace(KeyspaceMetadata.KeyspaceDiff keyspaceDiff) + { + keyspaceDiff.tables.altered.forEach(this::notifyPreAlterTable); + keyspaceDiff.views.altered.forEach(this::notifyPreAlterView); + } + + private void notifyPreAlterTable(Diff.Altered altered) + { + changeListeners.forEach(l -> l.onPreAlterTable(altered.before, altered.after)); + } + + private void notifyPreAlterView(Diff.Altered altered) + { + changeListeners.forEach(l -> l.onPreAlterView(altered.before, altered.after)); + } + + private void notifyCreateKeyspace(KeyspaceMetadata ksm) + { + changeListeners.forEach(l -> l.onCreateKeyspace(ksm)); + } + + private void notifyCreateTable(TableMetadata metadata) + { + changeListeners.forEach(l -> l.onCreateTable(metadata)); + } + + private void notifyCreateView(ViewMetadata view) + { + changeListeners.forEach(l -> l.onCreateView(view)); + } + + private void notifyCreateType(UserType ut) + { + changeListeners.forEach(l -> l.onCreateType(ut)); + } + + private void notifyCreateFunction(UDFunction udf) + { + changeListeners.forEach(l -> l.onCreateFunction(udf)); + } + + private void notifyCreateAggregate(UDAggregate udf) + { + changeListeners.forEach(l -> l.onCreateAggregate(udf)); + } + + private void notifyAlterKeyspace(KeyspaceMetadata before, KeyspaceMetadata after) + { + changeListeners.forEach(l -> l.onAlterKeyspace(before, after)); + } + + private void notifyAlterTable(TableMetadata before, TableMetadata after) + { + boolean changeAffectedPreparedStatements = before.changeAffectsPreparedStatements(after); + changeListeners.forEach(l -> l.onAlterTable(before, after, changeAffectedPreparedStatements)); + } + + private void notifyAlterView(ViewMetadata before, ViewMetadata after) + { + boolean changeAffectedPreparedStatements = before.metadata.changeAffectsPreparedStatements(after.metadata); + changeListeners.forEach(l -> l.onAlterView(before, after, changeAffectedPreparedStatements)); + } + + private void notifyAlterType(UserType before, UserType after) + { + changeListeners.forEach(l -> l.onAlterType(before, after)); + } + + private void notifyAlterFunction(UDFunction before, UDFunction after) + { + changeListeners.forEach(l -> l.onAlterFunction(before, after)); + } + + private void notifyAlterAggregate(UDAggregate before, UDAggregate after) + { + changeListeners.forEach(l -> l.onAlterAggregate(before, after)); + } + + private void notifyDropKeyspace(KeyspaceMetadata ksm) + { + changeListeners.forEach(l -> l.onDropKeyspace(ksm)); + } + + private void notifyDropTable(TableMetadata metadata) + { + changeListeners.forEach(l -> l.onDropTable(metadata)); + } + + private void notifyDropView(ViewMetadata view) + { + changeListeners.forEach(l -> l.onDropView(view)); + } + + private void notifyDropType(UserType ut) + { + changeListeners.forEach(l -> l.onDropType(ut)); + } + + private void notifyDropFunction(UDFunction udf) + { + changeListeners.forEach(l -> l.onDropFunction(udf)); + } + + private void notifyDropAggregate(UDAggregate udf) + { + changeListeners.forEach(l -> l.onDropAggregate(udf)); + } +} diff --git a/src/java/org/apache/cassandra/schema/SchemaDiagnostics.java b/src/java/org/apache/cassandra/schema/SchemaDiagnostics.java index 12b8409ebc..29243039da 100644 --- a/src/java/org/apache/cassandra/schema/SchemaDiagnostics.java +++ b/src/java/org/apache/cassandra/schema/SchemaDiagnostics.java @@ -86,28 +86,28 @@ final class SchemaDiagnostics delta.before, delta, null, null, null, null)); } - static void keyspaceDroping(Schema schema, KeyspaceMetadata keyspace) + static void keyspaceDropping(Schema schema, KeyspaceMetadata keyspace) { if (isEnabled(SchemaEventType.KS_DROPPING)) service.publish(new SchemaEvent(SchemaEventType.KS_DROPPING, schema, keyspace, null, null, null, null, null, null)); } - static void keyspaceDroped(Schema schema, KeyspaceMetadata keyspace) + static void keyspaceDropped(Schema schema, KeyspaceMetadata keyspace) { if (isEnabled(SchemaEventType.KS_DROPPED)) service.publish(new SchemaEvent(SchemaEventType.KS_DROPPED, schema, keyspace, null, null, null, null, null, null)); } - static void schemataLoading(Schema schema) + static void schemaLoading(Schema schema) { if (isEnabled(SchemaEventType.SCHEMATA_LOADING)) service.publish(new SchemaEvent(SchemaEventType.SCHEMATA_LOADING, schema, null, null, null, null, null, null, null)); } - static void schemataLoaded(Schema schema) + static void schemaLoaded(Schema schema) { if (isEnabled(SchemaEventType.SCHEMATA_LOADED)) service.publish(new SchemaEvent(SchemaEventType.SCHEMATA_LOADED, schema, null, @@ -121,7 +121,7 @@ final class SchemaDiagnostics null, null, null, null, null, null)); } - static void schemataCleared(Schema schema) + static void schemaCleared(Schema schema) { if (isEnabled(SchemaEventType.SCHEMATA_CLEARED)) service.publish(new SchemaEvent(SchemaEventType.SCHEMATA_CLEARED, schema, null, diff --git a/src/java/org/apache/cassandra/schema/SchemaEvent.java b/src/java/org/apache/cassandra/schema/SchemaEvent.java index 773deccbf5..c4085007e4 100644 --- a/src/java/org/apache/cassandra/schema/SchemaEvent.java +++ b/src/java/org/apache/cassandra/schema/SchemaEvent.java @@ -21,29 +21,30 @@ package org.apache.cassandra.schema; import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.UUID; -import java.util.stream.Collectors; +import java.util.stream.StreamSupport; import javax.annotation.Nullable; +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.MapDifference; import org.apache.cassandra.diag.DiagnosticEvent; -import org.apache.cassandra.utils.Pair; +import org.apache.cassandra.utils.Collectors3; public final class SchemaEvent extends DiagnosticEvent { private final SchemaEventType type; - private final HashSet keyspaces; - private final HashMap indexTables; - private final HashMap tables; - private final ArrayList nonSystemKeyspaces; - private final ArrayList userKeyspaces; + private final ImmutableCollection keyspaces; + private final ImmutableMap indexTables; + private final ImmutableCollection tables; + private final ImmutableCollection nonSystemKeyspaces; + private final ImmutableCollection userKeyspaces; private final int numberOfTables; private final UUID version; @@ -60,7 +61,7 @@ public final class SchemaEvent extends DiagnosticEvent @Nullable private final Views.ViewsDiff viewsDiff; @Nullable - private final MapDifference indexesDiff; + private final MapDifference indexesDiff; public enum SchemaEventType { @@ -89,7 +90,7 @@ public final class SchemaEvent extends DiagnosticEvent SchemaEvent(SchemaEventType type, Schema schema, @Nullable KeyspaceMetadata ksUpdate, @Nullable KeyspaceMetadata previous, @Nullable KeyspaceMetadata.KeyspaceDiff ksDiff, @Nullable TableMetadata tableUpdate, @Nullable Tables.TablesDiff tablesDiff, - @Nullable Views.ViewsDiff viewsDiff, @Nullable MapDifference indexesDiff) + @Nullable Views.ViewsDiff viewsDiff, @Nullable MapDifference indexesDiff) { this.type = type; this.ksUpdate = ksUpdate; @@ -100,27 +101,21 @@ public final class SchemaEvent extends DiagnosticEvent this.viewsDiff = viewsDiff; this.indexesDiff = indexesDiff; - this.keyspaces = new HashSet<>(schema.getKeyspaces()); - this.nonSystemKeyspaces = new ArrayList<>(schema.getNonSystemKeyspaces()); - this.userKeyspaces = new ArrayList<>(schema.getUserKeyspaces()); + this.keyspaces = schema.distributedAndLocalKeyspaces().names(); + this.nonSystemKeyspaces = schema.distributedKeyspaces().names(); + this.userKeyspaces = schema.getUserKeyspaces().names(); this.numberOfTables = schema.getNumberOfTables(); this.version = schema.getVersion(); - Map, TableMetadataRef> indexTableMetadataRefs = schema.getIndexTableMetadataRefs(); - Map indexTables = indexTableMetadataRefs.entrySet().stream() - .collect(Collectors.toMap(e -> e.getKey().left + ',' + - e.getKey().right, - e -> e.getValue().id.toHexString() + ',' + - e.getValue().keyspace + ',' + - e.getValue().name)); - this.indexTables = new HashMap<>(indexTables); - Map tableMetadataRefs = schema.getTableMetadataRefs(); - Map tables = tableMetadataRefs.entrySet().stream() - .collect(Collectors.toMap(e -> e.getKey().toHexString(), - e -> e.getValue().id.toHexString() + ',' + - e.getValue().keyspace + ',' + - e.getValue().name)); - this.tables = new HashMap<>(tables); + this.indexTables = schema.distributedKeyspaces().stream() + .flatMap(ks -> ks.tables.indexTables().entrySet().stream()) + .collect(Collectors3.toImmutableMap(e -> String.format("%s,%s", e.getValue().keyspace, e.getKey()), + e -> String.format("%s,%s,%s", e.getValue().id.toHexString(), e.getValue().keyspace, e.getValue().name))); + + this.tables = schema.distributedKeyspaces().stream() + .flatMap(ks -> StreamSupport.stream(ks.tablesAndViews().spliterator(), false)) + .map(e -> String.format("%s,%s,%s", e.id.toHexString(), e.keyspace, e.name)) + .collect(Collectors3.toImmutableList()); } public SchemaEventType getType() diff --git a/src/java/org/apache/cassandra/schema/SchemaKeyspace.java b/src/java/org/apache/cassandra/schema/SchemaKeyspace.java index 890bd15b4e..4f605390d7 100644 --- a/src/java/org/apache/cassandra/schema/SchemaKeyspace.java +++ b/src/java/org/apache/cassandra/schema/SchemaKeyspace.java @@ -64,7 +64,8 @@ import static org.apache.cassandra.utils.Simulate.With.GLOBAL_CLOCK; /** * system_schema.* tables and methods for manipulating them. * - * Please notice this class is _not_ thread safe. It should be accessed through {@link org.apache.cassandra.schema.Schema}. See CASSANDRA-16856/16996 + * Please notice this class is _not_ thread safe and all methods which reads or updates the data in schema keyspace + * should be accessed only from the implementation of {@link SchemaUpdateHandler} in synchronized blocks. */ @NotThreadSafe public final class SchemaKeyspace @@ -250,7 +251,7 @@ public final class SchemaKeyspace .build(); } - static KeyspaceMetadata metadata() + public static KeyspaceMetadata metadata() { return KeyspaceMetadata.create(SchemaConstants.SCHEMA_KEYSPACE_NAME, KeyspaceParams.local(), org.apache.cassandra.schema.Tables.of(ALL_TABLE_METADATA)); } @@ -259,32 +260,32 @@ public final class SchemaKeyspace { Map mutations = new HashMap<>(); - diff.created.forEach(k -> mutations.put(k.name, makeCreateKeyspaceMutation(k, timestamp).build())); diff.dropped.forEach(k -> mutations.put(k.name, makeDropKeyspaceMutation(k, timestamp).build())); + diff.created.forEach(k -> mutations.put(k.name, makeCreateKeyspaceMutation(k, timestamp).build())); diff.altered.forEach(kd -> { KeyspaceMetadata ks = kd.after; Mutation.SimpleBuilder builder = makeCreateKeyspaceMutation(ks.name, ks.params, timestamp); - kd.types.created.forEach(t -> addTypeToSchemaMutation(t, builder)); kd.types.dropped.forEach(t -> addDropTypeToSchemaMutation(t, builder)); + kd.types.created.forEach(t -> addTypeToSchemaMutation(t, builder)); kd.types.altered(Difference.SHALLOW).forEach(td -> addTypeToSchemaMutation(td.after, builder)); - kd.tables.created.forEach(t -> addTableToSchemaMutation(t, true, builder)); kd.tables.dropped.forEach(t -> addDropTableToSchemaMutation(t, builder)); + kd.tables.created.forEach(t -> addTableToSchemaMutation(t, true, builder)); kd.tables.altered(Difference.SHALLOW).forEach(td -> addAlterTableToSchemaMutation(td.before, td.after, builder)); - kd.views.created.forEach(v -> addViewToSchemaMutation(v, true, builder)); kd.views.dropped.forEach(v -> addDropViewToSchemaMutation(v, builder)); + kd.views.created.forEach(v -> addViewToSchemaMutation(v, true, builder)); kd.views.altered(Difference.SHALLOW).forEach(vd -> addAlterViewToSchemaMutation(vd.before, vd.after, builder)); - kd.udfs.created.forEach(f -> addFunctionToSchemaMutation((UDFunction) f, builder)); kd.udfs.dropped.forEach(f -> addDropFunctionToSchemaMutation((UDFunction) f, builder)); + kd.udfs.created.forEach(f -> addFunctionToSchemaMutation((UDFunction) f, builder)); kd.udfs.altered(Difference.SHALLOW).forEach(fd -> addFunctionToSchemaMutation(fd.after, builder)); - kd.udas.created.forEach(a -> addAggregateToSchemaMutation((UDAggregate) a, builder)); kd.udas.dropped.forEach(a -> addDropAggregateToSchemaMutation((UDAggregate) a, builder)); + kd.udas.created.forEach(a -> addAggregateToSchemaMutation((UDAggregate) a, builder)); kd.udas.altered(Difference.SHALLOW).forEach(ad -> addAggregateToSchemaMutation(ad.after, builder)); mutations.put(ks.name, builder.build()); @@ -319,6 +320,7 @@ public final class SchemaKeyspace static void truncate() { + logger.debug("Truncating schema tables..."); ALL.reverse().forEach(table -> getSchemaCFS(table).truncateBlocking()); } @@ -332,7 +334,7 @@ public final class SchemaKeyspace * Read schema from system keyspace and calculate MD5 digest of every row, resulting digest * will be converted into UUID which would act as content-based version of the schema. */ - static UUID calculateSchemaDigest() + public static UUID calculateSchemaDigest() { Digest digest = Digest.forSchema(); for (String table : ALL) @@ -444,7 +446,7 @@ public final class SchemaKeyspace return metadata.partitioner.decorateKey(metadata.partitionKeyType.decomposeUntyped(value)); } - static Mutation.SimpleBuilder makeCreateKeyspaceMutation(String name, KeyspaceParams params, long timestamp) + private static Mutation.SimpleBuilder makeCreateKeyspaceMutation(String name, KeyspaceParams params, long timestamp) { Mutation.SimpleBuilder builder = Mutation.simpleBuilder(Keyspaces.keyspace, decorate(Keyspaces, name)) .timestamp(timestamp); @@ -457,6 +459,7 @@ public final class SchemaKeyspace return builder; } + @VisibleForTesting static Mutation.SimpleBuilder makeCreateKeyspaceMutation(KeyspaceMetadata keyspace, long timestamp) { Mutation.SimpleBuilder builder = makeCreateKeyspaceMutation(keyspace.name, keyspace.params, timestamp); @@ -470,7 +473,7 @@ public final class SchemaKeyspace return builder; } - static Mutation.SimpleBuilder makeDropKeyspaceMutation(KeyspaceMetadata keyspace, long timestamp) + private static Mutation.SimpleBuilder makeDropKeyspaceMutation(KeyspaceMetadata keyspace, long timestamp) { Mutation.SimpleBuilder builder = Mutation.simpleBuilder(SchemaConstants.SCHEMA_KEYSPACE_NAME, decorate(Keyspaces, keyspace.name)) .timestamp(timestamp); @@ -494,6 +497,7 @@ public final class SchemaKeyspace builder.update(Types).row(type.name).delete(); } + @VisibleForTesting static Mutation.SimpleBuilder makeCreateTableMutation(KeyspaceMetadata keyspace, TableMetadata table, long timestamp) { // Include the serialized keyspace in case the target node missed a CREATE KEYSPACE migration (see CASSANDRA-5631). @@ -502,7 +506,7 @@ public final class SchemaKeyspace return builder; } - static void addTableToSchemaMutation(TableMetadata table, boolean withColumnsAndTriggers, Mutation.SimpleBuilder builder) + private static void addTableToSchemaMutation(TableMetadata table, boolean withColumnsAndTriggers, Mutation.SimpleBuilder builder) { Row.SimpleBuilder rowBuilder = builder.update(Tables) .row(table.name) @@ -608,6 +612,7 @@ public final class SchemaKeyspace addUpdatedIndexToSchemaMutation(newTable, diff.rightValue(), builder); } + @VisibleForTesting static Mutation.SimpleBuilder makeUpdateTableMutation(KeyspaceMetadata keyspace, TableMetadata oldTable, TableMetadata newTable, @@ -640,14 +645,6 @@ public final class SchemaKeyspace return Maps.difference(beforeMap, afterMap); } - static Mutation.SimpleBuilder makeDropTableMutation(KeyspaceMetadata keyspace, TableMetadata table, long timestamp) - { - // Include the serialized keyspace in case the target node missed a CREATE KEYSPACE migration (see CASSANDRA-5631). - Mutation.SimpleBuilder builder = makeCreateKeyspaceMutation(keyspace.name, keyspace.params, timestamp); - addDropTableToSchemaMutation(table, builder); - return builder; - } - private static void addDropTableToSchemaMutation(TableMetadata table, Mutation.SimpleBuilder builder) { builder.update(Tables).row(table.name).delete(); @@ -946,6 +943,7 @@ public final class SchemaKeyspace .build(); } + @VisibleForTesting static TableParams createTableParamsFromRow(UntypedResultSet.Row row) { return TableParams.builder() @@ -986,6 +984,7 @@ public final class SchemaKeyspace return columns; } + @VisibleForTesting static ColumnMetadata createColumnFromRow(UntypedResultSet.Row row, Types types) { String keyspace = row.getString("keyspace_name"); @@ -1032,7 +1031,7 @@ public final class SchemaKeyspace ? ColumnMetadata.Kind.valueOf(row.getString("kind").toUpperCase()) : ColumnMetadata.Kind.REGULAR; assert kind == ColumnMetadata.Kind.REGULAR || kind == ColumnMetadata.Kind.STATIC - : "Unexpected dropped column kind: " + kind.toString(); + : "Unexpected dropped column kind: " + kind; ColumnMetadata column = new ColumnMetadata(keyspace, table, ColumnIdentifier.getInterned(name, true), type, ColumnMetadata.NO_POSITION, kind); long droppedTime = TimeUnit.MILLISECONDS.toMicros(row.getLong("dropped_time")); diff --git a/src/java/org/apache/cassandra/schema/SchemaMigrationDiagnostics.java b/src/java/org/apache/cassandra/schema/SchemaMigrationDiagnostics.java deleted file mode 100644 index 62f1768d89..0000000000 --- a/src/java/org/apache/cassandra/schema/SchemaMigrationDiagnostics.java +++ /dev/null @@ -1,83 +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.schema; - -import java.util.UUID; - -import org.apache.cassandra.diag.DiagnosticEventService; -import org.apache.cassandra.locator.InetAddressAndPort; -import org.apache.cassandra.schema.SchemaMigrationEvent.MigrationManagerEventType; - -final class SchemaMigrationDiagnostics -{ - private static final DiagnosticEventService service = DiagnosticEventService.instance(); - - private SchemaMigrationDiagnostics() - { - } - - static void unknownLocalSchemaVersion(InetAddressAndPort endpoint, UUID theirVersion) - { - if (isEnabled(MigrationManagerEventType.UNKNOWN_LOCAL_SCHEMA_VERSION)) - service.publish(new SchemaMigrationEvent(MigrationManagerEventType.UNKNOWN_LOCAL_SCHEMA_VERSION, endpoint, - theirVersion)); - } - - static void versionMatch(InetAddressAndPort endpoint, UUID theirVersion) - { - if (isEnabled(MigrationManagerEventType.VERSION_MATCH)) - service.publish(new SchemaMigrationEvent(MigrationManagerEventType.VERSION_MATCH, endpoint, theirVersion)); - } - - static void skipPull(InetAddressAndPort endpoint, UUID theirVersion) - { - if (isEnabled(MigrationManagerEventType.SKIP_PULL)) - service.publish(new SchemaMigrationEvent(MigrationManagerEventType.SKIP_PULL, endpoint, theirVersion)); - } - - static void resetLocalSchema() - { - if (isEnabled(MigrationManagerEventType.RESET_LOCAL_SCHEMA)) - service.publish(new SchemaMigrationEvent(MigrationManagerEventType.RESET_LOCAL_SCHEMA, null, null)); - } - - static void taskCreated(InetAddressAndPort endpoint) - { - if (isEnabled(MigrationManagerEventType.TASK_CREATED)) - service.publish(new SchemaMigrationEvent(MigrationManagerEventType.TASK_CREATED, endpoint, null)); - } - - static void taskSendAborted(InetAddressAndPort endpoint) - { - if (isEnabled(MigrationManagerEventType.TASK_SEND_ABORTED)) - service.publish(new SchemaMigrationEvent(MigrationManagerEventType.TASK_SEND_ABORTED, endpoint, null)); - } - - static void taskRequestSend(InetAddressAndPort endpoint) - { - if (isEnabled(MigrationManagerEventType.TASK_REQUEST_SEND)) - service.publish(new SchemaMigrationEvent(MigrationManagerEventType.TASK_REQUEST_SEND, - endpoint, null)); - } - - private static boolean isEnabled(MigrationManagerEventType type) - { - return service.isEnabled(SchemaMigrationEvent.class, type); - } -} diff --git a/src/java/org/apache/cassandra/schema/SchemaMigrationEvent.java b/src/java/org/apache/cassandra/schema/SchemaMigrationEvent.java deleted file mode 100644 index e6f4e71315..0000000000 --- a/src/java/org/apache/cassandra/schema/SchemaMigrationEvent.java +++ /dev/null @@ -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.schema; - -import java.io.Serializable; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -import javax.annotation.Nullable; - -import org.apache.cassandra.db.SystemKeyspace; -import org.apache.cassandra.diag.DiagnosticEvent; -import org.apache.cassandra.gms.FailureDetector; -import org.apache.cassandra.gms.Gossiper; -import org.apache.cassandra.locator.InetAddressAndPort; -import org.apache.cassandra.net.MessagingService; - -/** - * Internal events emitted by {@link MigrationManager}. - */ -final class SchemaMigrationEvent extends DiagnosticEvent -{ - private final MigrationManagerEventType type; - @Nullable - private final InetAddressAndPort endpoint; - @Nullable - private final UUID endpointSchemaVersion; - private final UUID localSchemaVersion; - private final Integer localMessagingVersion; - private final SystemKeyspace.BootstrapState bootstrapState; - private final Integer inflightTaskCount; - @Nullable - private Integer endpointMessagingVersion; - @Nullable - private Boolean endpointGossipOnlyMember; - @Nullable - private Boolean isAlive; - - enum MigrationManagerEventType - { - UNKNOWN_LOCAL_SCHEMA_VERSION, - VERSION_MATCH, - SKIP_PULL, - RESET_LOCAL_SCHEMA, - TASK_CREATED, - TASK_SEND_ABORTED, - TASK_REQUEST_SEND - } - - SchemaMigrationEvent(MigrationManagerEventType type, - @Nullable InetAddressAndPort endpoint, @Nullable UUID endpointSchemaVersion) - { - this.type = type; - this.endpoint = endpoint; - this.endpointSchemaVersion = endpointSchemaVersion; - - localSchemaVersion = Schema.instance.getVersion(); - localMessagingVersion = MessagingService.current_version; - - inflightTaskCount = MigrationCoordinator.instance.getInflightTasks(); - - this.bootstrapState = SystemKeyspace.getBootstrapState(); - - if (endpoint == null) return; - - if (MessagingService.instance().versions.knows(endpoint)) - endpointMessagingVersion = MessagingService.instance().versions.getRaw(endpoint); - - endpointGossipOnlyMember = Gossiper.instance.isGossipOnlyMember(endpoint); - this.isAlive = FailureDetector.instance.isAlive(endpoint); - } - - public Enum getType() - { - return type; - } - - public Map toMap() - { - HashMap ret = new HashMap<>(); - if (endpoint != null) ret.put("endpoint", endpoint.getHostAddressAndPort()); - ret.put("endpointSchemaVersion", Schema.schemaVersionToString(endpointSchemaVersion)); - ret.put("localSchemaVersion", Schema.schemaVersionToString(localSchemaVersion)); - if (endpointMessagingVersion != null) ret.put("endpointMessagingVersion", endpointMessagingVersion); - if (localMessagingVersion != null) ret.put("localMessagingVersion", localMessagingVersion); - if (endpointGossipOnlyMember != null) ret.put("endpointGossipOnlyMember", endpointGossipOnlyMember); - if (isAlive != null) ret.put("endpointIsAlive", isAlive); - if (bootstrapState != null) ret.put("bootstrapState", bootstrapState.name()); - if (inflightTaskCount != null) ret.put("inflightTaskCount", inflightTaskCount); - return ret; - } -} diff --git a/src/java/org/apache/cassandra/schema/SchemaMutationsSerializer.java b/src/java/org/apache/cassandra/schema/SchemaMutationsSerializer.java new file mode 100644 index 0000000000..ba65c0d0c9 --- /dev/null +++ b/src/java/org/apache/cassandra/schema/SchemaMutationsSerializer.java @@ -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.schema; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; + +import org.apache.cassandra.db.Mutation; +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; + +public class SchemaMutationsSerializer implements IVersionedSerializer> +{ + public static final SchemaMutationsSerializer instance = new SchemaMutationsSerializer(); + + public void serialize(Collection schema, DataOutputPlus out, int version) throws IOException + { + out.writeInt(schema.size()); + for (Mutation mutation : schema) + Mutation.serializer.serialize(mutation, out, version); + } + + public Collection deserialize(DataInputPlus in, int version) throws IOException + { + int count = in.readInt(); + Collection schema = new ArrayList<>(count); + + for (int i = 0; i < count; i++) + schema.add(Mutation.serializer.deserialize(in, version)); + + return schema; + } + + public long serializedSize(Collection schema, int version) + { + int size = TypeSizes.sizeof(schema.size()); + for (Mutation mutation : schema) + size += mutation.serializedSize(version); + return size; + } +} diff --git a/src/java/org/apache/cassandra/schema/SchemaPullVerbHandler.java b/src/java/org/apache/cassandra/schema/SchemaPullVerbHandler.java index 73b85b5a7f..533090b7eb 100644 --- a/src/java/org/apache/cassandra/schema/SchemaPullVerbHandler.java +++ b/src/java/org/apache/cassandra/schema/SchemaPullVerbHandler.java @@ -18,11 +18,16 @@ package org.apache.cassandra.schema; import java.util.Collection; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Consumer; +import java.util.function.Function; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.cassandra.db.Mutation; +import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.net.IVerbHandler; import org.apache.cassandra.net.Message; import org.apache.cassandra.net.MessagingService; @@ -38,10 +43,20 @@ public final class SchemaPullVerbHandler implements IVerbHandler private static final Logger logger = LoggerFactory.getLogger(SchemaPullVerbHandler.class); + private final List>> handlers = new CopyOnWriteArrayList<>(); + + public void register(Consumer> handler) + { + handlers.add(handler); + } + public void doVerb(Message message) { logger.trace("Received schema pull request from {}", message.from()); - Message> response = message.responseWith(Schema.instance.schemaKeyspaceAsMutations()); - MessagingService.instance().send(response, message.from()); + List>> handlers = this.handlers; + if (handlers.isEmpty()) + throw new UnsupportedOperationException("There is no handler registered for schema pull verb"); + + handlers.forEach(h -> h.accept(message)); } } diff --git a/src/java/org/apache/cassandra/schema/SchemaPushVerbHandler.java b/src/java/org/apache/cassandra/schema/SchemaPushVerbHandler.java index f2f0faf3ac..114f918e0e 100644 --- a/src/java/org/apache/cassandra/schema/SchemaPushVerbHandler.java +++ b/src/java/org/apache/cassandra/schema/SchemaPushVerbHandler.java @@ -18,6 +18,9 @@ package org.apache.cassandra.schema; import java.util.Collection; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Consumer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,6 +29,7 @@ import org.apache.cassandra.concurrent.Stage; import org.apache.cassandra.db.Mutation; import org.apache.cassandra.net.IVerbHandler; import org.apache.cassandra.net.Message; +import org.apache.cassandra.net.NoPayload; /** * Called when node receives updated schema state from the schema migration coordinator node. @@ -39,11 +43,22 @@ public final class SchemaPushVerbHandler implements IVerbHandler>>> handlers = new CopyOnWriteArrayList<>(); + + public void register(Consumer>> handler) + { + handlers.add(handler); + } + public void doVerb(final Message> message) { logger.trace("Received schema push request from {}", message.from()); - SchemaAnnouncementDiagnostics.schemataMutationsReceived(message.from()); - Stage.MIGRATION.submit(() -> Schema.instance.mergeAndAnnounceVersion(message.payload)); + + List>>> handlers = this.handlers; + if (handlers.isEmpty()) + throw new UnsupportedOperationException("There is no handler registered for schema push verb"); + + handlers.forEach(h -> h.accept(message)); } } diff --git a/src/java/org/apache/cassandra/schema/SchemaTransformation.java b/src/java/org/apache/cassandra/schema/SchemaTransformation.java index e2290a339b..2b020a02a0 100644 --- a/src/java/org/apache/cassandra/schema/SchemaTransformation.java +++ b/src/java/org/apache/cassandra/schema/SchemaTransformation.java @@ -17,17 +17,51 @@ */ package org.apache.cassandra.schema; -import java.net.UnknownHostException; +import java.util.Optional; public interface SchemaTransformation { /** * Apply a statement transformation to a schema snapshot. - * - * Implementing methods should be side-effect free. + *

+ * Implementing methods should be side-effect free (outside of throwing exceptions if the transformation cannot + * be successfully applied to the provided schema). * * @param schema Keyspaces to base the transformation on * @return Keyspaces transformed by the statement */ - Keyspaces apply(Keyspaces schema) throws UnknownHostException; + Keyspaces apply(Keyspaces schema); + + /** + * If the transformation should be applied with a certain timestamp, this method should be overriden. This is used + * by {@link SchemaTransformations#updateSystemKeyspace(KeyspaceMetadata, long)} when we need to set the fixed + * timestamp in order to preserve user settings. + */ + default Optional fixedTimestampMicros() + { + return Optional.empty(); + } + + /** + * The result of applying (on this node) a given schema transformation. + */ + class SchemaTransformationResult + { + public final DistributedSchema before; + public final DistributedSchema after; + public final Keyspaces.KeyspacesDiff diff; + + public SchemaTransformationResult(DistributedSchema before, DistributedSchema after, Keyspaces.KeyspacesDiff diff) + { + this.before = before; + this.after = after; + this.diff = diff; + } + + @Override + public String toString() + { + return String.format("SchemaTransformationResult{%s --> %s, diff=%s}", before.getVersion(), after.getVersion(), diff); + } + } } diff --git a/src/java/org/apache/cassandra/schema/SchemaTransformations.java b/src/java/org/apache/cassandra/schema/SchemaTransformations.java new file mode 100644 index 0000000000..124f9a6ef6 --- /dev/null +++ b/src/java/org/apache/cassandra/schema/SchemaTransformations.java @@ -0,0 +1,210 @@ +/* + * 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 java.util.Optional; + +import org.apache.cassandra.db.marshal.UserType; +import org.apache.cassandra.exceptions.AlreadyExistsException; +import org.apache.cassandra.exceptions.ConfigurationException; + +import static org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest; + +/** + * Factory and utility methods to create simple schema transformation. + */ +public class SchemaTransformations +{ + /** + * Creates a schema transformation that adds the provided keyspace. + * + * @param keyspace the keyspace to add. + * @param ignoreIfExists if {@code true}, the transformation is a no-op if a keyspace of the same name than + * {@code keyspace} already exists in the schema the transformation is applied on. Otherwise, + * the transformation throws an {@link AlreadyExistsException} in that case. + * @return the created transformation. + */ + public static SchemaTransformation addKeyspace(KeyspaceMetadata keyspace, boolean ignoreIfExists) + { + return schema -> + { + KeyspaceMetadata existing = schema.getNullable(keyspace.name); + if (existing != null) + { + if (ignoreIfExists) + return schema; + + throw new AlreadyExistsException(keyspace.name); + } + + return schema.withAddedOrUpdated(keyspace); + }; + } + + /** + * Creates a schema transformation that adds the provided table. + * + * @param table the table to add. + * @param ignoreIfExists if {@code true}, the transformation is a no-op if a table of the same name than + * {@code table} already exists in the schema the transformation is applied on. Otherwise, + * the transformation throws an {@link AlreadyExistsException} in that case. + * @return the created transformation. + */ + public static SchemaTransformation addTable(TableMetadata table, boolean ignoreIfExists) + { + return schema -> + { + KeyspaceMetadata keyspace = schema.getNullable(table.keyspace); + if (keyspace == null) + throw invalidRequest("Keyspace '%s' doesn't exist", table.keyspace); + + if (keyspace.hasTable(table.name)) + { + if (ignoreIfExists) + return schema; + + throw new AlreadyExistsException(table.keyspace, table.name); + } + + table.validate(); + + return schema.withAddedOrUpdated(keyspace.withSwapped(keyspace.tables.with(table))); + }; + } + + public static SchemaTransformation addTypes(Types toAdd, boolean ignoreIfExists) + { + return schema -> + { + if (toAdd.isEmpty()) + return schema; + + String keyspaceName = toAdd.iterator().next().keyspace; + KeyspaceMetadata keyspace = schema.getNullable(keyspaceName); + if (null == keyspace) + throw invalidRequest("Keyspace '%s' doesn't exist", keyspaceName); + + Types types = keyspace.types; + for (UserType type : toAdd) + { + if (types.containsType(type.name)) + { + if (ignoreIfExists) + continue; + + throw new ConfigurationException("Type " + type + " already exists in " + keyspaceName); + } + + types = types.with(type); + } + return schema.withAddedOrReplaced(keyspace.withSwapped(types)); + }; + } + + /** + * Creates a schema transformation that adds the provided view. + * + * @param view the view to add. + * @param ignoreIfExists if {@code true}, the transformation is a no-op if a view of the same name than + * {@code view} already exists in the schema the transformation is applied on. Otherwise, + * the transformation throws an {@link AlreadyExistsException} in that case. + * @return the created transformation. + */ + public static SchemaTransformation addView(ViewMetadata view, boolean ignoreIfExists) + { + return schema -> + { + KeyspaceMetadata keyspace = schema.getNullable(view.keyspace()); + if (keyspace == null) + throw invalidRequest("Cannot add view to non existing keyspace '%s'", view.keyspace()); + + if (keyspace.hasView(view.name())) + { + if (ignoreIfExists) + return schema; + + throw new AlreadyExistsException(view.keyspace(), view.name()); + } + + return schema.withAddedOrUpdated(keyspace.withSwapped(keyspace.views.with(view))); + }; + } + + /** + * We have a set of non-local, distributed system keyspaces, e.g. system_traces, system_auth, etc. + * (see {@link SchemaConstants#REPLICATED_SYSTEM_KEYSPACE_NAMES}), that need to be created on cluster initialisation, + * and later evolved on major upgrades (sometimes minor too). This method compares the current known definitions + * of the tables (if the keyspace exists) to the expected, most modern ones expected by the running version of C*. + * If any changes have been detected, a schema transformation returned by this method should make cluster's view of + * that keyspace aligned with the expected modern definition. + * + * @param keyspace the metadata of the keyspace as it should be after application. + * @param generation timestamp to use for the table changes in the schema mutation + * @return the transformation. + */ + public static SchemaTransformation updateSystemKeyspace(KeyspaceMetadata keyspace, long generation) + { + return new SchemaTransformation() + { + @Override + public Optional fixedTimestampMicros() + { + return Optional.of(generation); + } + + @Override + public Keyspaces apply(Keyspaces schema) + { + KeyspaceMetadata updatedKeyspace = keyspace; + KeyspaceMetadata curKeyspace = schema.getNullable(keyspace.name); + if (curKeyspace != null) + { + // If the keyspace already exists, we preserve whatever parameters it has. + updatedKeyspace = updatedKeyspace.withSwapped(curKeyspace.params); + + for (TableMetadata curTable : curKeyspace.tables) + { + TableMetadata desiredTable = updatedKeyspace.tables.getNullable(curTable.name); + if (desiredTable == null) + { + // preserve exsiting tables which are missing in the new keyspace definition + updatedKeyspace = updatedKeyspace.withSwapped(updatedKeyspace.tables.with(curTable)); + } + else + { + updatedKeyspace = updatedKeyspace.withSwapped(updatedKeyspace.tables.without(desiredTable)); + + TableMetadata.Builder updatedBuilder = desiredTable.unbuild(); + + for (ColumnMetadata column : curTable.regularAndStaticColumns()) + { + if (!desiredTable.regularAndStaticColumns().contains(column)) + updatedBuilder.addColumn(column); + } + + updatedKeyspace = updatedKeyspace.withSwapped(updatedKeyspace.tables.with(updatedBuilder.build())); + } + } + } + return schema.withAddedOrReplaced(updatedKeyspace); + } + }; + } + +} \ No newline at end of file diff --git a/src/java/org/apache/cassandra/schema/SchemaUpdateHandler.java b/src/java/org/apache/cassandra/schema/SchemaUpdateHandler.java new file mode 100644 index 0000000000..c429f9b5d5 --- /dev/null +++ b/src/java/org/apache/cassandra/schema/SchemaUpdateHandler.java @@ -0,0 +1,76 @@ +/* + * 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 java.time.Duration; + +import org.apache.cassandra.schema.SchemaTransformation.SchemaTransformationResult; + +/** + * Schema update handler is responsible for maintaining the shared schema and synchronizing it with other nodes in + * the cluster, which means pushing and pulling changes, as well as tracking the current version in the cluster. + *

+ * The interface has been extracted to abstract out that functionality. It allows for various implementations like + * Gossip based (the default), ETCD, offline, etc., and make it easier for mocking in unit tests. + */ +public interface SchemaUpdateHandler +{ + /** + * Starts actively synchronizing schema with the rest of the cluster. It is called in the very beginning of the + * node startup. It is not expected to block - to await for the startup completion we have another method + * {@link #waitUntilReady(Duration)}. + */ + void start(); + + /** + * Waits until the schema update handler is ready and returns the result. If the method returns {@code false} it + * means that readiness could not be achieved within the specified period of time. The method can be used just to + * check if schema is ready by passing {@link Duration#ZERO} as the timeout - in such case it returns immediately. + * + * @param timeout the maximum time to wait for schema readiness + * @return whether readiness is achieved + */ + boolean waitUntilReady(Duration timeout); + + /** + * Applies schema transformation in the underlying storage and synchronizes with other nodes. + * + * @param transformation schema transformation to be performed + * @param local if true, the caller does not require synchronizing schema with other nodes - in practise local is + * used only in some tests + * @return transformation result + */ + SchemaTransformationResult apply(SchemaTransformation transformation, boolean local); + + /** + * Resets the schema either by reloading data from the local storage or from the other nodes. Once the schema is + * refreshed, the callbacks provided in the factory method are executed, and the updated schema version is announced. + * + * @param local whether we should reset with locally stored schema or fetch the schema from other nodes + * @return transformation result + */ + SchemaTransformationResult reset(boolean local); + + /** + * Clears the locally stored schema entirely. After this operation the schema is equal to {@link DistributedSchema#EMPTY}. + * The method does not execute any callback. It is indended to reinitialize the schema later using the method + * {@link #reset(boolean)}. + */ + void clear(); +} diff --git a/src/java/org/apache/cassandra/schema/SchemaUpdateHandlerFactory.java b/src/java/org/apache/cassandra/schema/SchemaUpdateHandlerFactory.java new file mode 100644 index 0000000000..f324a5d6e6 --- /dev/null +++ b/src/java/org/apache/cassandra/schema/SchemaUpdateHandlerFactory.java @@ -0,0 +1,35 @@ +/* + * 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 java.util.function.BiConsumer; + +import org.apache.cassandra.schema.SchemaTransformation.SchemaTransformationResult; + +public interface SchemaUpdateHandlerFactory +{ + /** + * A factory which provides the appropriate schema update handler. The actual implementation may be different for + * different run modes (client, tool, daemon). + * + * @param online whether schema update handler should work online and be aware of the other nodes (when in daemon mode) + * @param updateSchemaCallback callback which will be called right after the shared schema is updated + */ + SchemaUpdateHandler getSchemaUpdateHandler(boolean online, BiConsumer updateSchemaCallback); +} diff --git a/src/java/org/apache/cassandra/schema/SchemaUpdateHandlerFactoryProvider.java b/src/java/org/apache/cassandra/schema/SchemaUpdateHandlerFactoryProvider.java new file mode 100644 index 0000000000..3ee2851003 --- /dev/null +++ b/src/java/org/apache/cassandra/schema/SchemaUpdateHandlerFactoryProvider.java @@ -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.schema; + +import javax.inject.Provider; + +import org.apache.commons.lang3.StringUtils; + +import org.apache.cassandra.exceptions.ConfigurationException; +import org.apache.cassandra.utils.FBUtilities; + +/** + * Provides the instance of SchemaUpdateHandler factory pointed by {@link #SUH_FACTORY_CLASS_PROPERTY} system property. + * If the property is not defined, the default factory {@link DefaultSchemaUpdateHandler} instance is returned. + */ +public class SchemaUpdateHandlerFactoryProvider implements Provider +{ + private static final String SUH_FACTORY_CLASS_PROPERTY = "cassandra.schema.update_handler_factory.class"; + + public final static SchemaUpdateHandlerFactoryProvider instance = new SchemaUpdateHandlerFactoryProvider(); + + @Override + public SchemaUpdateHandlerFactory get() + { + String suhFactoryClassName = StringUtils.trimToNull(System.getProperty(SUH_FACTORY_CLASS_PROPERTY)); + if (suhFactoryClassName == null) + { + return DefaultSchemaUpdateHandlerFactory.instance; + } + else + { + Class suhFactoryClass = FBUtilities.classForName(suhFactoryClassName, "schema update handler factory"); + try + { + return suhFactoryClass.newInstance(); + } + catch (InstantiationException | IllegalAccessException ex) + { + throw new ConfigurationException(String.format("Failed to initialize schema update handler factory class %s defined in %s system property.", + suhFactoryClassName, SUH_FACTORY_CLASS_PROPERTY), ex); + } + } + } +} diff --git a/src/java/org/apache/cassandra/schema/TableId.java b/src/java/org/apache/cassandra/schema/TableId.java index cefc95f50e..fd47a47e58 100644 --- a/src/java/org/apache/cassandra/schema/TableId.java +++ b/src/java/org/apache/cassandra/schema/TableId.java @@ -28,6 +28,8 @@ import org.apache.cassandra.utils.ByteBufferUtil; import static org.apache.cassandra.utils.TimeUUID.Generator.nextTimeUUID; +import static java.nio.charset.StandardCharsets.UTF_8; + /** * The unique identifier of a table. *

@@ -76,7 +78,7 @@ public class TableId public static TableId unsafeDeterministic(String keyspace, String table) { - return new TableId(UUID.nameUUIDFromBytes(ArrayUtils.addAll(keyspace.getBytes(), table.getBytes()))); + return new TableId(UUID.nameUUIDFromBytes(ArrayUtils.addAll(keyspace.getBytes(UTF_8), table.getBytes(UTF_8)))); } public String toHexString() diff --git a/src/java/org/apache/cassandra/schema/TableMetadataRefCache.java b/src/java/org/apache/cassandra/schema/TableMetadataRefCache.java new file mode 100644 index 0000000000..d947d1d690 --- /dev/null +++ b/src/java/org/apache/cassandra/schema/TableMetadataRefCache.java @@ -0,0 +1,152 @@ +/* + * 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 java.util.Collections; +import java.util.Map; + +import com.google.common.collect.MapDifference; +import com.google.common.collect.Maps; + +import org.apache.cassandra.utils.Pair; + +/** + * Manages the cached {@link TableMetadataRef} objects which holds the references to {@link TableMetadata} objects. + *

+ * The purpose of {@link TableMetadataRef} is that the reference to {@link TableMetadataRef} remains unchanged when + * the metadata of the table changes. {@link TableMetadata} is immutable, so when it changes, we only switch + * the reference inside the existing {@link TableMetadataRef} object. + */ +class TableMetadataRefCache +{ + public final static TableMetadataRefCache EMPTY = new TableMetadataRefCache(Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap()); + + // UUID -> mutable metadata ref map. We have to update these in place every time a table changes. + private final Map metadataRefs; + + // keyspace and table names -> mutable metadata ref map. + private final Map, TableMetadataRef> metadataRefsByName; + + // (keyspace name, index name) -> mutable metadata ref map. We have to update these in place every time an index changes. + private final Map, TableMetadataRef> indexMetadataRefs; + + public TableMetadataRefCache(Map metadataRefs, + Map, TableMetadataRef> metadataRefsByName, + Map, TableMetadataRef> indexMetadataRefs) + { + this.metadataRefs = Collections.unmodifiableMap(metadataRefs); + this.metadataRefsByName = Collections.unmodifiableMap(metadataRefsByName); + this.indexMetadataRefs = Collections.unmodifiableMap(indexMetadataRefs); + } + + /** + * Returns cache copy with added the {@link TableMetadataRef} corresponding to the provided keyspace to {@link #metadataRefs} and + * {@link #indexMetadataRefs}, assuming the keyspace is new (in the sense of not being tracked by the manager yet). + */ + TableMetadataRefCache withNewRefs(KeyspaceMetadata ksm) + { + return withUpdatedRefs(ksm.empty(), ksm); + } + + /** + * Returns cache copy with updated the {@link TableMetadataRef} in {@link #metadataRefs} and {@link #indexMetadataRefs}, + * for an existing updated keyspace given it's previous and new definition. + *

+ * Note that {@link TableMetadataRef} are not duplicated and table metadata is altered in the existing refs. + */ + TableMetadataRefCache withUpdatedRefs(KeyspaceMetadata previous, KeyspaceMetadata updated) + { + Tables.TablesDiff tablesDiff = Tables.diff(previous.tables, updated.tables); + Views.ViewsDiff viewsDiff = Views.diff(previous.views, updated.views); + + MapDifference indexesDiff = previous.tables.indexesDiff(updated.tables); + + boolean hasCreatedOrDroppedTablesOrViews = tablesDiff.created.size() > 0 || tablesDiff.dropped.size() > 0 || viewsDiff.created.size() > 0 || viewsDiff.dropped.size() > 0; + boolean hasCreatedOrDroppedIndexes = !indexesDiff.entriesOnlyOnRight().isEmpty() || !indexesDiff.entriesOnlyOnLeft().isEmpty(); + + Map metadataRefs = hasCreatedOrDroppedTablesOrViews ? Maps.newHashMap(this.metadataRefs) : this.metadataRefs; + Map, TableMetadataRef> metadataRefsByName = hasCreatedOrDroppedTablesOrViews ? Maps.newHashMap(this.metadataRefsByName) : this.metadataRefsByName; + Map, TableMetadataRef> indexMetadataRefs = hasCreatedOrDroppedIndexes ? Maps.newHashMap(this.indexMetadataRefs) : this.indexMetadataRefs; + + // clean up after removed entries + tablesDiff.dropped.forEach(ref -> removeRef(metadataRefs, metadataRefsByName, ref)); + viewsDiff.dropped.forEach(view -> removeRef(metadataRefs, metadataRefsByName, view.metadata)); + indexesDiff.entriesOnlyOnLeft() + .values() + .forEach(indexTable -> indexMetadataRefs.remove(Pair.create(indexTable.keyspace, indexTable.indexName().get()))); + + // load up new entries + tablesDiff.created.forEach(table -> putRef(metadataRefs, metadataRefsByName, new TableMetadataRef(table))); + viewsDiff.created.forEach(view -> putRef(metadataRefs, metadataRefsByName, new TableMetadataRef(view.metadata))); + indexesDiff.entriesOnlyOnRight() + .values() + .forEach(indexTable -> indexMetadataRefs.put(Pair.create(indexTable.keyspace, indexTable.indexName().get()), new TableMetadataRef(indexTable))); + + // refresh refs to updated ones + tablesDiff.altered.forEach(diff -> metadataRefs.get(diff.after.id).set(diff.after)); + viewsDiff.altered.forEach(diff -> metadataRefs.get(diff.after.metadata.id).set(diff.after.metadata)); + indexesDiff.entriesDiffering() + .values() + .stream() + .map(MapDifference.ValueDifference::rightValue) + .forEach(indexTable -> indexMetadataRefs.get(Pair.create(indexTable.keyspace, indexTable.indexName().get())).set(indexTable)); + + return new TableMetadataRefCache(metadataRefs, metadataRefsByName, indexMetadataRefs); + } + + private void putRef(Map metadataRefs, + Map, TableMetadataRef> metadataRefsByName, + TableMetadataRef ref) + { + metadataRefs.put(ref.id, ref); + metadataRefsByName.put(Pair.create(ref.keyspace, ref.name), ref); + } + + private void removeRef(Map metadataRefs, + Map, TableMetadataRef> metadataRefsByName, + TableMetadata tm) + { + metadataRefs.remove(tm.id); + metadataRefsByName.remove(Pair.create(tm.keyspace, tm.name)); + } + + /** + * Returns cache copy with removed the {@link TableMetadataRef} from {@link #metadataRefs} and {@link #indexMetadataRefs} + * for the provided (dropped) keyspace. + */ + TableMetadataRefCache withRemovedRefs(KeyspaceMetadata ksm) + { + return withUpdatedRefs(ksm, ksm.empty()); + } + + public TableMetadataRef getTableMetadataRef(TableId id) + { + return metadataRefs.get(id); + } + + public TableMetadataRef getTableMetadataRef(String keyspace, String table) + { + return metadataRefsByName.get(Pair.create(keyspace, table)); + } + + public TableMetadataRef getIndexTableMetadataRef(String keyspace, String index) + { + return indexMetadataRefs.get(Pair.create(keyspace, index)); + } +} diff --git a/src/java/org/apache/cassandra/schema/Types.java b/src/java/org/apache/cassandra/schema/Types.java index 76694ccb39..0d264c4f49 100644 --- a/src/java/org/apache/cassandra/schema/Types.java +++ b/src/java/org/apache/cassandra/schema/Types.java @@ -109,6 +109,11 @@ public final class Types implements Iterable return Iterables.filter(types.values(), t -> t.referencesUserType(name) && !t.name.equals(name)); } + public boolean isEmpty() + { + return types.isEmpty(); + } + /** * Get the type with the specified name * diff --git a/src/java/org/apache/cassandra/service/CassandraDaemon.java b/src/java/org/apache/cassandra/service/CassandraDaemon.java index 7e9c208d5e..7f0efec5ab 100644 --- a/src/java/org/apache/cassandra/service/CassandraDaemon.java +++ b/src/java/org/apache/cassandra/service/CassandraDaemon.java @@ -372,8 +372,6 @@ public class CassandraDaemon throw new RuntimeException(e); } - SystemKeyspace.finishStartup(); - // Clean up system.size_estimates entries left lying around from missed keyspace drops (CASSANDRA-14905) StorageService.instance.cleanupSizeEstimates(); @@ -441,7 +439,7 @@ public class CassandraDaemon logger.debug("Completed submission of build tasks for any materialized views defined at startup"); }; - ScheduledExecutors.optionalTasks.schedule(viewRebuild, StorageService.RING_DELAY, TimeUnit.MILLISECONDS); + ScheduledExecutors.optionalTasks.schedule(viewRebuild, StorageService.RING_DELAY_MILLIS, TimeUnit.MILLISECONDS); if (!FBUtilities.getBroadcastAddressAndPort().equals(InetAddressAndPort.getLoopbackAddress())) Gossiper.waitToSettle(); diff --git a/src/java/org/apache/cassandra/service/PendingRangeCalculatorService.java b/src/java/org/apache/cassandra/service/PendingRangeCalculatorService.java index ad0e0e0b0f..837bdd882e 100644 --- a/src/java/org/apache/cassandra/service/PendingRangeCalculatorService.java +++ b/src/java/org/apache/cassandra/service/PendingRangeCalculatorService.java @@ -18,7 +18,7 @@ package org.apache.cassandra.service; -import java.util.List; +import java.util.Collection; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -53,7 +53,7 @@ public class PendingRangeCalculatorService private final AtLeastOnceTrigger update = executor.atLeastOnceTrigger(() -> { PendingRangeCalculatorServiceDiagnostics.taskStarted(1); long start = currentTimeMillis(); - List keyspaces = Schema.instance.getNonLocalStrategyKeyspaces(); + Collection keyspaces = Schema.instance.getNonLocalStrategyKeyspaces().names(); for (String keyspaceName : keyspaces) calculatePendingRanges(Keyspace.open(keyspaceName).getReplicationStrategy(), keyspaceName); if (logger.isTraceEnabled()) diff --git a/src/java/org/apache/cassandra/service/StorageService.java b/src/java/org/apache/cassandra/service/StorageService.java index 83fc2d8c0e..0f5675a194 100644 --- a/src/java/org/apache/cassandra/service/StorageService.java +++ b/src/java/org/apache/cassandra/service/StorageService.java @@ -71,6 +71,7 @@ import org.apache.cassandra.fql.FullQueryLoggerOptions; import org.apache.cassandra.fql.FullQueryLoggerOptionsCompositeData; import org.apache.cassandra.io.util.File; import org.apache.cassandra.locator.ReplicaCollection.Builder.Conflict; +import org.apache.cassandra.schema.Keyspaces; import org.apache.cassandra.utils.concurrent.Future; import org.apache.cassandra.schema.TableId; import org.apache.cassandra.utils.concurrent.Future; @@ -126,11 +127,10 @@ import org.apache.cassandra.repair.*; import org.apache.cassandra.repair.messages.RepairOption; import org.apache.cassandra.schema.CompactionParams.TombstoneOption; import org.apache.cassandra.schema.KeyspaceMetadata; -import org.apache.cassandra.schema.MigrationCoordinator; -import org.apache.cassandra.schema.MigrationManager; import org.apache.cassandra.schema.ReplicationParams; import org.apache.cassandra.schema.Schema; import org.apache.cassandra.schema.SchemaConstants; +import org.apache.cassandra.schema.SchemaTransformations; import org.apache.cassandra.schema.SystemDistributedKeyspace; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.schema.TableMetadataRef; @@ -172,7 +172,6 @@ import static org.apache.cassandra.index.SecondaryIndexManager.getIndexName; import static org.apache.cassandra.index.SecondaryIndexManager.isIndexColumnFamily; import static org.apache.cassandra.net.NoPayload.noPayload; import static org.apache.cassandra.net.Verb.REPLICATION_DONE_REQ; -import static org.apache.cassandra.schema.MigrationManager.evolveSystemKeyspace; import static org.apache.cassandra.service.ActiveRepairService.*; import static org.apache.cassandra.utils.Clock.Global.currentTimeMillis; import static org.apache.cassandra.utils.Clock.Global.nanoTime; @@ -190,7 +189,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE private static final Logger logger = LoggerFactory.getLogger(StorageService.class); public static final int INDEFINITE = -1; - public static final int RING_DELAY = getRingDelay(); // delay after which we assume ring has stablized + public static final int RING_DELAY_MILLIS = getRingDelay(); // delay after which we assume ring has stablized public static final int SCHEMA_DELAY_MILLIS = getSchemaDelay(); private static final boolean REQUIRE_SCHEMAS = !BOOTSTRAP_SKIP_SCHEMA_CHECK.getBoolean(); @@ -321,7 +320,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE /* the probability for tracing any particular request, 0 disables tracing and 1 enables for all */ private double traceProbability = 0.0; - private static enum Mode { STARTING, NORMAL, JOINING, LEAVING, DECOMMISSIONED, MOVING, DRAINING, DRAINED } + private enum Mode { STARTING, NORMAL, JOINING, LEAVING, DECOMMISSIONED, MOVING, DRAINING, DRAINED } private volatile Mode operationMode = Mode.STARTING; /* Used for tracking drain progress */ @@ -784,10 +783,15 @@ public class StorageService extends NotificationBroadcasterSupport implements IE public synchronized void initServer() throws ConfigurationException { - initServer(RING_DELAY); + initServer(SCHEMA_DELAY_MILLIS, RING_DELAY_MILLIS); } - public synchronized void initServer(int delay) throws ConfigurationException + public synchronized void initServer(int schemaAndRingDelayMillis) throws ConfigurationException + { + initServer(schemaAndRingDelayMillis, RING_DELAY_MILLIS); + } + + public synchronized void initServer(int schemaTimeoutMillis, int ringTimeoutMillis) throws ConfigurationException { logger.info("Cassandra version: {}", FBUtilities.getReleaseVersionString()); logger.info("CQL version: {}", QueryProcessor.CQL_VERSION); @@ -850,7 +854,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE if (joinRing) { - joinTokenRing(delay); + joinTokenRing(schemaTimeoutMillis, ringTimeoutMillis); } else { @@ -906,13 +910,17 @@ public class StorageService extends NotificationBroadcasterSupport implements IE tokenMetadata.updateHostIds(hostIdToEndpointMap); } - private boolean isReplacing() + public boolean isReplacing() { + if (replacing) + return true; + if (System.getProperty("cassandra.replace_address_first_boot", null) != null && SystemKeyspace.bootstrapComplete()) { logger.info("Replace address on first boot requested; this node is already bootstrapped"); return false; } + return DatabaseDescriptor.getReplaceAddress() != null; } @@ -939,7 +947,6 @@ public class StorageService extends NotificationBroadcasterSupport implements IE private void prepareToJoin() throws ConfigurationException { - MigrationCoordinator.instance.start(); if (!joined) { Map appStates = new EnumMap<>(ApplicationState.class); @@ -983,7 +990,6 @@ public class StorageService extends NotificationBroadcasterSupport implements IE appStates.put(ApplicationState.STATUS_WITH_PORT, valueFactory.hibernate(true)); appStates.put(ApplicationState.STATUS, valueFactory.hibernate(true)); } - MigrationCoordinator.instance.removeAndIgnoreEndpoint(DatabaseDescriptor.getReplaceAddress()); } else { @@ -1029,8 +1035,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE // gossip snitch infos (local DC and rack) gossipSnitchInfo(); - // gossip Schema.emptyVersion forcing immediate check for schema updates (see MigrationManager#maybeScheduleSchemaPull) - Schema.instance.updateVersionAndAnnounce(); // Ensure we know our own actual Schema UUID in preparation for updates + Schema.instance.startSync(); LoadBroadcaster.instance.startBroadcasting(); HintsService.instance.startDispatch(); BatchlogManager.instance.start(); @@ -1038,47 +1043,28 @@ public class StorageService extends NotificationBroadcasterSupport implements IE } } - public void waitForSchema(long delay) + public void waitForSchema(long schemaTimeoutMillis, long ringTimeoutMillis) { - // first sleep the delay to make sure we see all our peers - for (long i = 0; i < delay; i += 1000) - { - // if we see schema, we can proceed to the next check directly - if (!Schema.instance.isEmpty()) - { - logger.debug("current schema version: {}", Schema.instance.getVersion()); - break; - } + Instant deadline = FBUtilities.now().plus(java.time.Duration.ofMillis(ringTimeoutMillis)); + + while (Schema.instance.isEmpty() && FBUtilities.now().isBefore(deadline)) Uninterruptibles.sleepUninterruptibly(1, TimeUnit.SECONDS); - } - boolean schemasReceived = MigrationCoordinator.instance.awaitSchemaRequests(SCHEMA_DELAY_MILLIS); - - if (schemasReceived) - return; - - logger.warn(String.format("There are nodes in the cluster with a different schema version than us we did not merged schemas from, " + - "our version : (%s), outstanding versions -> endpoints : %s. Use -Dcassandra.skip_schema_check=true " + - "to ignore this, -Dcassandra.skip_schema_check_for_endpoints= to skip specific endpoints," + - "or -Dcassandra.skip_schema_check_for_versions= to skip specific schema versions", - Schema.instance.getVersion(), - MigrationCoordinator.instance.outstandingVersions())); - - if (REQUIRE_SCHEMAS) - throw new RuntimeException("Didn't receive schemas for all known versions within the timeout. " + - "Use -Dcassandra.skip_schema_check=true to skip this check."); + if (!Schema.instance.waitUntilReady(java.time.Duration.ofMillis(schemaTimeoutMillis))) + throw new IllegalStateException("Could not achieve schema readiness in " + java.time.Duration.ofMillis(schemaTimeoutMillis)); } - private void joinTokenRing(long schemaTimeoutMillis) throws ConfigurationException + private void joinTokenRing(long schemaTimeoutMillis, long ringTimeoutMillis) throws ConfigurationException { - joinTokenRing(!isSurveyMode, shouldBootstrap(), schemaTimeoutMillis, INDEFINITE); + joinTokenRing(!isSurveyMode, shouldBootstrap(), schemaTimeoutMillis, INDEFINITE, ringTimeoutMillis); } @VisibleForTesting public void joinTokenRing(boolean finishJoiningRing, boolean shouldBootstrap, long schemaTimeoutMillis, - long bootstrapTimeoutMillis) throws ConfigurationException + long bootstrapTimeoutMillis, + long ringTimeoutMillis) throws ConfigurationException { joined = true; @@ -1109,7 +1095,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE if (shouldBootstrap) { - current.addAll(prepareForBootstrap(schemaTimeoutMillis)); + current.addAll(prepareForBootstrap(schemaTimeoutMillis, ringTimeoutMillis)); dataAvailable = bootstrap(bootstrapTokens, bootstrapTimeoutMillis); } else @@ -1117,7 +1103,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE bootstrapTokens = SystemKeyspace.getSavedTokens(); if (bootstrapTokens.isEmpty()) { - bootstrapTokens = BootStrapper.getBootstrapTokens(tokenMetadata, FBUtilities.getBroadcastAddressAndPort(), schemaTimeoutMillis); + bootstrapTokens = BootStrapper.getBootstrapTokens(tokenMetadata, FBUtilities.getBroadcastAddressAndPort(), schemaTimeoutMillis, ringTimeoutMillis); } else { @@ -1188,7 +1174,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE logger.info("Joining ring by operator request"); try { - joinTokenRing(0); + joinTokenRing(SCHEMA_DELAY_MILLIS, 0); doAuthSetup(false); } catch (ConfigurationException e) @@ -1223,7 +1209,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE private void executePreJoinTasks(boolean bootstrap) { StreamSupport.stream(ColumnFamilyStore.all().spliterator(), false) - .filter(cfs -> Schema.instance.getUserKeyspaces().contains(cfs.keyspace.getName())) + .filter(cfs -> Schema.instance.getUserKeyspaces().names().contains(cfs.keyspace.getName())) .forEach(cfs -> cfs.indexManager.executePreJoinTasksBlocking(bootstrap)); } @@ -1246,8 +1232,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE { if (setUpSchema) { - Optional mutation = evolveSystemKeyspace(AuthKeyspace.metadata(), AuthKeyspace.GENERATION); - mutation.ifPresent(value -> FBUtilities.waitOnFuture(MigrationManager.announceWithoutPush(Collections.singleton(value)))); + Schema.instance.transform(SchemaTransformations.updateSystemKeyspace(AuthKeyspace.metadata(), AuthKeyspace.GENERATION)); } DatabaseDescriptor.getRoleManager().setup(); @@ -1275,14 +1260,9 @@ public class StorageService extends NotificationBroadcasterSupport implements IE @VisibleForTesting public void setUpDistributedSystemKeyspaces() { - Collection changes = new ArrayList<>(3); - - evolveSystemKeyspace( TraceKeyspace.metadata(), TraceKeyspace.GENERATION).ifPresent(changes::add); - evolveSystemKeyspace(SystemDistributedKeyspace.metadata(), SystemDistributedKeyspace.GENERATION).ifPresent(changes::add); - evolveSystemKeyspace( AuthKeyspace.metadata(), AuthKeyspace.GENERATION).ifPresent(changes::add); - - if (!changes.isEmpty()) - FBUtilities.waitOnFuture(MigrationManager.announceWithoutPush(changes)); + Schema.instance.transform(SchemaTransformations.updateSystemKeyspace(TraceKeyspace.metadata(), TraceKeyspace.GENERATION)); + Schema.instance.transform(SchemaTransformations.updateSystemKeyspace(SystemDistributedKeyspace.metadata(), SystemDistributedKeyspace.GENERATION)); + Schema.instance.transform(SchemaTransformations.updateSystemKeyspace(AuthKeyspace.metadata(), AuthKeyspace.GENERATION)); } public boolean isJoined() @@ -1331,7 +1311,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE if (keyspace == null) { - for (String keyspaceName : Schema.instance.getNonLocalStrategyKeyspaces()) + for (String keyspaceName : Schema.instance.getNonLocalStrategyKeyspaces().names()) streamer.addRanges(keyspaceName, getLocalReplicas(keyspaceName)); } else if (tokens == null) @@ -1751,7 +1731,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE } @VisibleForTesting - public Collection prepareForBootstrap(long schemaDelay) + public Collection prepareForBootstrap(long schemaTimeoutMillis, long ringTimeoutMillis) { Set collisions = new HashSet<>(); if (SystemKeyspace.bootstrapInProgress()) @@ -1759,7 +1739,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE else SystemKeyspace.setBootstrapState(SystemKeyspace.BootstrapState.IN_PROGRESS); setMode(Mode.JOINING, "waiting for ring information", true); - waitForSchema(schemaDelay); + waitForSchema(schemaTimeoutMillis, ringTimeoutMillis); setMode(Mode.JOINING, "schema complete, ready to bootstrap", true); setMode(Mode.JOINING, "waiting for pending range calculation", true); PendingRangeCalculatorService.instance.blockUntilFinished(); @@ -1789,7 +1769,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE throw new UnsupportedOperationException(s); } setMode(Mode.JOINING, "getting bootstrap token", true); - bootstrapTokens = BootStrapper.getBootstrapTokens(tokenMetadata, FBUtilities.getBroadcastAddressAndPort(), schemaDelay); + bootstrapTokens = BootStrapper.getBootstrapTokens(tokenMetadata, FBUtilities.getBroadcastAddressAndPort(), schemaTimeoutMillis, ringTimeoutMillis); } else { @@ -1812,7 +1792,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE InetAddressAndPort existing = tokenMetadata.getEndpoint(token); if (existing != null) { - long nanoDelay = schemaDelay * 1000000L; + long nanoDelay = ringTimeoutMillis * 1000000L; if (Gossiper.instance.getEndpointStateForEndpoint(existing).getUpdateTimestamp() > (nanoTime() - nanoDelay)) throw new UnsupportedOperationException("Cannot replace a live node... "); collisions.add(existing); @@ -1827,7 +1807,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE { try { - Thread.sleep(RING_DELAY); + Thread.sleep(RING_DELAY_MILLIS); } catch (InterruptedException e) { @@ -1867,8 +1847,8 @@ public class StorageService extends NotificationBroadcasterSupport implements IE valueFactory.bootReplacing(DatabaseDescriptor.getReplaceAddress().getAddress()) : valueFactory.bootstrapping(tokens))); Gossiper.instance.addLocalApplicationStates(states); - setMode(Mode.JOINING, "sleeping " + RING_DELAY + " ms for pending range setup", true); - Uninterruptibles.sleepUninterruptibly(RING_DELAY, MILLISECONDS); + setMode(Mode.JOINING, "sleeping " + RING_DELAY_MILLIS + " ms for pending range setup", true); + Uninterruptibles.sleepUninterruptibly(RING_DELAY_MILLIS, MILLISECONDS); } else { @@ -1938,7 +1918,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE * All MVs have been created during bootstrap, so mark them as built */ private void markViewsAsBuilt() { - for (String keyspace : Schema.instance.getUserKeyspaces()) + for (String keyspace : Schema.instance.getUserKeyspaces().names()) { for (ViewMetadata view: Schema.instance.getKeyspaceMetadata(keyspace).views) SystemKeyspace.finishViewBuildStatus(view.keyspace(), view.name()); @@ -2147,7 +2127,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE // some people just want to get a visual representation of things. Allow null and set it to the first // non-system keyspace. if (keyspace == null) - keyspace = Schema.instance.getNonLocalStrategyKeyspaces().get(0); + keyspace = Schema.instance.getNonLocalStrategyKeyspaces().iterator().next().name; Map, List> map = new HashMap<>(); for (Map.Entry, EndpointsForRange> entry : tokenMetadata.getPendingRangesMM(keyspace).asMap().entrySet()) @@ -2201,7 +2181,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE // some people just want to get a visual representation of things. Allow null and set it to the first // non-system keyspace. if (keyspace == null) - keyspace = Schema.instance.getNonLocalStrategyKeyspaces().get(0); + keyspace = Schema.instance.getNonLocalStrategyKeyspaces().iterator().next().name; List> ranges = getAllRanges(sortedTokens); return constructRangeToEndpointMap(keyspace, ranges); @@ -2501,7 +2481,6 @@ public class StorageService extends NotificationBroadcasterSupport implements IE break; case SCHEMA: SystemKeyspace.updatePeerInfo(endpoint, "schema_version", UUID.fromString(value.value)); - MigrationCoordinator.instance.reportEndpointVersion(endpoint, UUID.fromString(value.value)); break; case HOST_ID: SystemKeyspace.updatePeerInfo(endpoint, "host_id", UUID.fromString(value.value)); @@ -3131,7 +3110,6 @@ public class StorageService extends NotificationBroadcasterSupport implements IE private void removeEndpoint(InetAddressAndPort endpoint) { Gossiper.runInGossipStageBlocking(() -> Gossiper.instance.removeEndpoint(endpoint)); - MigrationCoordinator.instance.removeAndIgnoreEndpoint(endpoint); SystemKeyspace.removeEndpoint(endpoint); } @@ -3293,7 +3271,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE InetAddressAndPort myAddress = FBUtilities.getBroadcastAddressAndPort(); - for (String keyspaceName : Schema.instance.getNonLocalStrategyKeyspaces()) + for (String keyspaceName : Schema.instance.getNonLocalStrategyKeyspaces().names()) { logger.debug("Restoring replica count for keyspace {}", keyspaceName); EndpointsByReplica changedReplicas = getChangedReplicasForLeaving(keyspaceName, endpoint, tokenMetadata, Keyspace.open(keyspaceName).getReplicationStrategy()); @@ -3445,13 +3423,13 @@ public class StorageService extends NotificationBroadcasterSupport implements IE statusValue = epState.getApplicationState(statusState); } if (statusValue != null) - onChange(endpoint, statusState, statusValue); + Gossiper.instance.doOnChangeNotifications(endpoint, statusState, statusValue); for (Map.Entry entry : epState.states()) { if (entry.getKey() == ApplicationState.STATUS_WITH_PORT || entry.getKey() == ApplicationState.STATUS) continue; - onChange(endpoint, entry.getKey(), entry.getValue()); + Gossiper.instance.doOnChangeNotifications(endpoint, entry.getKey(), entry.getValue()); } } @@ -4389,8 +4367,8 @@ public class StorageService extends NotificationBroadcasterSupport implements IE List> futures = new ArrayList<>(); - List keyspaces = Schema.instance.getNonLocalStrategyKeyspaces() ; - for (String ksName : keyspaces) + Keyspaces keyspaces = Schema.instance.getNonLocalStrategyKeyspaces(); + for (String ksName : keyspaces.names()) { if (SchemaConstants.REPLICATED_SYSTEM_KEYSPACE_NAMES.contains(ksName)) continue; @@ -4722,7 +4700,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE if (operationMode != Mode.LEAVING) // If we're already decommissioning there is no point checking RF/pending ranges { int rf, numNodes; - for (String keyspaceName : Schema.instance.getNonLocalStrategyKeyspaces()) + for (String keyspaceName : Schema.instance.getNonLocalStrategyKeyspaces().names()) { if (!force) { @@ -4751,7 +4729,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE } startLeaving(); - long timeout = Math.max(RING_DELAY, BatchlogManager.instance.getBatchlogTimeout()); + long timeout = Math.max(RING_DELAY_MILLIS, BatchlogManager.instance.getBatchlogTimeout()); setMode(Mode.LEAVING, "sleeping " + timeout + " ms for batch processing and pending range setup", true); Thread.sleep(timeout); @@ -4801,7 +4779,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE Gossiper.instance.addLocalApplicationState(ApplicationState.STATUS_WITH_PORT, valueFactory.left(getLocalTokens(),Gossiper.computeExpireTime())); Gossiper.instance.addLocalApplicationState(ApplicationState.STATUS, valueFactory.left(getLocalTokens(),Gossiper.computeExpireTime())); - int delay = Math.max(RING_DELAY, Gossiper.intervalInMillis * 2); + int delay = Math.max(RING_DELAY_MILLIS, Gossiper.intervalInMillis * 2); logger.info("Announcing that I have left the ring for {}ms", delay); Uninterruptibles.sleepUninterruptibly(delay, MILLISECONDS); } @@ -4810,7 +4788,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE { Map rangesToStream = new HashMap<>(); - for (String keyspaceName : Schema.instance.getNonLocalStrategyKeyspaces()) + for (String keyspaceName : Schema.instance.getNonLocalStrategyKeyspaces().names()) { EndpointsByReplica rangesMM = getChangedReplicasForLeaving(keyspaceName, FBUtilities.getBroadcastAddressAndPort(), tokenMetadata, Keyspace.open(keyspaceName).getReplicationStrategy()); @@ -4924,7 +4902,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE throw new UnsupportedOperationException("This node has more than one token and cannot be moved thusly."); } - List keyspacesToProcess = Schema.instance.getNonLocalStrategyKeyspaces(); + List keyspacesToProcess = ImmutableList.copyOf(Schema.instance.getNonLocalStrategyKeyspaces().names()); PendingRangeCalculatorService.instance.blockUntilFinished(); // checking if data is moving to this node @@ -4939,8 +4917,8 @@ public class StorageService extends NotificationBroadcasterSupport implements IE Gossiper.instance.addLocalApplicationState(ApplicationState.STATUS, valueFactory.moving(newToken)); setMode(Mode.MOVING, String.format("Moving %s from %s to %s.", localAddress, getLocalTokens().iterator().next(), newToken), true); - setMode(Mode.MOVING, String.format("Sleeping %s ms before start streaming/fetching ranges", RING_DELAY), true); - Uninterruptibles.sleepUninterruptibly(RING_DELAY, MILLISECONDS); + setMode(Mode.MOVING, String.format("Sleeping %s ms before start streaming/fetching ranges", RING_DELAY_MILLIS), true); + Uninterruptibles.sleepUninterruptibly(RING_DELAY_MILLIS, MILLISECONDS); RangeRelocator relocator = new RangeRelocator(Collections.singleton(newToken), keyspacesToProcess, tokenMetadata); relocator.calculateToFromStreams(); @@ -5071,7 +5049,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE Collection tokens = tokenMetadata.getTokens(endpoint); // Find the endpoints that are going to become responsible for data - for (String keyspaceName : Schema.instance.getNonLocalStrategyKeyspaces()) + for (String keyspaceName : Schema.instance.getNonLocalStrategyKeyspaces().names()) { // if the replication factor is 1 the data is lost so we shouldn't wait for confirmation if (Keyspace.open(keyspaceName).getReplicationStrategy().getReplicationFactor().allReplicas == 1) @@ -5507,11 +5485,11 @@ public class StorageService extends NotificationBroadcasterSupport implements IE } else { - List userKeyspaces = Schema.instance.getUserKeyspaces(); + Collection userKeyspaces = Schema.instance.getUserKeyspaces().names(); if (userKeyspaces.size() > 0) { - keyspace = userKeyspaces.get(0); + keyspace = userKeyspaces.iterator().next(); AbstractReplicationStrategy replicationStrategy = Schema.instance.getKeyspaceInstance(keyspace).getReplicationStrategy(); for (String keyspaceName : userKeyspaces) { @@ -5578,19 +5556,17 @@ public class StorageService extends NotificationBroadcasterSupport implements IE public List getKeyspaces() { - List keyspaceNamesList = new ArrayList<>(Schema.instance.getKeyspaces()); - return Collections.unmodifiableList(keyspaceNamesList); + return Lists.newArrayList(Schema.instance.distributedAndLocalKeyspaces().names()); } public List getNonSystemKeyspaces() { - List nonKeyspaceNamesList = new ArrayList<>(Schema.instance.getNonSystemKeyspaces()); - return Collections.unmodifiableList(nonKeyspaceNamesList); + return Lists.newArrayList(Schema.instance.distributedKeyspaces().names()); } public List getNonLocalStrategyKeyspaces() { - return Collections.unmodifiableList(Schema.instance.getNonLocalStrategyKeyspaces()); + return Lists.newArrayList(Schema.instance.getNonLocalStrategyKeyspaces().names()); } public Map getViewBuildStatuses(String keyspace, String view, boolean withPort) @@ -5911,7 +5887,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE public void resetLocalSchema() throws IOException { - MigrationManager.resetLocalSchema(); + Schema.instance.resetLocalSchema(); } public void reloadLocalSchema() @@ -6299,7 +6275,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE @Override public Map> getOutstandingSchemaVersions() { - Map> outstanding = MigrationCoordinator.instance.outstandingVersions(); + Map> outstanding = Schema.instance.getOutstandingSchemaVersions(); return outstanding.entrySet().stream().collect(Collectors.toMap(e -> e.getKey().toString(), e -> e.getValue().stream().map(InetSocketAddress::getAddress).collect(Collectors.toSet()))); } @@ -6307,7 +6283,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE @Override public Map> getOutstandingSchemaVersionsWithPort() { - Map> outstanding = MigrationCoordinator.instance.outstandingVersions(); + Map> outstanding = Schema.instance.getOutstandingSchemaVersions(); return outstanding.entrySet().stream().collect(Collectors.toMap(e -> e.getKey().toString(), e -> e.getValue().stream().map(Object::toString).collect(Collectors.toSet()))); } @@ -6686,7 +6662,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE logger.info("StorageService#clearPaxosRepairs called via jmx"); PaxosTableRepairs.clearRepairs(); } - + public void setSkipPaxosRepairCompatibilityCheck(boolean v) { PaxosRepair.setSkipPaxosRepairCompatibilityCheck(v); diff --git a/src/java/org/apache/cassandra/tools/SSTableExpiredBlockers.java b/src/java/org/apache/cassandra/tools/SSTableExpiredBlockers.java index 62a018c78b..f5d24ed7ea 100644 --- a/src/java/org/apache/cassandra/tools/SSTableExpiredBlockers.java +++ b/src/java/org/apache/cassandra/tools/SSTableExpiredBlockers.java @@ -60,7 +60,7 @@ public class SSTableExpiredBlockers String keyspace = args[args.length - 2]; String columnfamily = args[args.length - 1]; - Schema.instance.loadFromDisk(false); + Schema.instance.loadFromDisk(); TableMetadata metadata = Schema.instance.validateTable(keyspace, columnfamily); diff --git a/src/java/org/apache/cassandra/tools/SSTableLevelResetter.java b/src/java/org/apache/cassandra/tools/SSTableLevelResetter.java index 4340e8eaa1..9618d3621f 100644 --- a/src/java/org/apache/cassandra/tools/SSTableLevelResetter.java +++ b/src/java/org/apache/cassandra/tools/SSTableLevelResetter.java @@ -65,7 +65,7 @@ public class SSTableLevelResetter try { // load keyspace descriptions. - Schema.instance.loadFromDisk(false); + Schema.instance.loadFromDisk(); String keyspaceName = args[1]; String columnfamily = args[2]; diff --git a/src/java/org/apache/cassandra/tools/SSTableOfflineRelevel.java b/src/java/org/apache/cassandra/tools/SSTableOfflineRelevel.java index 72c1e99bcc..e4e343fe51 100644 --- a/src/java/org/apache/cassandra/tools/SSTableOfflineRelevel.java +++ b/src/java/org/apache/cassandra/tools/SSTableOfflineRelevel.java @@ -91,7 +91,7 @@ public class SSTableOfflineRelevel boolean dryRun = args[0].equals("--dry-run"); String keyspace = args[args.length - 2]; String columnfamily = args[args.length - 1]; - Schema.instance.loadFromDisk(false); + Schema.instance.loadFromDisk(); if (Schema.instance.getTableMetadataRef(keyspace, columnfamily) == null) throw new IllegalArgumentException(String.format("Unknown keyspace/table %s.%s", diff --git a/src/java/org/apache/cassandra/tools/StandaloneSSTableUtil.java b/src/java/org/apache/cassandra/tools/StandaloneSSTableUtil.java index 06618b39b4..4d2acd20d4 100644 --- a/src/java/org/apache/cassandra/tools/StandaloneSSTableUtil.java +++ b/src/java/org/apache/cassandra/tools/StandaloneSSTableUtil.java @@ -48,7 +48,7 @@ public class StandaloneSSTableUtil { // load keyspace descriptions. Util.initDatabaseDescriptor(); - Schema.instance.loadFromDisk(false); + Schema.instance.loadFromDisk(); TableMetadata metadata = Schema.instance.getTableMetadata(options.keyspaceName, options.cfName); if (metadata == null) diff --git a/src/java/org/apache/cassandra/tools/StandaloneScrubber.java b/src/java/org/apache/cassandra/tools/StandaloneScrubber.java index b1f8e6c4b0..d6e99b9c2f 100644 --- a/src/java/org/apache/cassandra/tools/StandaloneScrubber.java +++ b/src/java/org/apache/cassandra/tools/StandaloneScrubber.java @@ -87,7 +87,7 @@ public class StandaloneScrubber try { // load keyspace descriptions. - Schema.instance.loadFromDisk(false); + Schema.instance.loadFromDisk(); if (Schema.instance.getKeyspaceMetadata(options.keyspaceName) == null) throw new IllegalArgumentException(String.format("Unknown keyspace %s", options.keyspaceName)); diff --git a/src/java/org/apache/cassandra/tools/StandaloneSplitter.java b/src/java/org/apache/cassandra/tools/StandaloneSplitter.java index cd3affa755..efadb56e0c 100644 --- a/src/java/org/apache/cassandra/tools/StandaloneSplitter.java +++ b/src/java/org/apache/cassandra/tools/StandaloneSplitter.java @@ -61,7 +61,7 @@ public class StandaloneSplitter try { // load keyspace descriptions. - Schema.instance.loadFromDisk(false); + Schema.instance.loadFromDisk(); String ksName = null; String cfName = null; diff --git a/src/java/org/apache/cassandra/tools/StandaloneUpgrader.java b/src/java/org/apache/cassandra/tools/StandaloneUpgrader.java index 323dab142c..442a95b0c7 100644 --- a/src/java/org/apache/cassandra/tools/StandaloneUpgrader.java +++ b/src/java/org/apache/cassandra/tools/StandaloneUpgrader.java @@ -57,7 +57,7 @@ public class StandaloneUpgrader try { // load keyspace descriptions. - Schema.instance.loadFromDisk(false); + Schema.instance.loadFromDisk(); if (Schema.instance.getTableMetadataRef(options.keyspace, options.cf) == null) throw new IllegalArgumentException(String.format("Unknown keyspace/table %s.%s", diff --git a/src/java/org/apache/cassandra/tools/StandaloneVerifier.java b/src/java/org/apache/cassandra/tools/StandaloneVerifier.java index df276bd470..17b93d1aed 100644 --- a/src/java/org/apache/cassandra/tools/StandaloneVerifier.java +++ b/src/java/org/apache/cassandra/tools/StandaloneVerifier.java @@ -74,7 +74,7 @@ public class StandaloneVerifier try { // load keyspace descriptions. - Schema.instance.loadFromDisk(false); + Schema.instance.loadFromDisk(); boolean hasFailed = false; diff --git a/src/java/org/apache/cassandra/tools/nodetool/stats/TableStatsHolder.java b/src/java/org/apache/cassandra/tools/nodetool/stats/TableStatsHolder.java index 11dea9c85d..9107007439 100644 --- a/src/java/org/apache/cassandra/tools/nodetool/stats/TableStatsHolder.java +++ b/src/java/org/apache/cassandra/tools/nodetool/stats/TableStatsHolder.java @@ -457,7 +457,7 @@ public class TableStatsHolder implements StatsHolder return filter.get(keyspace) != null || ignoreMode; } - public void verifyKeyspaces(List keyspaces) + public void verifyKeyspaces(Collection keyspaces) { for (String ks : verifier.keySet()) if (!keyspaces.contains(ks)) diff --git a/src/java/org/apache/cassandra/transport/Server.java b/src/java/org/apache/cassandra/transport/Server.java index bf3f3e48ca..ac1af4e5db 100644 --- a/src/java/org/apache/cassandra/transport/Server.java +++ b/src/java/org/apache/cassandra/transport/Server.java @@ -41,10 +41,15 @@ import io.netty.util.internal.logging.Slf4JLoggerFactory; import org.apache.cassandra.auth.AuthenticatedUser; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.config.EncryptionOptions; +import org.apache.cassandra.cql3.functions.UDAggregate; +import org.apache.cassandra.cql3.functions.UDFunction; import org.apache.cassandra.db.marshal.AbstractType; +import org.apache.cassandra.db.marshal.UserType; import org.apache.cassandra.locator.InetAddressAndPort; +import org.apache.cassandra.schema.KeyspaceMetadata; import org.apache.cassandra.schema.Schema; import org.apache.cassandra.schema.SchemaChangeListener; +import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.service.*; import org.apache.cassandra.transport.messages.EventMessage; import org.apache.cassandra.utils.FBUtilities; @@ -354,7 +359,7 @@ public class Server implements CassandraDaemon.Server } } - public static class EventNotifier extends SchemaChangeListener implements IEndpointLifecycleSubscriber + public static class EventNotifier implements SchemaChangeListener, IEndpointLifecycleSubscriber { private ConnectionTracker connectionTracker; @@ -407,6 +412,7 @@ public class Server implements CassandraDaemon.Server connectionTracker.send(event); } + @Override public void onJoinCluster(InetAddressAndPort endpoint) { if (!StorageService.instance.isRpcReady(endpoint)) @@ -415,16 +421,19 @@ public class Server implements CassandraDaemon.Server onTopologyChange(endpoint, Event.TopologyChange.newNode(getNativeAddress(endpoint))); } + @Override public void onLeaveCluster(InetAddressAndPort endpoint) { onTopologyChange(endpoint, Event.TopologyChange.removedNode(getNativeAddress(endpoint))); } + @Override public void onMove(InetAddressAndPort endpoint) { onTopologyChange(endpoint, Event.TopologyChange.movedNode(getNativeAddress(endpoint))); } + @Override public void onUp(InetAddressAndPort endpoint) { if (endpointsPendingJoinedNotification.remove(endpoint)) @@ -433,6 +442,7 @@ public class Server implements CassandraDaemon.Server onStatusChange(endpoint, Event.StatusChange.nodeUp(getNativeAddress(endpoint))); } + @Override public void onDown(InetAddressAndPort endpoint) { onStatusChange(endpoint, Event.StatusChange.nodeDown(getNativeAddress(endpoint))); @@ -466,85 +476,100 @@ public class Server implements CassandraDaemon.Server } } - public void onCreateKeyspace(String ksName) + @Override + public void onCreateKeyspace(KeyspaceMetadata keyspace) { - send(new Event.SchemaChange(Event.SchemaChange.Change.CREATED, ksName)); + send(new Event.SchemaChange(Event.SchemaChange.Change.CREATED, keyspace.name)); } - public void onCreateTable(String ksName, String cfName) + @Override + public void onCreateTable(TableMetadata table) { - send(new Event.SchemaChange(Event.SchemaChange.Change.CREATED, Event.SchemaChange.Target.TABLE, ksName, cfName)); + send(new Event.SchemaChange(Event.SchemaChange.Change.CREATED, Event.SchemaChange.Target.TABLE, table.keyspace, table.name)); } - public void onCreateType(String ksName, String typeName) + @Override + public void onCreateType(UserType type) { - send(new Event.SchemaChange(Event.SchemaChange.Change.CREATED, Event.SchemaChange.Target.TYPE, ksName, typeName)); + send(new Event.SchemaChange(Event.SchemaChange.Change.CREATED, Event.SchemaChange.Target.TYPE, type.keyspace, type.getNameAsString())); } - public void onCreateFunction(String ksName, String functionName, List> argTypes) + @Override + public void onCreateFunction(UDFunction function) { send(new Event.SchemaChange(Event.SchemaChange.Change.CREATED, Event.SchemaChange.Target.FUNCTION, - ksName, functionName, AbstractType.asCQLTypeStringList(argTypes))); + function.name().keyspace, function.name().name, AbstractType.asCQLTypeStringList(function.argTypes()))); } - public void onCreateAggregate(String ksName, String aggregateName, List> argTypes) + @Override + public void onCreateAggregate(UDAggregate aggregate) { send(new Event.SchemaChange(Event.SchemaChange.Change.CREATED, Event.SchemaChange.Target.AGGREGATE, - ksName, aggregateName, AbstractType.asCQLTypeStringList(argTypes))); + aggregate.name().keyspace, aggregate.name().name, AbstractType.asCQLTypeStringList(aggregate.argTypes()))); } - public void onAlterKeyspace(String ksName) + @Override + public void onAlterKeyspace(KeyspaceMetadata before, KeyspaceMetadata after) { - send(new Event.SchemaChange(Event.SchemaChange.Change.UPDATED, ksName)); + send(new Event.SchemaChange(Event.SchemaChange.Change.UPDATED, after.name)); } - public void onAlterTable(String ksName, String cfName, boolean affectsStatements) + @Override + public void onAlterTable(TableMetadata before, TableMetadata after, boolean affectsStatements) { - send(new Event.SchemaChange(Event.SchemaChange.Change.UPDATED, Event.SchemaChange.Target.TABLE, ksName, cfName)); + send(new Event.SchemaChange(Event.SchemaChange.Change.UPDATED, Event.SchemaChange.Target.TABLE, after.keyspace, after.name)); } - public void onAlterType(String ksName, String typeName) + @Override + public void onAlterType(UserType before, UserType after) { - send(new Event.SchemaChange(Event.SchemaChange.Change.UPDATED, Event.SchemaChange.Target.TYPE, ksName, typeName)); + send(new Event.SchemaChange(Event.SchemaChange.Change.UPDATED, Event.SchemaChange.Target.TYPE, after.keyspace, after.getNameAsString())); } - public void onAlterFunction(String ksName, String functionName, List> argTypes) + @Override + public void onAlterFunction(UDFunction before, UDFunction after) { send(new Event.SchemaChange(Event.SchemaChange.Change.UPDATED, Event.SchemaChange.Target.FUNCTION, - ksName, functionName, AbstractType.asCQLTypeStringList(argTypes))); + after.name().keyspace, after.name().name, AbstractType.asCQLTypeStringList(after.argTypes()))); } - public void onAlterAggregate(String ksName, String aggregateName, List> argTypes) + @Override + public void onAlterAggregate(UDAggregate before, UDAggregate after) { send(new Event.SchemaChange(Event.SchemaChange.Change.UPDATED, Event.SchemaChange.Target.AGGREGATE, - ksName, aggregateName, AbstractType.asCQLTypeStringList(argTypes))); + after.name().keyspace, after.name().name, AbstractType.asCQLTypeStringList(after.argTypes()))); } - public void onDropKeyspace(String ksName) + @Override + public void onDropKeyspace(KeyspaceMetadata keyspace) { - send(new Event.SchemaChange(Event.SchemaChange.Change.DROPPED, ksName)); + send(new Event.SchemaChange(Event.SchemaChange.Change.DROPPED, keyspace.name)); } - public void onDropTable(String ksName, String cfName) + @Override + public void onDropTable(TableMetadata table) { - send(new Event.SchemaChange(Event.SchemaChange.Change.DROPPED, Event.SchemaChange.Target.TABLE, ksName, cfName)); + send(new Event.SchemaChange(Event.SchemaChange.Change.DROPPED, Event.SchemaChange.Target.TABLE, table.keyspace, table.name)); } - public void onDropType(String ksName, String typeName) + @Override + public void onDropType(UserType type) { - send(new Event.SchemaChange(Event.SchemaChange.Change.DROPPED, Event.SchemaChange.Target.TYPE, ksName, typeName)); + send(new Event.SchemaChange(Event.SchemaChange.Change.DROPPED, Event.SchemaChange.Target.TYPE, type.keyspace, type.getNameAsString())); } - public void onDropFunction(String ksName, String functionName, List> argTypes) + @Override + public void onDropFunction(UDFunction function) { send(new Event.SchemaChange(Event.SchemaChange.Change.DROPPED, Event.SchemaChange.Target.FUNCTION, - ksName, functionName, AbstractType.asCQLTypeStringList(argTypes))); + function.name().keyspace, function.name().name, AbstractType.asCQLTypeStringList(function.argTypes()))); } - public void onDropAggregate(String ksName, String aggregateName, List> argTypes) + @Override + public void onDropAggregate(UDAggregate aggregate) { send(new Event.SchemaChange(Event.SchemaChange.Change.DROPPED, Event.SchemaChange.Target.AGGREGATE, - ksName, aggregateName, AbstractType.asCQLTypeStringList(argTypes))); + aggregate.name().keyspace, aggregate.name().name, AbstractType.asCQLTypeStringList(aggregate.argTypes()))); } } } diff --git a/src/java/org/apache/cassandra/utils/Collectors3.java b/src/java/org/apache/cassandra/utils/Collectors3.java index f8f262e69e..c48f16062a 100644 --- a/src/java/org/apache/cassandra/utils/Collectors3.java +++ b/src/java/org/apache/cassandra/utils/Collectors3.java @@ -18,22 +18,24 @@ package org.apache.cassandra.utils; -import java.util.List; -import java.util.Set; +import java.util.Map; +import java.util.function.Function; import java.util.stream.Collector; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; /** * Some extra Collector implementations. - * + *

* Named Collectors3 just in case Guava ever makes a Collectors2 */ public class Collectors3 { - private static final Collector.Characteristics[] LIST_CHARACTERISTICS = new Collector.Characteristics[] { }; - public static Collector> toImmutableList() + private static final Collector.Characteristics[] LIST_CHARACTERISTICS = new Collector.Characteristics[]{}; + + public static Collector> toImmutableList() { return Collector.of(ImmutableList.Builder::new, ImmutableList.Builder::add, @@ -42,8 +44,9 @@ public class Collectors3 LIST_CHARACTERISTICS); } - private static final Collector.Characteristics[] SET_CHARACTERISTICS = new Collector.Characteristics[] { Collector.Characteristics.UNORDERED }; - public static Collector> toImmutableSet() + private static final Collector.Characteristics[] SET_CHARACTERISTICS = new Collector.Characteristics[]{ Collector.Characteristics.UNORDERED }; + + public static Collector> toImmutableSet() { return Collector.of(ImmutableSet.Builder::new, ImmutableSet.Builder::add, @@ -51,4 +54,25 @@ public class Collectors3 ImmutableSet.Builder::build, SET_CHARACTERISTICS); } + + private static final Collector.Characteristics[] MAP_CHARACTERISTICS = new Collector.Characteristics[]{ Collector.Characteristics.UNORDERED }; + + public static Collector, ?, ImmutableMap> toImmutableMap() + { + return Collector.of(ImmutableMap.Builder::new, + ImmutableMap.Builder::put, + (l, r) -> l.putAll(r.build()), + ImmutableMap.Builder::build, + MAP_CHARACTERISTICS); + } + + public static Collector> toImmutableMap(Function keyMapper, Function valueMapper) + { + return Collector.of(ImmutableMap.Builder::new, + (b, t) -> b.put(keyMapper.apply(t), valueMapper.apply(t)), + (l, r) -> l.putAll(r.build()), + ImmutableMap.Builder::build, + MAP_CHARACTERISTICS); + } + } diff --git a/src/java/org/apache/cassandra/utils/FBUtilities.java b/src/java/org/apache/cassandra/utils/FBUtilities.java index cf2f8469cb..0a5b71f503 100644 --- a/src/java/org/apache/cassandra/utils/FBUtilities.java +++ b/src/java/org/apache/cassandra/utils/FBUtilities.java @@ -512,7 +512,7 @@ public class FBUtilities } catch (ExecutionException ee) { - throw new RuntimeException(ee); + throw Throwables.cleaned(ee); } catch (InterruptedException ie) { diff --git a/test/distributed/org/apache/cassandra/distributed/action/GossipHelper.java b/test/distributed/org/apache/cassandra/distributed/action/GossipHelper.java index 683713adc5..fc377a65c0 100644 --- a/test/distributed/org/apache/cassandra/distributed/action/GossipHelper.java +++ b/test/distributed/org/apache/cassandra/distributed/action/GossipHelper.java @@ -45,7 +45,7 @@ import org.apache.cassandra.io.util.DataInputBuffer; import org.apache.cassandra.io.util.DataOutputBuffer; import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.net.MessagingService; -import org.apache.cassandra.schema.MigrationCoordinator; +import org.apache.cassandra.schema.Schema; import org.apache.cassandra.service.StorageService; import org.apache.cassandra.utils.FBUtilities; @@ -227,8 +227,8 @@ public class GossipHelper pullTo.acceptsOnInstance((InetSocketAddress pullFrom) -> { InetAddressAndPort endpoint = toCassandraInetAddressAndPort(pullFrom); EndpointState state = Gossiper.instance.getEndpointStateForEndpoint(endpoint); - MigrationCoordinator.instance.reportEndpointVersion(endpoint, state); - MigrationCoordinator.instance.awaitSchemaRequests(TimeUnit.SECONDS.toMillis(10)); + Gossiper.instance.doOnChangeNotifications(endpoint, ApplicationState.SCHEMA, state.getApplicationState(ApplicationState.SCHEMA)); + Schema.instance.waitUntilReady(Duration.ofSeconds(10)); }).accept(pullFrom); } } @@ -258,7 +258,7 @@ public class GossipHelper List tokens = Collections.singletonList(partitioner.getTokenFactory().fromString(tokenString)); try { - Collection collisions = StorageService.instance.prepareForBootstrap(waitForSchema.toMillis()); + Collection collisions = StorageService.instance.prepareForBootstrap(waitForSchema.toMillis(), 0); assert collisions.size() == 0 : String.format("Didn't expect any replacements but got %s", collisions); boolean isBootstrapSuccessful = StorageService.instance.bootstrap(tokens, waitForBootstrap.toMillis()); assert isBootstrapSuccessful : "Bootstrap did not complete successfully"; diff --git a/test/distributed/org/apache/cassandra/distributed/impl/Instance.java b/test/distributed/org/apache/cassandra/distributed/impl/Instance.java index 760d3b1af4..2a44ffe2ce 100644 --- a/test/distributed/org/apache/cassandra/distributed/impl/Instance.java +++ b/test/distributed/org/apache/cassandra/distributed/impl/Instance.java @@ -35,7 +35,6 @@ import java.util.UUID; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.stream.Stream; import javax.management.ListenerNotFoundException; @@ -105,7 +104,6 @@ import org.apache.cassandra.net.Message; import org.apache.cassandra.net.MessagingService; import org.apache.cassandra.net.NoPayload; import org.apache.cassandra.net.Verb; -import org.apache.cassandra.schema.MigrationManager; import org.apache.cassandra.schema.Schema; import org.apache.cassandra.schema.SchemaConstants; import org.apache.cassandra.service.ActiveRepairService; @@ -633,7 +631,6 @@ public class Instance extends IsolatedExecutor implements IInvokableInstance StorageService.instance.registerDaemon(CassandraDaemon.getInstanceForTesting()); if (config.has(GOSSIP)) { - MigrationManager.setUptimeFn(() -> TimeUnit.NANOSECONDS.toMillis(nanoTime() - startedAt)); try { StorageService.instance.initServer(); @@ -652,6 +649,7 @@ public class Instance extends IsolatedExecutor implements IInvokableInstance } else { + Schema.instance.startSync(); Stream peers = cluster.stream().filter(instance -> ((IInstance) instance).isValid()); SystemKeyspace.setLocalHostId(config.hostId()); if (config.has(BLANK_GOSSIP)) @@ -670,8 +668,6 @@ public class Instance extends IsolatedExecutor implements IInvokableInstance // see org.apache.cassandra.service.CassandraDaemon.setup StorageService.instance.populateTokenMetadata(); - SystemKeyspace.finishStartup(); - CassandraDaemon.getInstanceForTesting().completeSetup(); if (config.has(NATIVE_PROTOCOL)) diff --git a/test/distributed/org/apache/cassandra/distributed/test/metric/TableMetricTest.java b/test/distributed/org/apache/cassandra/distributed/test/metric/TableMetricTest.java index 0e915d328c..08c9324649 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/metric/TableMetricTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/metric/TableMetricTest.java @@ -121,7 +121,7 @@ public class TableMetricTest extends TestBaseImpl SYSTEM_TABLES = cluster.get(1).callOnInstance(() -> { Map> map = new HashMap<>(); Arrays.asList(SystemKeyspace.metadata(), AuthKeyspace.metadata(), SystemDistributedKeyspace.metadata(), - Schema.getSystemKeyspaceMetadata(), TraceKeyspace.metadata()) + SchemaKeyspace.metadata(), TraceKeyspace.metadata()) .forEach(meta -> { Set tables = meta.tables.stream().map(t -> t.name).collect(Collectors.toSet()); map.put(meta.name, tables); diff --git a/test/distributed/org/apache/cassandra/distributed/test/ring/BootstrapTest.java b/test/distributed/org/apache/cassandra/distributed/test/ring/BootstrapTest.java index 909b37420b..423e78b4ba 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/ring/BootstrapTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/ring/BootstrapTest.java @@ -18,13 +18,17 @@ package org.apache.cassandra.distributed.test.ring; +import java.lang.management.ManagementFactory; import java.util.Map; import java.util.stream.Collectors; import java.util.stream.IntStream; +import org.junit.After; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; +import org.apache.cassandra.config.CassandraRelevantProperties; import org.apache.cassandra.distributed.Cluster; import org.apache.cassandra.distributed.api.ConsistencyLevel; import org.apache.cassandra.distributed.api.ICluster; @@ -44,6 +48,27 @@ import static org.apache.cassandra.distributed.api.Feature.NETWORK; public class BootstrapTest extends TestBaseImpl { + private long savedMigrationDelay; + + @Before + public void beforeTest() + { + // MigrationCoordinator schedules schema pull requests immediatelly when the node is just starting up, otherwise + // the first pull request is sent in 60 seconds. Whether we are starting up or not is detected by examining + // the node up-time and if it is lower than MIGRATION_DELAY, we consider the server is starting up. + // When we are running multiple test cases in the class, where each starts a node but in the same JVM, the + // up-time will be more or less relevant only for the first test. In order to enforce the startup-like behaviour + // for each test case, the MIGRATION_DELAY time is adjusted accordingly + savedMigrationDelay = CassandraRelevantProperties.MIGRATION_DELAY.getLong(); + CassandraRelevantProperties.MIGRATION_DELAY.setLong(ManagementFactory.getRuntimeMXBean().getUptime() + savedMigrationDelay); + } + + @After + public void afterTest() + { + CassandraRelevantProperties.MIGRATION_DELAY.setLong(savedMigrationDelay); + } + @Test public void bootstrapTest() throws Throwable { diff --git a/test/microbench/org/apache/cassandra/test/microbench/BatchStatementBench.java b/test/microbench/org/apache/cassandra/test/microbench/BatchStatementBench.java index 4d10107160..9678b8d8ea 100644 --- a/test/microbench/org/apache/cassandra/test/microbench/BatchStatementBench.java +++ b/test/microbench/org/apache/cassandra/test/microbench/BatchStatementBench.java @@ -40,6 +40,7 @@ import org.apache.cassandra.dht.Murmur3Partitioner; import org.apache.cassandra.schema.KeyspaceMetadata; import org.apache.cassandra.schema.KeyspaceParams; import org.apache.cassandra.schema.Schema; +import org.apache.cassandra.schema.SchemaTestUtil; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.utils.FBUtilities; @@ -101,11 +102,11 @@ public class BatchStatementBench @Setup public void setup() throws Throwable { - Schema.instance.load(KeyspaceMetadata.create(keyspace, KeyspaceParams.simple(1))); + SchemaTestUtil.addOrUpdateKeyspace(KeyspaceMetadata.create(keyspace, KeyspaceParams.simple(1)), false); KeyspaceMetadata ksm = Schema.instance.getKeyspaceMetadata(keyspace); TableMetadata metadata = CreateTableStatement.parse(String.format("CREATE TABLE %s (id int, ck int, v int, primary key (id, ck))", table), keyspace).build(); - Schema.instance.load(ksm.withSwapped(ksm.tables.with(metadata))); + SchemaTestUtil.addOrUpdateKeyspace(ksm.withSwapped(ksm.tables.with(metadata)), false); List modifications = new ArrayList<>(batchSize); List> parameters = new ArrayList<>(batchSize); diff --git a/test/microbench/org/apache/cassandra/test/microbench/MutationBench.java b/test/microbench/org/apache/cassandra/test/microbench/MutationBench.java index 41d6aab01c..d258026071 100644 --- a/test/microbench/org/apache/cassandra/test/microbench/MutationBench.java +++ b/test/microbench/org/apache/cassandra/test/microbench/MutationBench.java @@ -29,6 +29,7 @@ import org.apache.cassandra.cql3.statements.schema.CreateTableStatement; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.schema.Schema; +import org.apache.cassandra.schema.SchemaTestUtil; import org.apache.cassandra.db.Mutation; import org.apache.cassandra.dht.Murmur3Partitioner; import org.apache.cassandra.io.util.DataInputBuffer; @@ -87,7 +88,7 @@ public class MutationBench @Setup public void setup() throws IOException { - Schema.instance.load(KeyspaceMetadata.create(keyspace, KeyspaceParams.simple(1))); + SchemaTestUtil.addOrUpdateKeyspace(KeyspaceMetadata.create(keyspace, KeyspaceParams.simple(1)), false); KeyspaceMetadata ksm = Schema.instance.getKeyspaceMetadata(keyspace); TableMetadata metadata = CreateTableStatement.parse("CREATE TABLE userpics " + @@ -97,7 +98,7 @@ public class MutationBench "PRIMARY KEY(userid, picid))", keyspace) .build(); - Schema.instance.load(ksm.withSwapped(ksm.tables.with(metadata))); + SchemaTestUtil.addOrUpdateKeyspace(ksm.withSwapped(ksm.tables.with(metadata)), false); mutation = (Mutation)UpdateBuilder.create(metadata, 1L).newRow(1L).add("commentid", 32L).makeMutation(); buffer = ByteBuffer.allocate(mutation.serializedSize(MessagingService.current_version)); diff --git a/test/simulator/main/org/apache/cassandra/simulator/cluster/OnInstanceSyncSchemaForBootstrap.java b/test/simulator/main/org/apache/cassandra/simulator/cluster/OnInstanceSyncSchemaForBootstrap.java index 5032f54b4e..bf10db2d0a 100644 --- a/test/simulator/main/org/apache/cassandra/simulator/cluster/OnInstanceSyncSchemaForBootstrap.java +++ b/test/simulator/main/org/apache/cassandra/simulator/cluster/OnInstanceSyncSchemaForBootstrap.java @@ -18,7 +18,9 @@ package org.apache.cassandra.simulator.cluster; -import org.apache.cassandra.schema.MigrationCoordinator; +import java.time.Duration; + +import org.apache.cassandra.schema.Schema; import org.apache.cassandra.simulator.systems.SimulatedActionTask; import static org.apache.cassandra.simulator.Action.Modifier.DISPLAY_ORIGIN; @@ -29,6 +31,6 @@ class OnInstanceSyncSchemaForBootstrap extends SimulatedActionTask public OnInstanceSyncSchemaForBootstrap(ClusterActions actions, int node) { super("Sync Schema on " + node, RELIABLE_NO_TIMEOUTS.with(DISPLAY_ORIGIN), RELIABLE_NO_TIMEOUTS, actions, actions.cluster.get(node), - () -> MigrationCoordinator.instance.awaitSchemaRequests(Long.MAX_VALUE)); + () -> Schema.instance.waitUntilReady(Duration.ofMinutes(10))); } } diff --git a/test/unit/org/apache/cassandra/SchemaLoader.java b/test/unit/org/apache/cassandra/SchemaLoader.java index c5016970aa..327949074b 100644 --- a/test/unit/org/apache/cassandra/SchemaLoader.java +++ b/test/unit/org/apache/cassandra/SchemaLoader.java @@ -247,7 +247,7 @@ public class SchemaLoader // if you're messing with low-level sstable stuff, it can be useful to inject the schema directly // Schema.instance.load(schemaDefinition()); for (KeyspaceMetadata ksm : schema) - MigrationManager.announceNewKeyspace(ksm, false); + SchemaTestUtil.announceNewKeyspace(ksm); if (Boolean.parseBoolean(System.getProperty("cassandra.test.compression", "false"))) useCompression(schema, compressionParams(CompressionParams.DEFAULT_CHUNK_LENGTH)); @@ -255,7 +255,7 @@ public class SchemaLoader public static void createKeyspace(String name, KeyspaceParams params) { - MigrationManager.announceNewKeyspace(KeyspaceMetadata.create(name, params, Tables.of()), true); + SchemaTestUtil.announceNewKeyspace(KeyspaceMetadata.create(name, params, Tables.of())); } public static void createKeyspace(String name, KeyspaceParams params, TableMetadata.Builder... builders) @@ -264,17 +264,17 @@ public class SchemaLoader for (TableMetadata.Builder builder : builders) tables.add(builder.build()); - MigrationManager.announceNewKeyspace(KeyspaceMetadata.create(name, params, tables.build()), true); + SchemaTestUtil.announceNewKeyspace(KeyspaceMetadata.create(name, params, tables.build())); } public static void createKeyspace(String name, KeyspaceParams params, TableMetadata... tables) { - MigrationManager.announceNewKeyspace(KeyspaceMetadata.create(name, params, Tables.of(tables)), true); + SchemaTestUtil.announceNewKeyspace(KeyspaceMetadata.create(name, params, Tables.of(tables))); } public static void createKeyspace(String name, KeyspaceParams params, Tables tables, Types types) { - MigrationManager.announceNewKeyspace(KeyspaceMetadata.create(name, params, tables, Views.none(), types, Functions.none()), true); + SchemaTestUtil.announceNewKeyspace(KeyspaceMetadata.create(name, params, tables, Views.none(), types, Functions.none())); } public static void setupAuth(IRoleManager roleManager, IAuthenticator authenticator, IAuthorizer authorizer, INetworkAuthorizer networkAuthorizer) @@ -283,7 +283,7 @@ public class SchemaLoader DatabaseDescriptor.setAuthenticator(authenticator); DatabaseDescriptor.setAuthorizer(authorizer); DatabaseDescriptor.setNetworkAuthorizer(networkAuthorizer); - MigrationManager.announceNewKeyspace(AuthKeyspace.metadata(), true); + SchemaTestUtil.announceNewKeyspace(AuthKeyspace.metadata()); DatabaseDescriptor.getRoleManager().setup(); DatabaseDescriptor.getAuthenticator().setup(); DatabaseDescriptor.getAuthorizer().setup(); @@ -335,7 +335,7 @@ public class SchemaLoader { for (KeyspaceMetadata ksm : schema) for (TableMetadata cfm : ksm.tablesAndViews()) - MigrationManager.announceTableUpdate(cfm.unbuild().compression(compressionParams.copy()).build(), true); + SchemaTestUtil.announceTableUpdate(cfm.unbuild().compression(compressionParams.copy()).build()); } public static TableMetadata.Builder counterCFMD(String ksName, String cfName) diff --git a/test/unit/org/apache/cassandra/cql3/CQLTester.java b/test/unit/org/apache/cassandra/cql3/CQLTester.java index bfe446fc91..583d2a4f76 100644 --- a/test/unit/org/apache/cassandra/cql3/CQLTester.java +++ b/test/unit/org/apache/cassandra/cql3/CQLTester.java @@ -479,7 +479,7 @@ public abstract class CQLTester }; DatabaseDescriptor.setRoleManager(roleManager); - MigrationManager.announceNewKeyspace(AuthKeyspace.metadata(), true); + SchemaTestUtil.addOrUpdateKeyspace(AuthKeyspace.metadata(), true); DatabaseDescriptor.getRoleManager().setup(); DatabaseDescriptor.getAuthenticator().setup(); DatabaseDescriptor.getAuthorizer().setup(); @@ -514,7 +514,6 @@ public abstract class CQLTester private static void startServices() { - SystemKeyspace.finishStartup(); VirtualKeyspaceRegistry.instance.register(VirtualSchemaKeyspace.instance); StorageService.instance.initServer(); SchemaLoader.startGossiper(); diff --git a/test/unit/org/apache/cassandra/cql3/KeyCacheCqlTest.java b/test/unit/org/apache/cassandra/cql3/KeyCacheCqlTest.java index b1074b23bc..861b60f5e8 100644 --- a/test/unit/org/apache/cassandra/cql3/KeyCacheCqlTest.java +++ b/test/unit/org/apache/cassandra/cql3/KeyCacheCqlTest.java @@ -30,12 +30,15 @@ import org.junit.Test; import org.apache.cassandra.cache.KeyCacheKey; import org.apache.cassandra.config.DatabaseDescriptor; -import org.apache.cassandra.schema.Schema; +import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.db.Keyspace; import org.apache.cassandra.index.Index; +import org.apache.cassandra.io.sstable.format.SSTableFormat; import org.apache.cassandra.metrics.CacheMetrics; import org.apache.cassandra.metrics.CassandraMetricsRegistry; import org.apache.cassandra.schema.CachingParams; +import org.apache.cassandra.schema.Schema; +import org.apache.cassandra.schema.SchemaKeyspace; import org.apache.cassandra.schema.TableMetadataRef; import org.apache.cassandra.service.CacheService; import org.apache.cassandra.service.StorageService; @@ -309,7 +312,7 @@ public class KeyCacheCqlTest extends CQLTester } dropTable("DROP TABLE %s"); - Schema.instance.updateVersion(); + assert Schema.instance.isSameVersion(SchemaKeyspace.calculateSchemaDigest()); //Test loading for a dropped 2i/table CacheService.instance.keyCache.clear(); diff --git a/test/unit/org/apache/cassandra/cql3/ViewSchemaTest.java b/test/unit/org/apache/cassandra/cql3/ViewSchemaTest.java index b74391b797..0ab9aec094 100644 --- a/test/unit/org/apache/cassandra/cql3/ViewSchemaTest.java +++ b/test/unit/org/apache/cassandra/cql3/ViewSchemaTest.java @@ -848,8 +848,9 @@ public class ViewSchemaTest extends ViewAbstractTest String view = createView(createView); - ColumnFamilyStore mv = Keyspace.open(keyspace()).getColumnFamilyStore(view); - assertTrue(SchemaCQLHelper.getTableMetadataAsCQL(mv.metadata()) + Keyspace keyspace = Keyspace.open(keyspace()); + ColumnFamilyStore mv = keyspace.getColumnFamilyStore(view); + assertTrue(SchemaCQLHelper.getTableMetadataAsCQL(mv.metadata(), keyspace.getMetadata()) .startsWith(String.format(viewSnapshotSchema, keyspace(), view, diff --git a/test/unit/org/apache/cassandra/cql3/validation/entities/TupleTypeTest.java b/test/unit/org/apache/cassandra/cql3/validation/entities/TupleTypeTest.java index c23a32a2a4..9f53db4966 100644 --- a/test/unit/org/apache/cassandra/cql3/validation/entities/TupleTypeTest.java +++ b/test/unit/org/apache/cassandra/cql3/validation/entities/TupleTypeTest.java @@ -33,7 +33,6 @@ import org.junit.Test; import org.apache.cassandra.cql3.CQLTester; import org.apache.cassandra.cql3.UntypedResultSet; -import org.apache.cassandra.db.SchemaCQLHelper; import org.apache.cassandra.db.marshal.TupleType; import org.apache.cassandra.utils.AbstractTypeGenerators.TypeSupport; import org.quicktheories.core.Gen; diff --git a/test/unit/org/apache/cassandra/cql3/validation/entities/UFTest.java b/test/unit/org/apache/cassandra/cql3/validation/entities/UFTest.java index d4712737a4..6999bf6528 100644 --- a/test/unit/org/apache/cassandra/cql3/validation/entities/UFTest.java +++ b/test/unit/org/apache/cassandra/cql3/validation/entities/UFTest.java @@ -38,6 +38,7 @@ import org.apache.cassandra.db.marshal.CollectionType; import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.schema.KeyspaceMetadata; import org.apache.cassandra.schema.Schema; +import org.apache.cassandra.schema.SchemaTestUtil; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.transport.Event.SchemaChange.Change; import org.apache.cassandra.transport.Event.SchemaChange.Target; @@ -860,7 +861,7 @@ public class UFTest extends CQLTester "java", f.body(), new InvalidRequestException("foo bar is broken")); - Schema.instance.load(ksm.withSwapped(ksm.functions.without(f.name(), f.argTypes()).with(broken))); + SchemaTestUtil.addOrUpdateKeyspace(ksm.withSwapped(ksm.functions.without(f.name(), f.argTypes()).with(broken)), false); assertInvalidThrowMessage("foo bar is broken", InvalidRequestException.class, "SELECT key, " + fName + "(dval) FROM %s"); diff --git a/test/unit/org/apache/cassandra/cql3/validation/operations/CompactStorageTest.java b/test/unit/org/apache/cassandra/cql3/validation/operations/CompactStorageTest.java index 5d904ea4c1..212e698aad 100644 --- a/test/unit/org/apache/cassandra/cql3/validation/operations/CompactStorageTest.java +++ b/test/unit/org/apache/cassandra/cql3/validation/operations/CompactStorageTest.java @@ -4638,7 +4638,7 @@ public class CompactStorageTest extends CQLTester ColumnFamilyStore cfs = Keyspace.open(keyspace).getColumnFamilyStore(table); - String actual = SchemaCQLHelper.getTableMetadataAsCQL(cfs.metadata()); + String actual = SchemaCQLHelper.getTableMetadataAsCQL(cfs.metadata(), cfs.keyspace.getMetadata()); String expected = "CREATE TABLE IF NOT EXISTS cql_test_keyspace_compact.test_table_compact (\n" + " pk1 varint,\n" + " pk2 ascii,\n" + @@ -4674,7 +4674,7 @@ public class CompactStorageTest extends CQLTester ColumnFamilyStore cfs = Keyspace.open(keyspace).getColumnFamilyStore(table); - String actual = SchemaCQLHelper.getTableMetadataAsCQL(cfs.metadata()); + String actual = SchemaCQLHelper.getTableMetadataAsCQL(cfs.metadata(), cfs.keyspace.getMetadata()); String expected = "CREATE TABLE IF NOT EXISTS cql_test_keyspace_counter.test_table_counter (\n" + " pk1 varint,\n" + " pk2 ascii,\n" + @@ -4699,7 +4699,7 @@ public class CompactStorageTest extends CQLTester ColumnFamilyStore cfs = Keyspace.open(keyspace()).getColumnFamilyStore(tableName); - String actual = SchemaCQLHelper.getTableMetadataAsCQL(cfs.metadata()); + String actual = SchemaCQLHelper.getTableMetadataAsCQL(cfs.metadata(), cfs.keyspace.getMetadata()); String expected = "CREATE TABLE IF NOT EXISTS " + keyspace() + "." + tableName + " (\n" + " pk1 varint,\n" + " reg1 int,\n" + @@ -4721,7 +4721,7 @@ public class CompactStorageTest extends CQLTester " WITH COMPACT STORAGE"); ColumnFamilyStore cfs = Keyspace.open(keyspace()).getColumnFamilyStore(tableName); - assertTrue(SchemaCQLHelper.getTableMetadataAsCQL(cfs.metadata()).contains( + assertTrue(SchemaCQLHelper.getTableMetadataAsCQL(cfs.metadata(), cfs.keyspace.getMetadata()).contains( "CREATE TABLE IF NOT EXISTS " + keyspace() + "." + tableName + " (\n" + " pk1 varint,\n" + " reg1 int,\n" + @@ -4743,7 +4743,7 @@ public class CompactStorageTest extends CQLTester ColumnFamilyStore cfs = Keyspace.open(keyspace()).getColumnFamilyStore(tableName); - String actual = SchemaCQLHelper.getTableMetadataAsCQL(cfs.metadata()); + String actual = SchemaCQLHelper.getTableMetadataAsCQL(cfs.metadata(), cfs.keyspace.getMetadata()); String expected = "CREATE TABLE IF NOT EXISTS " + keyspace() + "." + tableName + " (\n" + " pk1 varint,\n" + " reg1 counter,\n" + @@ -4766,7 +4766,7 @@ public class CompactStorageTest extends CQLTester ColumnFamilyStore cfs = Keyspace.open(keyspace()).getColumnFamilyStore(tableName); - String actual = SchemaCQLHelper.getTableMetadataAsCQL(cfs.metadata()); + String actual = SchemaCQLHelper.getTableMetadataAsCQL(cfs.metadata(), cfs.keyspace.getMetadata()); String expected = "CREATE TABLE IF NOT EXISTS " + keyspace() + "." + tableName + " (\n" + " pk1 varint,\n" + " ck1 int,\n" + @@ -4789,7 +4789,7 @@ public class CompactStorageTest extends CQLTester ColumnFamilyStore cfs = Keyspace.open(keyspace()).getColumnFamilyStore(tableName); - String actual = SchemaCQLHelper.getTableMetadataAsCQL(cfs.metadata()); + String actual = SchemaCQLHelper.getTableMetadataAsCQL(cfs.metadata(), cfs.keyspace.getMetadata()); String expected = "CREATE TABLE IF NOT EXISTS " + keyspace() + "." + tableName + " (\n" + " pk1 varint,\n" + " ck1 int,\n" + diff --git a/test/unit/org/apache/cassandra/cql3/validation/operations/CompactTableTest.java b/test/unit/org/apache/cassandra/cql3/validation/operations/CompactTableTest.java index 589490fc59..2281e46976 100644 --- a/test/unit/org/apache/cassandra/cql3/validation/operations/CompactTableTest.java +++ b/test/unit/org/apache/cassandra/cql3/validation/operations/CompactTableTest.java @@ -27,6 +27,7 @@ import org.apache.cassandra.cql3.QueryHandler; import org.apache.cassandra.cql3.QueryProcessor; import org.apache.cassandra.schema.Schema; import org.apache.cassandra.schema.SchemaChangeListener; +import org.apache.cassandra.schema.TableMetadata; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -71,7 +72,8 @@ public class CompactTableTest extends CQLTester // Verify that schema change listeners are told statements are affected with DROP COMPACT STORAGE. SchemaChangeListener listener = new SchemaChangeListener() { - public void onAlterTable(String keyspace, String table, boolean affectsStatements) + @Override + public void onAlterTable(TableMetadata before, TableMetadata after, boolean affectsStatements) { assertTrue(affectsStatements); } diff --git a/test/unit/org/apache/cassandra/db/CounterCacheTest.java b/test/unit/org/apache/cassandra/db/CounterCacheTest.java index 6396742c77..2b743a9f59 100644 --- a/test/unit/org/apache/cassandra/db/CounterCacheTest.java +++ b/test/unit/org/apache/cassandra/db/CounterCacheTest.java @@ -22,6 +22,8 @@ import java.util.concurrent.ExecutionException; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.schema.ColumnMetadata; +import org.apache.cassandra.schema.KeyspaceMetadata; +import org.apache.cassandra.schema.SchemaTestUtil; import org.apache.cassandra.dht.Bounds; import org.apache.cassandra.dht.Token; import org.apache.cassandra.schema.KeyspaceParams; @@ -191,7 +193,8 @@ public class CounterCacheTest CacheService.instance.invalidateCounterCache(); assertEquals(0, CacheService.instance.counterCache.size()); - Keyspace ks = Schema.instance.maybeRemoveKeyspaceInstance(KEYSPACE1); + KeyspaceMetadata ksm = Schema.instance.getKeyspaceMetadata(KEYSPACE1); + SchemaTestUtil.dropKeyspaceIfExist(KEYSPACE1, true); try { @@ -201,7 +204,7 @@ public class CounterCacheTest } finally { - Schema.instance.maybeAddKeyspaceInstance(ks.getName(), () -> ks); + SchemaTestUtil.addOrUpdateKeyspace(ksm, true); } } diff --git a/test/unit/org/apache/cassandra/db/KeyspaceTest.java b/test/unit/org/apache/cassandra/db/KeyspaceTest.java index 2939ef5bbf..b0c8011b41 100644 --- a/test/unit/org/apache/cassandra/db/KeyspaceTest.java +++ b/test/unit/org/apache/cassandra/db/KeyspaceTest.java @@ -21,12 +21,9 @@ package org.apache.cassandra.db; import java.nio.ByteBuffer; import java.util.*; -import org.apache.cassandra.gms.Gossiper; -import org.apache.cassandra.schema.MigrationManager; import org.apache.cassandra.schema.Schema; import org.assertj.core.api.Assertions; import org.junit.Test; -import org.mockito.Mockito; import org.apache.cassandra.Util; import org.apache.cassandra.cql3.CQLTester; @@ -40,16 +37,10 @@ import org.apache.cassandra.db.filter.*; import org.apache.cassandra.db.partitions.PartitionIterator; import org.apache.cassandra.io.sstable.format.SSTableReader; import org.apache.cassandra.metrics.ClearableHistogram; -import org.apache.cassandra.schema.SchemaProvider; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.FBUtilities; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; public class KeyspaceTest extends CQLTester { diff --git a/test/unit/org/apache/cassandra/db/RangeTombstoneTest.java b/test/unit/org/apache/cassandra/db/RangeTombstoneTest.java index 755302d1bd..9e8f913501 100644 --- a/test/unit/org/apache/cassandra/db/RangeTombstoneTest.java +++ b/test/unit/org/apache/cassandra/db/RangeTombstoneTest.java @@ -45,8 +45,8 @@ import org.apache.cassandra.io.sstable.format.SSTableReader; import org.apache.cassandra.io.sstable.metadata.StatsMetadata; import org.apache.cassandra.schema.IndexMetadata; import org.apache.cassandra.schema.KeyspaceParams; +import org.apache.cassandra.schema.SchemaTestUtil; import org.apache.cassandra.schema.TableMetadata; -import org.apache.cassandra.schema.MigrationManager; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.FBUtilities; @@ -320,7 +320,7 @@ public class RangeTombstoneTest { Keyspace ks = Keyspace.open(KSNAME); ColumnFamilyStore cfs = ks.getColumnFamilyStore(CFNAME); - MigrationManager.announceTableUpdate(cfs.metadata().unbuild().gcGraceSeconds(2).build(), true); + SchemaTestUtil.announceTableUpdate(cfs.metadata().unbuild().gcGraceSeconds(2).build()); String key = "7810"; @@ -343,7 +343,7 @@ public class RangeTombstoneTest { Keyspace ks = Keyspace.open(KSNAME); ColumnFamilyStore cfs = ks.getColumnFamilyStore(CFNAME); - MigrationManager.announceTableUpdate(cfs.metadata().unbuild().gcGraceSeconds(2).build(), true); + SchemaTestUtil.announceTableUpdate(cfs.metadata().unbuild().gcGraceSeconds(2).build()); String key = "7808_1"; UpdateBuilder builder = UpdateBuilder.create(cfs.metadata(), key).withTimestamp(0); @@ -363,7 +363,7 @@ public class RangeTombstoneTest { Keyspace ks = Keyspace.open(KSNAME); ColumnFamilyStore cfs = ks.getColumnFamilyStore(CFNAME); - MigrationManager.announceTableUpdate(cfs.metadata().unbuild().gcGraceSeconds(2).build(), true); + SchemaTestUtil.announceTableUpdate(cfs.metadata().unbuild().gcGraceSeconds(2).build()); String key = "7808_2"; UpdateBuilder builder = UpdateBuilder.create(cfs.metadata(), key).withTimestamp(0); @@ -489,7 +489,7 @@ public class RangeTombstoneTest current.unbuild() .indexes(current.indexes.with(indexDef)) .build(); - MigrationManager.announceTableUpdate(updated, true); + SchemaTestUtil.announceTableUpdate(updated); } Future rebuild = cfs.indexManager.addIndex(indexDef, false); @@ -595,7 +595,7 @@ public class RangeTombstoneTest current.unbuild() .indexes(current.indexes.with(indexDef)) .build(); - MigrationManager.announceTableUpdate(updated, true); + SchemaTestUtil.announceTableUpdate(updated); } Future rebuild = cfs.indexManager.addIndex(indexDef, false); diff --git a/test/unit/org/apache/cassandra/db/ReadCommandTest.java b/test/unit/org/apache/cassandra/db/ReadCommandTest.java index 9bbd956ee3..8be5608626 100644 --- a/test/unit/org/apache/cassandra/db/ReadCommandTest.java +++ b/test/unit/org/apache/cassandra/db/ReadCommandTest.java @@ -72,6 +72,7 @@ import org.apache.cassandra.schema.CachingParams; import org.apache.cassandra.schema.KeyspaceMetadata; import org.apache.cassandra.schema.KeyspaceParams; import org.apache.cassandra.schema.Schema; +import org.apache.cassandra.schema.SchemaTestUtil; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.schema.TableParams; import org.apache.cassandra.service.ActiveRepairService; @@ -902,10 +903,7 @@ public class ReadCommandTest { TableParams newParams = cfs.metadata().params.unbuild().gcGraceSeconds(gcGrace).build(); KeyspaceMetadata keyspaceMetadata = Schema.instance.getKeyspaceMetadata(cfs.metadata().keyspace); - Schema.instance.load( - keyspaceMetadata.withSwapped( - keyspaceMetadata.tables.withSwapped( - cfs.metadata().withSwapped(newParams)))); + SchemaTestUtil.addOrUpdateKeyspace(keyspaceMetadata.withSwapped(keyspaceMetadata.tables.withSwapped(cfs.metadata().withSwapped(newParams))), true); } private long getAndResetOverreadCount(ColumnFamilyStore cfs) diff --git a/test/unit/org/apache/cassandra/db/RowCacheTest.java b/test/unit/org/apache/cassandra/db/RowCacheTest.java index a0157888f8..c44cfb2c8b 100644 --- a/test/unit/org/apache/cassandra/db/RowCacheTest.java +++ b/test/unit/org/apache/cassandra/db/RowCacheTest.java @@ -33,6 +33,7 @@ import org.apache.cassandra.Util; import org.apache.cassandra.cache.RowCacheKey; import org.apache.cassandra.db.marshal.ValueAccessors; import org.apache.cassandra.locator.InetAddressAndPort; +import org.apache.cassandra.schema.KeyspaceMetadata; import org.apache.cassandra.schema.Schema; import org.apache.cassandra.db.marshal.AsciiType; import org.apache.cassandra.db.rows.*; @@ -48,6 +49,7 @@ import org.apache.cassandra.locator.TokenMetadata; import org.apache.cassandra.metrics.ClearableHistogram; import org.apache.cassandra.schema.CachingParams; import org.apache.cassandra.schema.KeyspaceParams; +import org.apache.cassandra.schema.SchemaTestUtil; import org.apache.cassandra.service.CacheService; import org.apache.cassandra.service.StorageService; import org.apache.cassandra.utils.ByteBufferUtil; @@ -367,7 +369,9 @@ public class RowCacheTest CacheService.instance.setRowCacheCapacityInMB(1); rowCacheLoad(100, 50, 0); CacheService.instance.rowCache.submitWrite(Integer.MAX_VALUE).get(); - Keyspace instance = Schema.instance.maybeRemoveKeyspaceInstance(KEYSPACE_CACHED); + + KeyspaceMetadata ksm = Schema.instance.getKeyspaceMetadata(KEYSPACE_CACHED); + SchemaTestUtil.dropKeyspaceIfExist(KEYSPACE_CACHED, true); try { CacheService.instance.rowCache.size(); @@ -378,7 +382,7 @@ public class RowCacheTest } finally { - Schema.instance.maybeAddKeyspaceInstance(instance.getName(), () -> instance); + SchemaTestUtil.addOrUpdateKeyspace(ksm, true); } } diff --git a/test/unit/org/apache/cassandra/db/SchemaCQLHelperTest.java b/test/unit/org/apache/cassandra/db/SchemaCQLHelperTest.java index 03aa32de94..ae434c8d29 100644 --- a/test/unit/org/apache/cassandra/db/SchemaCQLHelperTest.java +++ b/test/unit/org/apache/cassandra/db/SchemaCQLHelperTest.java @@ -167,7 +167,7 @@ public class SchemaCQLHelperTest extends CQLTester " reg2 varint,\n" + " st1 varint static,\n" + " PRIMARY KEY (pk1, ck1)\n) WITH ID ="; - String actual = SchemaCQLHelper.getTableMetadataAsCQL(cfs.metadata()); + String actual = SchemaCQLHelper.getTableMetadataAsCQL(cfs.metadata(), cfs.keyspace.getMetadata()); assertThat(actual, allOf(startsWith(expected), @@ -208,7 +208,7 @@ public class SchemaCQLHelperTest extends CQLTester ColumnFamilyStore cfs = Keyspace.open(keyspace).getColumnFamilyStore(table); // when re-adding, column is present as both column and as dropped column record. - String actual = SchemaCQLHelper.getTableMetadataAsCQL(cfs.metadata()); + String actual = SchemaCQLHelper.getTableMetadataAsCQL(cfs.metadata(), cfs.keyspace.getMetadata()); String expected = "CREATE TABLE IF NOT EXISTS cql_test_keyspace_readded_columns.test_table_readded_columns (\n" + " pk1 varint,\n" + " ck1 varint,\n" + @@ -247,7 +247,7 @@ public class SchemaCQLHelperTest extends CQLTester ColumnFamilyStore cfs = Keyspace.open(keyspace).getColumnFamilyStore(table); - assertThat(SchemaCQLHelper.getTableMetadataAsCQL(cfs.metadata()), + assertThat(SchemaCQLHelper.getTableMetadataAsCQL(cfs.metadata(), cfs.keyspace.getMetadata()), startsWith( "CREATE TABLE IF NOT EXISTS cql_test_keyspace_create_table.test_table_create_table (\n" + " pk1 varint,\n" + @@ -294,7 +294,7 @@ public class SchemaCQLHelperTest extends CQLTester ColumnFamilyStore cfs = Keyspace.open(keyspace).getColumnFamilyStore(table); - assertThat(SchemaCQLHelper.getTableMetadataAsCQL(cfs.metadata()), + assertThat(SchemaCQLHelper.getTableMetadataAsCQL(cfs.metadata(), cfs.keyspace.getMetadata()), containsString("CLUSTERING ORDER BY (cl1 ASC)\n" + " AND additional_write_policy = 'ALWAYS'\n" + " AND bloom_filter_fp_chance = 1.0\n" + diff --git a/test/unit/org/apache/cassandra/db/SecondaryIndexTest.java b/test/unit/org/apache/cassandra/db/SecondaryIndexTest.java index e7bfb120bd..544196f4e9 100644 --- a/test/unit/org/apache/cassandra/db/SecondaryIndexTest.java +++ b/test/unit/org/apache/cassandra/db/SecondaryIndexTest.java @@ -40,8 +40,8 @@ import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.index.Index; import org.apache.cassandra.schema.IndexMetadata; import org.apache.cassandra.schema.KeyspaceParams; +import org.apache.cassandra.schema.SchemaTestUtil; import org.apache.cassandra.schema.TableMetadata; -import org.apache.cassandra.schema.MigrationManager; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.FBUtilities; @@ -482,7 +482,7 @@ public class SecondaryIndexTest current.unbuild() .indexes(current.indexes.with(indexDef)) .build(); - MigrationManager.announceTableUpdate(updated, true); + SchemaTestUtil.announceTableUpdate(updated); // wait for the index to be built Index index = cfs.indexManager.getIndex(indexDef); diff --git a/test/unit/org/apache/cassandra/db/SystemKeyspaceTest.java b/test/unit/org/apache/cassandra/db/SystemKeyspaceTest.java index 74565ad835..bf59b62a39 100644 --- a/test/unit/org/apache/cassandra/db/SystemKeyspaceTest.java +++ b/test/unit/org/apache/cassandra/db/SystemKeyspaceTest.java @@ -25,18 +25,18 @@ import org.junit.BeforeClass; import org.junit.Test; import org.apache.cassandra.config.DatabaseDescriptor; -import org.apache.cassandra.db.commitlog.CommitLog; -import org.apache.cassandra.locator.InetAddressAndPort; -import org.apache.cassandra.schema.Schema; -import org.apache.cassandra.schema.SchemaConstants; import org.apache.cassandra.cql3.QueryProcessor; import org.apache.cassandra.cql3.UntypedResultSet; +import org.apache.cassandra.db.commitlog.CommitLog; import org.apache.cassandra.dht.ByteOrderedPartitioner.BytesToken; import org.apache.cassandra.dht.Token; +import org.apache.cassandra.locator.InetAddressAndPort; +import org.apache.cassandra.schema.SchemaConstants; +import org.apache.cassandra.schema.SchemaKeyspace; import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.utils.ByteBufferUtil; -import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.CassandraVersion; +import org.apache.cassandra.utils.FBUtilities; import static java.lang.String.format; import static org.apache.cassandra.cql3.QueryProcessor.executeInternal; @@ -121,7 +121,7 @@ public class SystemKeyspaceTest Set snapshottedSystemTables = getSystemSnapshotFiles(SchemaConstants.SYSTEM_KEYSPACE_NAME); SystemKeyspace.metadata().tables.forEach(t -> assertTrue(snapshottedSystemTables.contains(t.name))); Set snapshottedSchemaTables = getSystemSnapshotFiles(SchemaConstants.SCHEMA_KEYSPACE_NAME); - Schema.getSystemKeyspaceMetadata().tables.forEach(t -> assertTrue(snapshottedSchemaTables.contains(t.name))); + SchemaKeyspace.metadata().tables.forEach(t -> assertTrue(snapshottedSchemaTables.contains(t.name))); // clear out the snapshots & set the previous recorded version equal to the latest, we shouldn't // see any new snapshots created this time. diff --git a/test/unit/org/apache/cassandra/db/commitlog/CommitLogTest.java b/test/unit/org/apache/cassandra/db/commitlog/CommitLogTest.java index a70d06c53d..cd66d0a624 100644 --- a/test/unit/org/apache/cassandra/db/commitlog/CommitLogTest.java +++ b/test/unit/org/apache/cassandra/db/commitlog/CommitLogTest.java @@ -160,8 +160,8 @@ public abstract class CommitLogTest custom); SchemaLoader.createKeyspace(KEYSPACE2, KeyspaceParams.simpleTransient(1), - SchemaLoader.standardCFMD(KEYSPACE1, STANDARD1, 0, AsciiType.instance, BytesType.instance), - SchemaLoader.standardCFMD(KEYSPACE1, STANDARD2, 0, AsciiType.instance, BytesType.instance)); + SchemaLoader.standardCFMD(KEYSPACE2, STANDARD1, 0, AsciiType.instance, BytesType.instance), + SchemaLoader.standardCFMD(KEYSPACE2, STANDARD2, 0, AsciiType.instance, BytesType.instance)); CompactionManager.instance.disableAutoCompaction(); testKiller = new KillerForTests(); diff --git a/test/unit/org/apache/cassandra/db/commitlog/CommitLogUpgradeTest.java b/test/unit/org/apache/cassandra/db/commitlog/CommitLogUpgradeTest.java index 3228a5c72d..96b32d255b 100644 --- a/test/unit/org/apache/cassandra/db/commitlog/CommitLogUpgradeTest.java +++ b/test/unit/org/apache/cassandra/db/commitlog/CommitLogUpgradeTest.java @@ -42,6 +42,7 @@ import org.apache.cassandra.schema.TableId; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.schema.Schema; +import org.apache.cassandra.schema.SchemaTestUtil; import org.apache.cassandra.db.Mutation; import org.apache.cassandra.db.rows.Cell; import org.apache.cassandra.db.rows.Row; @@ -127,7 +128,10 @@ public class CommitLogUpgradeTest { TableId tableId = TableId.fromString(cfidString); if (Schema.instance.getTableMetadata(tableId) == null) - Schema.instance.load(KeyspaceMetadata.create(KEYSPACE, KeyspaceParams.simple(1), Tables.of(metadata.unbuild().id(tableId).build()))); + SchemaTestUtil.addOrUpdateKeyspace(KeyspaceMetadata.create(KEYSPACE, + KeyspaceParams.simple(1), + Tables.of(metadata.unbuild().id(tableId).build())), + true); } Hasher hasher = new Hasher(); diff --git a/test/unit/org/apache/cassandra/db/compaction/CompactionTaskTest.java b/test/unit/org/apache/cassandra/db/compaction/CompactionTaskTest.java index e1797883c2..4698e6e0a7 100644 --- a/test/unit/org/apache/cassandra/db/compaction/CompactionTaskTest.java +++ b/test/unit/org/apache/cassandra/db/compaction/CompactionTaskTest.java @@ -55,7 +55,7 @@ public class CompactionTaskTest public static void setUpClass() throws Exception { SchemaLoader.prepareServer(); - cfm = CreateTableStatement.parse("CREATE TABLE tbl (k INT PRIMARY KEY, v INT)", "coordinatorsessiontest").build(); + cfm = CreateTableStatement.parse("CREATE TABLE tbl (k INT PRIMARY KEY, v INT)", "ks").build(); SchemaLoader.createKeyspace("ks", KeyspaceParams.simple(1), cfm); cfs = Schema.instance.getColumnFamilyStoreInstance(cfm.id); } diff --git a/test/unit/org/apache/cassandra/db/compaction/CompactionsBytemanTest.java b/test/unit/org/apache/cassandra/db/compaction/CompactionsBytemanTest.java index baa8206359..6ea05b2ce6 100644 --- a/test/unit/org/apache/cassandra/db/compaction/CompactionsBytemanTest.java +++ b/test/unit/org/apache/cassandra/db/compaction/CompactionsBytemanTest.java @@ -35,6 +35,7 @@ import org.apache.cassandra.dht.Range; import org.apache.cassandra.dht.Token; import org.apache.cassandra.io.sstable.Descriptor; import org.apache.cassandra.utils.FBUtilities; +import org.apache.cassandra.utils.Throwables; import org.jboss.byteman.contrib.bmunit.BMRule; import org.jboss.byteman.contrib.bmunit.BMRules; import org.jboss.byteman.contrib.bmunit.BMUnitRunner; @@ -216,7 +217,7 @@ public class CompactionsBytemanTest extends CQLTester } catch (RuntimeException t) { - if (!(t.getCause().getCause() instanceof CompactionInterruptedException)) + if (!Throwables.isCausedBy(t, CompactionInterruptedException.class::isInstance)) throw t; //expected } diff --git a/test/unit/org/apache/cassandra/db/compaction/CompactionsTest.java b/test/unit/org/apache/cassandra/db/compaction/CompactionsTest.java index 5a94f078e5..ae3063051f 100644 --- a/test/unit/org/apache/cassandra/db/compaction/CompactionsTest.java +++ b/test/unit/org/apache/cassandra/db/compaction/CompactionsTest.java @@ -68,7 +68,7 @@ import org.apache.cassandra.io.sstable.metadata.StatsMetadata; import org.apache.cassandra.io.util.File; import org.apache.cassandra.schema.CompactionParams; import org.apache.cassandra.schema.KeyspaceParams; -import org.apache.cassandra.schema.MigrationManager; +import org.apache.cassandra.schema.SchemaTestUtil; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.FBUtilities; @@ -131,7 +131,7 @@ public class CompactionsTest Keyspace keyspace = Keyspace.open(KEYSPACE1); ColumnFamilyStore store = keyspace.getColumnFamilyStore(CF_STANDARD1); store.clearUnsafe(); - MigrationManager.announceTableUpdate(store.metadata().unbuild().gcGraceSeconds(1).build(), true); + SchemaTestUtil.announceTableUpdate(store.metadata().unbuild().gcGraceSeconds(1).build()); // disable compaction while flushing store.disableAutoCompaction(); @@ -173,7 +173,7 @@ public class CompactionsTest ColumnFamilyStore store = keyspace.getColumnFamilyStore(CF_STANDARD1); store.clearUnsafe(); - MigrationManager.announceTableUpdate(store.metadata().unbuild().gcGraceSeconds(1).compaction(CompactionParams.stcs(compactionOptions)).build(), true); + SchemaTestUtil.announceTableUpdate(store.metadata().unbuild().gcGraceSeconds(1).compaction(CompactionParams.stcs(compactionOptions)).build()); // disable compaction while flushing store.disableAutoCompaction(); @@ -216,7 +216,7 @@ public class CompactionsTest // now let's enable the magic property compactionOptions.put("unchecked_tombstone_compaction", "true"); - MigrationManager.announceTableUpdate(store.metadata().unbuild().gcGraceSeconds(1).compaction(CompactionParams.stcs(compactionOptions)).build(), true); + SchemaTestUtil.announceTableUpdate(store.metadata().unbuild().gcGraceSeconds(1).compaction(CompactionParams.stcs(compactionOptions)).build()); //submit background task again and wait for it to complete FBUtilities.waitOnFutures(CompactionManager.instance.submitBackground(store)); diff --git a/test/unit/org/apache/cassandra/db/compaction/TTLExpiryTest.java b/test/unit/org/apache/cassandra/db/compaction/TTLExpiryTest.java index c20316a7f3..bf95dd712e 100644 --- a/test/unit/org/apache/cassandra/db/compaction/TTLExpiryTest.java +++ b/test/unit/org/apache/cassandra/db/compaction/TTLExpiryTest.java @@ -29,6 +29,7 @@ import org.junit.Test; import org.apache.cassandra.SchemaLoader; import org.apache.cassandra.Util; +import org.apache.cassandra.schema.SchemaTestUtil; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.db.*; import org.apache.cassandra.db.filter.ColumnFilter; @@ -40,7 +41,6 @@ import org.apache.cassandra.io.sstable.format.SSTableReadsListener; import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.io.sstable.ISSTableScanner; import org.apache.cassandra.schema.KeyspaceParams; -import org.apache.cassandra.schema.MigrationManager; import org.apache.cassandra.tools.SSTableExpiredBlockers; import org.apache.cassandra.utils.ByteBufferUtil; @@ -80,7 +80,7 @@ public class TTLExpiryTest { ColumnFamilyStore cfs = Keyspace.open(KEYSPACE1).getColumnFamilyStore("Standard1"); cfs.disableAutoCompaction(); - MigrationManager.announceTableUpdate(cfs.metadata().unbuild().gcGraceSeconds(0).build(), true); + SchemaTestUtil.announceTableUpdate(cfs.metadata().unbuild().gcGraceSeconds(0).build()); String key = "ttl"; new RowUpdateBuilder(cfs.metadata(), 1L, 1, key) .add("col1", ByteBufferUtil.EMPTY_BYTE_BUFFER) @@ -161,7 +161,7 @@ public class TTLExpiryTest cfs.truncateBlocking(); cfs.disableAutoCompaction(); // To reproduce #10944, we need our gcBefore to be equal to the locaDeletionTime. A gcGrace of 1 will (almost always) give us that. - MigrationManager.announceTableUpdate(cfs.metadata().unbuild().gcGraceSeconds(force10944Bug ? 1 : 0).build(), true); + SchemaTestUtil.announceTableUpdate(cfs.metadata().unbuild().gcGraceSeconds(force10944Bug ? 1 : 0).build()); long timestamp = System.currentTimeMillis(); String key = "ttl"; new RowUpdateBuilder(cfs.metadata(), timestamp, 1, key) @@ -209,7 +209,7 @@ public class TTLExpiryTest ColumnFamilyStore cfs = Keyspace.open(KEYSPACE1).getColumnFamilyStore("Standard1"); cfs.truncateBlocking(); cfs.disableAutoCompaction(); - MigrationManager.announceTableUpdate(cfs.metadata().unbuild().gcGraceSeconds(0).build(), true); + SchemaTestUtil.announceTableUpdate(cfs.metadata().unbuild().gcGraceSeconds(0).build()); long timestamp = System.currentTimeMillis(); String key = "ttl"; new RowUpdateBuilder(cfs.metadata(), timestamp, 1, key) @@ -259,7 +259,7 @@ public class TTLExpiryTest ColumnFamilyStore cfs = Keyspace.open(KEYSPACE1).getColumnFamilyStore("Standard1"); cfs.truncateBlocking(); cfs.disableAutoCompaction(); - MigrationManager.announceTableUpdate(cfs.metadata().unbuild().gcGraceSeconds(0).build(), true); + SchemaTestUtil.announceTableUpdate(cfs.metadata().unbuild().gcGraceSeconds(0).build()); new RowUpdateBuilder(cfs.metadata(), System.currentTimeMillis(), "test") .noRowMarker() diff --git a/test/unit/org/apache/cassandra/db/streaming/EntireSSTableStreamConcurrentComponentMutationTest.java b/test/unit/org/apache/cassandra/db/streaming/EntireSSTableStreamConcurrentComponentMutationTest.java index 061bfb3735..9eb3e14be1 100644 --- a/test/unit/org/apache/cassandra/db/streaming/EntireSSTableStreamConcurrentComponentMutationTest.java +++ b/test/unit/org/apache/cassandra/db/streaming/EntireSSTableStreamConcurrentComponentMutationTest.java @@ -67,7 +67,7 @@ import org.apache.cassandra.net.BufferPoolAllocator; import org.apache.cassandra.net.MessagingService; import org.apache.cassandra.net.SharedDefaultFileRegion; import org.apache.cassandra.schema.KeyspaceParams; -import org.apache.cassandra.schema.MigrationManager; +import org.apache.cassandra.schema.SchemaTestUtil; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.service.ActiveRepairService; import org.apache.cassandra.streaming.async.NettyStreamingConnectionFactory; @@ -247,7 +247,7 @@ public class EntireSSTableStreamConcurrentComponentMutationTest // rewrite index summary file with new min/max index interval TableMetadata origin = store.metadata(); - MigrationManager.announceTableUpdate(origin.unbuild().minIndexInterval(1).maxIndexInterval(2).build(), true); + SchemaTestUtil.announceTableUpdate(origin.unbuild().minIndexInterval(1).maxIndexInterval(2).build()); try (LifecycleTransaction txn = store.getTracker().tryModify(sstable, OperationType.INDEX_SUMMARY)) { @@ -257,7 +257,7 @@ public class EntireSSTableStreamConcurrentComponentMutationTest } // reset min/max index interval - MigrationManager.announceTableUpdate(origin, true); + SchemaTestUtil.announceTableUpdate(origin); return true; } diff --git a/test/unit/org/apache/cassandra/db/view/ViewUtilsTest.java b/test/unit/org/apache/cassandra/db/view/ViewUtilsTest.java index b37cfdef25..e0ef3d6887 100644 --- a/test/unit/org/apache/cassandra/db/view/ViewUtilsTest.java +++ b/test/unit/org/apache/cassandra/db/view/ViewUtilsTest.java @@ -18,36 +18,41 @@ package org.apache.cassandra.db.view; +import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Optional; +import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; -import org.junit.Assert; +import org.apache.cassandra.ServerTestUtils; import org.apache.cassandra.config.DatabaseDescriptor; -import org.apache.cassandra.locator.InetAddressAndPort; -import org.apache.cassandra.locator.Replica; -import org.apache.cassandra.schema.Schema; import org.apache.cassandra.db.Keyspace; import org.apache.cassandra.dht.OrderPreservingPartitioner.StringToken; import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.locator.IEndpointSnitch; +import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.locator.NetworkTopologyStrategy; import org.apache.cassandra.locator.PropertyFileSnitch; +import org.apache.cassandra.locator.Replica; import org.apache.cassandra.locator.TokenMetadata; import org.apache.cassandra.schema.KeyspaceMetadata; import org.apache.cassandra.schema.KeyspaceParams; import org.apache.cassandra.schema.ReplicationParams; +import org.apache.cassandra.schema.SchemaTestUtil; import org.apache.cassandra.service.StorageService; public class ViewUtilsTest { + private final String KS = "Keyspace1"; + @BeforeClass - public static void setUp() throws ConfigurationException + public static void setUp() throws ConfigurationException, IOException { DatabaseDescriptor.daemonInitialization(); + ServerTestUtils.cleanupAndLeaveDirs(); IEndpointSnitch snitch = new PropertyFileSnitch(); DatabaseDescriptor.setEndpointSnitch(snitch); Keyspace.setInitialized(); @@ -73,11 +78,9 @@ public class ViewUtilsTest replicationMap.put("DC1", "1"); replicationMap.put("DC2", "1"); - Schema.instance.maybeRemoveKeyspaceInstance("Keyspace1"); - KeyspaceMetadata meta = KeyspaceMetadata.create("Keyspace1", KeyspaceParams.create(false, replicationMap)); - Schema.instance.load(meta); + recreateKeyspace(replicationMap); - Optional naturalEndpoint = ViewUtils.getViewNaturalEndpoint(Keyspace.open("Keyspace1").getReplicationStrategy(), + Optional naturalEndpoint = ViewUtils.getViewNaturalEndpoint(Keyspace.open(KS).getReplicationStrategy(), new StringToken("CA"), new StringToken("BB")); @@ -106,11 +109,9 @@ public class ViewUtilsTest replicationMap.put("DC1", "2"); replicationMap.put("DC2", "2"); - Schema.instance.maybeRemoveKeyspaceInstance("Keyspace1"); - KeyspaceMetadata meta = KeyspaceMetadata.create("Keyspace1", KeyspaceParams.create(false, replicationMap)); - Schema.instance.load(meta); + recreateKeyspace(replicationMap); - Optional naturalEndpoint = ViewUtils.getViewNaturalEndpoint(Keyspace.open("Keyspace1").getReplicationStrategy(), + Optional naturalEndpoint = ViewUtils.getViewNaturalEndpoint(Keyspace.open(KS).getReplicationStrategy(), new StringToken("CA"), new StringToken("BB")); @@ -138,14 +139,19 @@ public class ViewUtilsTest replicationMap.put("DC1", "1"); replicationMap.put("DC2", "1"); - Schema.instance.maybeRemoveKeyspaceInstance("Keyspace1"); - KeyspaceMetadata meta = KeyspaceMetadata.create("Keyspace1", KeyspaceParams.create(false, replicationMap)); - Schema.instance.load(meta); + recreateKeyspace(replicationMap); - Optional naturalEndpoint = ViewUtils.getViewNaturalEndpoint(Keyspace.open("Keyspace1").getReplicationStrategy(), + Optional naturalEndpoint = ViewUtils.getViewNaturalEndpoint(Keyspace.open(KS).getReplicationStrategy(), new StringToken("AB"), new StringToken("BB")); Assert.assertFalse(naturalEndpoint.isPresent()); } + + private void recreateKeyspace(Map replicationMap) + { + SchemaTestUtil.dropKeyspaceIfExist(KS, true); + KeyspaceMetadata meta = KeyspaceMetadata.create(KS, KeyspaceParams.create(false, replicationMap)); + SchemaTestUtil.addOrUpdateKeyspace(meta, true); + } } diff --git a/test/unit/org/apache/cassandra/dht/BootStrapperTest.java b/test/unit/org/apache/cassandra/dht/BootStrapperTest.java index 05d42cf32c..395ff4072f 100644 --- a/test/unit/org/apache/cassandra/dht/BootStrapperTest.java +++ b/test/unit/org/apache/cassandra/dht/BootStrapperTest.java @@ -74,7 +74,7 @@ public class BootStrapperTest public void testSourceTargetComputation() throws UnknownHostException { final int[] clusterSizes = new int[] { 1, 3, 5, 10, 100}; - for (String keyspaceName : Schema.instance.getNonLocalStrategyKeyspaces()) + for (String keyspaceName : Schema.instance.getNonLocalStrategyKeyspaces().names()) { int replicationFactor = Keyspace.open(keyspaceName).getReplicationStrategy().getReplicationFactor().allReplicas; for (int clusterSize : clusterSizes) diff --git a/test/unit/org/apache/cassandra/hints/HintTest.java b/test/unit/org/apache/cassandra/hints/HintTest.java index 4933d37f1c..67e969b32c 100644 --- a/test/unit/org/apache/cassandra/hints/HintTest.java +++ b/test/unit/org/apache/cassandra/hints/HintTest.java @@ -46,8 +46,8 @@ import org.apache.cassandra.net.MessagingService; import org.apache.cassandra.schema.ColumnMetadata; import org.apache.cassandra.schema.KeyspaceParams; import org.apache.cassandra.schema.Schema; +import org.apache.cassandra.schema.SchemaTestUtil; import org.apache.cassandra.schema.TableMetadata; -import org.apache.cassandra.schema.MigrationManager; import org.apache.cassandra.service.StorageProxy; import org.apache.cassandra.service.StorageService; import org.apache.cassandra.utils.FBUtilities; @@ -87,7 +87,7 @@ public class HintTest tokenMeta.updateNormalTokens(BootStrapper.getRandomTokens(tokenMeta, 1), local); for (TableMetadata table : Schema.instance.getTablesAndViews(KEYSPACE)) - MigrationManager.announceTableUpdate(table.unbuild().gcGraceSeconds(864000).build(), true); + SchemaTestUtil.announceTableUpdate(table.unbuild().gcGraceSeconds(864000).build()); } @Test @@ -177,7 +177,7 @@ public class HintTest .unbuild() .gcGraceSeconds(0) .build(); - MigrationManager.announceTableUpdate(updated, true); + SchemaTestUtil.announceTableUpdate(updated); Mutation mutation = createMutation(key, now); Hint.create(mutation, now / 1000).apply(); @@ -206,7 +206,7 @@ public class HintTest .unbuild() .gcGraceSeconds(0) .build(); - MigrationManager.announceTableUpdate(updated, true); + SchemaTestUtil.announceTableUpdate(updated); Mutation mutation = createMutation(key, now); Hint hint = Hint.create(mutation, now / 1000); diff --git a/test/unit/org/apache/cassandra/hints/HintsReaderTest.java b/test/unit/org/apache/cassandra/hints/HintsReaderTest.java index 41f86a0ed2..249772bb8e 100644 --- a/test/unit/org/apache/cassandra/hints/HintsReaderTest.java +++ b/test/unit/org/apache/cassandra/hints/HintsReaderTest.java @@ -43,8 +43,8 @@ import org.apache.cassandra.io.FSReadError; import org.apache.cassandra.io.util.DataInputBuffer; import org.apache.cassandra.io.util.FileUtils; import org.apache.cassandra.schema.KeyspaceParams; -import org.apache.cassandra.schema.MigrationManager; import org.apache.cassandra.schema.Schema; +import org.apache.cassandra.schema.SchemaTestUtil; import org.apache.cassandra.schema.TableMetadata; import static junit.framework.Assert.assertEquals; @@ -244,7 +244,7 @@ public class HintsReaderTest try { generateHints(3, ks); - MigrationManager.announceTableDrop(ks, CF_STANDARD1, true); + SchemaTestUtil.announceTableDrop(ks, CF_STANDARD1); readHints(3, 1); } finally diff --git a/test/unit/org/apache/cassandra/index/sasi/disk/PerSSTableIndexWriterTest.java b/test/unit/org/apache/cassandra/index/sasi/disk/PerSSTableIndexWriterTest.java index da0dbde750..ad0caff44f 100644 --- a/test/unit/org/apache/cassandra/index/sasi/disk/PerSSTableIndexWriterTest.java +++ b/test/unit/org/apache/cassandra/index/sasi/disk/PerSSTableIndexWriterTest.java @@ -44,9 +44,9 @@ import org.apache.cassandra.io.util.FileUtils; import org.apache.cassandra.schema.ColumnMetadata; import org.apache.cassandra.schema.KeyspaceMetadata; import org.apache.cassandra.schema.KeyspaceParams; +import org.apache.cassandra.schema.SchemaTestUtil; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.schema.Tables; -import org.apache.cassandra.schema.MigrationManager; import org.apache.cassandra.utils.ByteBufferUtil; import com.google.common.util.concurrent.Futures; @@ -65,9 +65,9 @@ public class PerSSTableIndexWriterTest extends SchemaLoader { System.setProperty("cassandra.config", "cassandra-murmur.yaml"); SchemaLoader.loadSchema(); - MigrationManager.announceNewKeyspace(KeyspaceMetadata.create(KS_NAME, - KeyspaceParams.simpleTransient(1), - Tables.of(SchemaLoader.sasiCFMD(KS_NAME, CF_NAME).build()))); + SchemaTestUtil.announceNewKeyspace(KeyspaceMetadata.create(KS_NAME, + KeyspaceParams.simpleTransient(1), + Tables.of(SchemaLoader.sasiCFMD(KS_NAME, CF_NAME).build()))); } @Test diff --git a/test/unit/org/apache/cassandra/io/sstable/CQLSSTableWriterClientTest.java b/test/unit/org/apache/cassandra/io/sstable/CQLSSTableWriterClientTest.java index 778e8a7710..44b2399088 100644 --- a/test/unit/org/apache/cassandra/io/sstable/CQLSSTableWriterClientTest.java +++ b/test/unit/org/apache/cassandra/io/sstable/CQLSSTableWriterClientTest.java @@ -41,7 +41,8 @@ public class CQLSSTableWriterClientTest public void setUp() { this.testDirectory = new File(Files.createTempDir()); - DatabaseDescriptor.daemonInitialization(); + DatabaseDescriptor.setDefaultFailureDetector(); + DatabaseDescriptor.clientInitialization(); } @Test diff --git a/test/unit/org/apache/cassandra/io/sstable/IndexSummaryManagerTest.java b/test/unit/org/apache/cassandra/io/sstable/IndexSummaryManagerTest.java index c98ba2ceb3..f7cf6e9311 100644 --- a/test/unit/org/apache/cassandra/io/sstable/IndexSummaryManagerTest.java +++ b/test/unit/org/apache/cassandra/io/sstable/IndexSummaryManagerTest.java @@ -50,7 +50,7 @@ import org.apache.cassandra.io.sstable.format.SSTableReader; import org.apache.cassandra.metrics.RestorableMeter; import org.apache.cassandra.schema.CachingParams; import org.apache.cassandra.schema.KeyspaceParams; -import org.apache.cassandra.schema.MigrationManager; +import org.apache.cassandra.schema.SchemaTestUtil; import org.apache.cassandra.schema.TableId; import org.apache.cassandra.utils.ByteBufferUtil; @@ -121,8 +121,8 @@ public class IndexSummaryManagerTest Keyspace keyspace = Keyspace.open(ksname); ColumnFamilyStore cfs = keyspace.getColumnFamilyStore(cfname); - MigrationManager.announceTableUpdate(cfs.metadata().unbuild().minIndexInterval(originalMinIndexInterval).build(), true); - MigrationManager.announceTableUpdate(cfs.metadata().unbuild().maxIndexInterval(originalMaxIndexInterval).build(), true); + SchemaTestUtil.announceTableUpdate(cfs.metadata().unbuild().minIndexInterval(originalMinIndexInterval).build()); + SchemaTestUtil.announceTableUpdate(cfs.metadata().unbuild().maxIndexInterval(originalMaxIndexInterval).build()); IndexSummaryManager.instance.setMemoryPoolCapacityInMB(originalCapacity); } @@ -227,7 +227,7 @@ public class IndexSummaryManagerTest assertEquals(cfs.metadata().params.minIndexInterval, sstable.getEffectiveIndexInterval(), 0.001); // double the min_index_interval - MigrationManager.announceTableUpdate(cfs.metadata().unbuild().minIndexInterval(originalMinIndexInterval * 2).build(), true); + SchemaTestUtil.announceTableUpdate(cfs.metadata().unbuild().minIndexInterval(originalMinIndexInterval * 2).build()); IndexSummaryManager.instance.redistributeSummaries(); for (SSTableReader sstable : cfs.getLiveSSTables()) { @@ -236,7 +236,7 @@ public class IndexSummaryManagerTest } // return min_index_interval to its original value - MigrationManager.announceTableUpdate(cfs.metadata().unbuild().minIndexInterval(originalMinIndexInterval).build(), true); + SchemaTestUtil.announceTableUpdate(cfs.metadata().unbuild().minIndexInterval(originalMinIndexInterval).build()); IndexSummaryManager.instance.redistributeSummaries(); for (SSTableReader sstable : cfs.getLiveSSTables()) { @@ -246,7 +246,7 @@ public class IndexSummaryManagerTest // halve the min_index_interval, but constrain the available space to exactly what we have now; as a result, // the summary shouldn't change - MigrationManager.announceTableUpdate(cfs.metadata().unbuild().minIndexInterval(originalMinIndexInterval / 2).build(), true); + SchemaTestUtil.announceTableUpdate(cfs.metadata().unbuild().minIndexInterval(originalMinIndexInterval / 2).build()); SSTableReader sstable = cfs.getLiveSSTables().iterator().next(); long summarySpace = sstable.getIndexSummaryOffHeapSize(); try (LifecycleTransaction txn = cfs.getTracker().tryModify(asList(sstable), OperationType.UNKNOWN)) @@ -271,7 +271,7 @@ public class IndexSummaryManagerTest // return min_index_interval to it's original value (double it), but only give the summary enough space // to have an effective index interval of twice the new min - MigrationManager.announceTableUpdate(cfs.metadata().unbuild().minIndexInterval(originalMinIndexInterval).build(), true); + SchemaTestUtil.announceTableUpdate(cfs.metadata().unbuild().minIndexInterval(originalMinIndexInterval).build()); try (LifecycleTransaction txn = cfs.getTracker().tryModify(asList(sstable), OperationType.UNKNOWN)) { redistributeSummaries(Collections.EMPTY_LIST, of(cfs.metadata.id, txn), (long) Math.ceil(summarySpace / 2.0)); @@ -283,8 +283,8 @@ public class IndexSummaryManagerTest // raise the min_index_interval above our current effective interval, but set the max_index_interval lower // than what we actually have space for (meaning the index summary would ideally be smaller, but this would // result in an effective interval above the new max) - MigrationManager.announceTableUpdate(cfs.metadata().unbuild().minIndexInterval(originalMinIndexInterval * 4).build(), true); - MigrationManager.announceTableUpdate(cfs.metadata().unbuild().maxIndexInterval(originalMinIndexInterval * 4).build(), true); + SchemaTestUtil.announceTableUpdate(cfs.metadata().unbuild().minIndexInterval(originalMinIndexInterval * 4).build()); + SchemaTestUtil.announceTableUpdate(cfs.metadata().unbuild().maxIndexInterval(originalMinIndexInterval * 4).build()); try (LifecycleTransaction txn = cfs.getTracker().tryModify(asList(sstable), OperationType.UNKNOWN)) { redistributeSummaries(Collections.EMPTY_LIST, of(cfs.metadata.id, txn), 10); @@ -317,7 +317,7 @@ public class IndexSummaryManagerTest assertEquals(cfs.metadata().params.maxIndexInterval, sstable.getEffectiveIndexInterval(), 0.01); // halve the max_index_interval - MigrationManager.announceTableUpdate(cfs.metadata().unbuild().maxIndexInterval(cfs.metadata().params.maxIndexInterval / 2).build(), true); + SchemaTestUtil.announceTableUpdate(cfs.metadata().unbuild().maxIndexInterval(cfs.metadata().params.maxIndexInterval / 2).build()); try (LifecycleTransaction txn = cfs.getTracker().tryModify(sstables, OperationType.UNKNOWN)) { redistributeSummaries(Collections.EMPTY_LIST, of(cfs.metadata.id, txn), 1); @@ -330,7 +330,7 @@ public class IndexSummaryManagerTest } // return max_index_interval to its original value - MigrationManager.announceTableUpdate(cfs.metadata().unbuild().maxIndexInterval(cfs.metadata().params.maxIndexInterval * 2).build(), true); + SchemaTestUtil.announceTableUpdate(cfs.metadata().unbuild().maxIndexInterval(cfs.metadata().params.maxIndexInterval * 2).build()); try (LifecycleTransaction txn = cfs.getTracker().tryModify(sstables, OperationType.UNKNOWN)) { redistributeSummaries(Collections.EMPTY_LIST, of(cfs.metadata.id, txn), 1); diff --git a/test/unit/org/apache/cassandra/io/sstable/IndexSummaryRedistributionTest.java b/test/unit/org/apache/cassandra/io/sstable/IndexSummaryRedistributionTest.java index 07a2212e8f..49872d5b15 100644 --- a/test/unit/org/apache/cassandra/io/sstable/IndexSummaryRedistributionTest.java +++ b/test/unit/org/apache/cassandra/io/sstable/IndexSummaryRedistributionTest.java @@ -38,7 +38,7 @@ import org.apache.cassandra.metrics.RestorableMeter; import org.apache.cassandra.metrics.StorageMetrics; import org.apache.cassandra.schema.CachingParams; import org.apache.cassandra.schema.KeyspaceParams; -import org.apache.cassandra.schema.MigrationManager; +import org.apache.cassandra.schema.SchemaTestUtil; import static org.junit.Assert.assertEquals; @@ -88,7 +88,7 @@ public class IndexSummaryRedistributionTest int originalMinIndexInterval = cfs.metadata().params.minIndexInterval; // double the min_index_interval - MigrationManager.announceTableUpdate(cfs.metadata().unbuild().minIndexInterval(originalMinIndexInterval * 2).build(), true); + SchemaTestUtil.announceTableUpdate(cfs.metadata().unbuild().minIndexInterval(originalMinIndexInterval * 2).build()); IndexSummaryManager.instance.redistributeSummaries(); long newSize = 0; diff --git a/test/unit/org/apache/cassandra/locator/AssureSufficientLiveNodesTest.java b/test/unit/org/apache/cassandra/locator/AssureSufficientLiveNodesTest.java index d5f62d7530..1903e15446 100644 --- a/test/unit/org/apache/cassandra/locator/AssureSufficientLiveNodesTest.java +++ b/test/unit/org/apache/cassandra/locator/AssureSufficientLiveNodesTest.java @@ -45,7 +45,7 @@ import org.apache.cassandra.dht.Token; import org.apache.cassandra.exceptions.UnavailableException; import org.apache.cassandra.schema.KeyspaceMetadata; import org.apache.cassandra.schema.KeyspaceParams; -import org.apache.cassandra.schema.MigrationManager; +import org.apache.cassandra.schema.SchemaTestUtil; import org.apache.cassandra.schema.Tables; import org.apache.cassandra.service.StorageService; import org.apache.cassandra.service.reads.NeverSpeculativeRetryPolicy; @@ -253,9 +253,9 @@ public class AssureSufficientLiveNodesTest Consumer test) throws Throwable { String keyspaceName = keyspaceNameGen.get(); - KeyspaceMetadata initKsMeta = KeyspaceMetadata.create(keyspaceName, init, Tables.of(SchemaLoader.standardCFMD("Foo", "Bar").build())); + KeyspaceMetadata initKsMeta = KeyspaceMetadata.create(keyspaceName, init, Tables.of(SchemaLoader.standardCFMD(keyspaceName, "Bar").build())); KeyspaceMetadata alterToKsMeta = initKsMeta.withSwapped(alterTo); - MigrationManager.announceNewKeyspace(initKsMeta, true); + SchemaTestUtil.announceNewKeyspace(initKsMeta); Keyspace racedKs = Keyspace.open(keyspaceName); ExecutorService es = Executors.newFixedThreadPool(2); try (AutoCloseable ignore = () -> { diff --git a/test/unit/org/apache/cassandra/locator/SimpleStrategyTest.java b/test/unit/org/apache/cassandra/locator/SimpleStrategyTest.java index 4c1ff26390..1665d01a33 100644 --- a/test/unit/org/apache/cassandra/locator/SimpleStrategyTest.java +++ b/test/unit/org/apache/cassandra/locator/SimpleStrategyTest.java @@ -19,6 +19,7 @@ package org.apache.cassandra.locator; import java.net.UnknownHostException; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -159,7 +160,7 @@ public class SimpleStrategyTest { TokenMetadata tmd; AbstractReplicationStrategy strategy; - for (String keyspaceName : Schema.instance.getNonLocalStrategyKeyspaces()) + for (String keyspaceName : Schema.instance.getNonLocalStrategyKeyspaces().names()) { tmd = new TokenMetadata(); strategy = getStrategy(keyspaceName, tmd, new SimpleSnitch()); @@ -214,7 +215,7 @@ public class SimpleStrategyTest tmd.addBootstrapToken(bsToken, bootstrapEndpoint); AbstractReplicationStrategy strategy = null; - for (String keyspaceName : Schema.instance.getNonLocalStrategyKeyspaces()) + for (String keyspaceName : Schema.instance.getNonLocalStrategyKeyspaces().names()) { strategy = getStrategy(keyspaceName, tmd, new SimpleSnitch()); diff --git a/test/unit/org/apache/cassandra/schema/MigrationCoordinatorTest.java b/test/unit/org/apache/cassandra/schema/MigrationCoordinatorTest.java index 19094c979d..b9980414a7 100644 --- a/test/unit/org/apache/cassandra/schema/MigrationCoordinatorTest.java +++ b/test/unit/org/apache/cassandra/schema/MigrationCoordinatorTest.java @@ -19,31 +19,57 @@ package org.apache.cassandra.schema; import java.net.UnknownHostException; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.LinkedList; -import java.util.List; import java.util.Queue; import java.util.Set; import java.util.UUID; import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; import com.google.common.collect.Iterables; import com.google.common.collect.Sets; -import com.google.common.util.concurrent.Futures; import org.junit.Assert; import org.junit.Test; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.apache.cassandra.concurrent.ImmediateExecutor; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.db.Mutation; +import org.apache.cassandra.exceptions.RequestFailureReason; +import org.apache.cassandra.gms.ApplicationState; +import org.apache.cassandra.gms.EndpointState; +import org.apache.cassandra.gms.Gossiper; +import org.apache.cassandra.gms.VersionedValue; import org.apache.cassandra.locator.InetAddressAndPort; +import org.apache.cassandra.metrics.MessagingMetrics; +import org.apache.cassandra.net.EndpointMessagingVersions; +import org.apache.cassandra.net.Message; +import org.apache.cassandra.net.MessagingService; +import org.apache.cassandra.net.RequestCallback; +import org.apache.cassandra.net.Verb; +import org.apache.cassandra.utils.FBUtilities; +import org.apache.cassandra.utils.Pair; import org.apache.cassandra.utils.concurrent.WaitQueue; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; +import org.mockito.internal.creation.MockSettingsImpl; import static com.google.common.util.concurrent.Futures.getUnchecked; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class MigrationCoordinatorTest { @@ -57,6 +83,8 @@ public class MigrationCoordinatorTest private static final UUID V1 = UUID.randomUUID(); private static final UUID V2 = UUID.randomUUID(); + private static final EndpointState validEndpointState = mock(EndpointState.class); + static { try @@ -71,105 +99,116 @@ public class MigrationCoordinatorTest } DatabaseDescriptor.daemonInitialization(); + + when(validEndpointState.getApplicationState(ApplicationState.RELEASE_VERSION)).thenReturn(VersionedValue.unsafeMakeVersionedValue(FBUtilities.getReleaseVersionString(), 0)); } - private static class InstrumentedCoordinator extends MigrationCoordinator + private static class Wrapper { + final Queue>>> requests = new LinkedList<>(); + final ScheduledExecutorService oneTimeExecutor = mock(ScheduledExecutorService.class); + final Gossiper gossiper = mock(Gossiper.class); + final Set mergedSchemasFrom = new HashSet<>(); + final EndpointMessagingVersions versions = mock(EndpointMessagingVersions.class); + final MessagingService messagingService = mock(MessagingService.class, new MockSettingsImpl<>().defaultAnswer(a -> { + throw new UnsupportedOperationException(); + }).useConstructor(true, versions, mock(MessagingMetrics.class))); - Queue requests = new LinkedList<>(); - @Override - protected void sendMigrationMessage(MigrationCoordinator.Callback callback) + UUID localSchemaVersion = LOCAL_VERSION; + + final MigrationCoordinator coordinator; + + private Wrapper() { - requests.add(callback); + this(3); } - boolean shouldPullSchema = true; - @Override - protected boolean shouldPullSchema(UUID version) + private Wrapper(int maxOutstandingRequests) { - return shouldPullSchema; + when(oneTimeExecutor.scheduleWithFixedDelay(any(), anyLong(), anyLong(), any())).thenAnswer(a -> { + a.getArgument(0, Runnable.class).run(); + return mock(ScheduledFuture.class); + }); + when(gossiper.getEndpointStateForEndpoint(any())).thenReturn(validEndpointState); + when(gossiper.isAlive(any())).thenReturn(true); + doAnswer(a -> requests.add(Pair.create(a.getArgument(1, InetAddressAndPort.class), a.getArgument(2, RequestCallback.class)))) + .when(messagingService) + .sendWithCallback(any(), any(), any()); + + when(versions.knows(any())).thenReturn(true); + when(versions.getRaw(any())).thenReturn(MessagingService.current_version); + this.coordinator = new MigrationCoordinator(messagingService, + ImmediateExecutor.INSTANCE, + oneTimeExecutor, + maxOutstandingRequests, + gossiper, + () -> localSchemaVersion, + (endpoint, ignored) -> mergedSchemasFrom.add(endpoint)); } - boolean shouldPullFromEndpoint = true; - @Override - protected boolean shouldPullFromEndpoint(InetAddressAndPort endpoint) + private InetAddressAndPort configureMocksForEndpoint(String endpointName, EndpointState es, Integer msgVersion, boolean gossipOnlyMember) throws UnknownHostException { - return shouldPullFromEndpoint; + InetAddressAndPort endpoint = InetAddressAndPort.getByName(endpointName); + return configureMocksForEndpoint(endpoint, es, msgVersion, gossipOnlyMember); } - boolean shouldPullImmediately = true; - @Override - protected boolean shouldPullImmediately(InetAddressAndPort endpoint, UUID version) + private InetAddressAndPort configureMocksForEndpoint(InetAddressAndPort endpoint, EndpointState es, Integer msgVersion, boolean gossipOnlyMember) throws UnknownHostException { - return shouldPullImmediately; - } + when(gossiper.getEndpointStateForEndpoint(endpoint)).thenReturn(es); + if (msgVersion == null) + { + when(versions.knows(endpoint)).thenReturn(false); + when(versions.getRaw(endpoint)).thenThrow(new IllegalArgumentException()); + } + else + { + when(versions.knows(endpoint)).thenReturn(true); + when(versions.getRaw(endpoint)).thenReturn(msgVersion); + } + when(gossiper.isGossipOnlyMember(endpoint)).thenReturn(gossipOnlyMember); + when(gossiper.isAlive(endpoint)).thenReturn(true); - Set deadNodes = new HashSet<>(); - protected boolean isAlive(InetAddressAndPort endpoint) - { - return !deadNodes.contains(endpoint); - } - - UUID localVersion = LOCAL_VERSION; - @Override - protected boolean isLocalVersion(UUID version) - { - return localVersion.equals(version); - } - - int maxOutstandingRequests = 3; - @Override - protected int getMaxOutstandingVersionRequests() - { - return maxOutstandingRequests; - } - - Set mergedSchemasFrom = new HashSet<>(); - @Override - protected void mergeSchemaFrom(InetAddressAndPort endpoint, Collection mutations) - { - mergedSchemasFrom.add(endpoint); + return endpoint; } } @Test public void requestResponseCycle() throws InterruptedException { - InstrumentedCoordinator coordinator = new InstrumentedCoordinator(); - coordinator.maxOutstandingRequests = 1; + Wrapper wrapper = new Wrapper(1); + MigrationCoordinator coordinator = wrapper.coordinator; - Assert.assertTrue(coordinator.requests.isEmpty()); + Assert.assertTrue(wrapper.requests.isEmpty()); // first schema report should send a migration request getUnchecked(coordinator.reportEndpointVersion(EP1, V1)); - Assert.assertEquals(1, coordinator.requests.size()); + Assert.assertEquals(1, wrapper.requests.size()); Assert.assertFalse(coordinator.awaitSchemaRequests(1)); // second should not getUnchecked(coordinator.reportEndpointVersion(EP2, V1)); - Assert.assertEquals(1, coordinator.requests.size()); + Assert.assertEquals(1, wrapper.requests.size()); Assert.assertFalse(coordinator.awaitSchemaRequests(1)); // until the first request fails, then the second endpoint should be contacted - MigrationCoordinator.Callback request1 = coordinator.requests.poll(); - Assert.assertEquals(EP1, request1.endpoint); - getUnchecked(request1.fail()); - Assert.assertTrue(coordinator.mergedSchemasFrom.isEmpty()); + Pair>> request1 = wrapper.requests.poll(); + Assert.assertEquals(EP1, request1.left); + request1.right.onFailure(null, null); + Assert.assertTrue(wrapper.mergedSchemasFrom.isEmpty()); Assert.assertFalse(coordinator.awaitSchemaRequests(1)); // ... then the second endpoint should be contacted - Assert.assertEquals(1, coordinator.requests.size()); - MigrationCoordinator.Callback request2 = coordinator.requests.poll(); - Assert.assertEquals(EP2, request2.endpoint); + Assert.assertEquals(1, wrapper.requests.size()); + Pair>> request2 = wrapper.requests.poll(); + Assert.assertEquals(EP2, request2.left); Assert.assertFalse(coordinator.awaitSchemaRequests(1)); - getUnchecked(request2.response(Collections.emptyList())); - Assert.assertEquals(EP2, Iterables.getOnlyElement(coordinator.mergedSchemasFrom)); + request2.right.onResponse(Message.remoteResponse(request2.left, Verb.SCHEMA_PULL_RSP, Collections.emptyList())); + Assert.assertEquals(EP2, Iterables.getOnlyElement(wrapper.mergedSchemasFrom)); Assert.assertTrue(coordinator.awaitSchemaRequests(1)); // and migration tasks should not be sent out for subsequent version reports getUnchecked(coordinator.reportEndpointVersion(EP3, V1)); - Assert.assertTrue(coordinator.requests.isEmpty()); - + Assert.assertTrue(wrapper.requests.isEmpty()); } /** @@ -179,14 +218,14 @@ public class MigrationCoordinatorTest @Test public void versionsAreSignaledWhenDeleted() { - InstrumentedCoordinator coordinator = new InstrumentedCoordinator(); + Wrapper wrapper = new Wrapper(); - coordinator.reportEndpointVersion(EP1, V1); - WaitQueue.Signal signal = coordinator.getVersionInfoUnsafe(V1).register(); + wrapper.coordinator.reportEndpointVersion(EP1, V1); + WaitQueue.Signal signal = wrapper.coordinator.getVersionInfoUnsafe(V1).register(); Assert.assertFalse(signal.isSignalled()); - coordinator.reportEndpointVersion(EP1, V2); - Assert.assertNull(coordinator.getVersionInfoUnsafe(V1)); + wrapper.coordinator.reportEndpointVersion(EP1, V2); + Assert.assertNull(wrapper.coordinator.getVersionInfoUnsafe(V1)); Assert.assertTrue(signal.isSignalled()); } @@ -199,31 +238,31 @@ public class MigrationCoordinatorTest @Test public void versionsAreSignaledWhenEndpointsRemoved() { - InstrumentedCoordinator coordinator = new InstrumentedCoordinator(); + Wrapper wrapper = new Wrapper(); - coordinator.reportEndpointVersion(EP1, V1); - WaitQueue.Signal signal = coordinator.getVersionInfoUnsafe(V1).register(); + wrapper.coordinator.reportEndpointVersion(EP1, V1); + WaitQueue.Signal signal = wrapper.coordinator.getVersionInfoUnsafe(V1).register(); Assert.assertFalse(signal.isSignalled()); - coordinator.removeAndIgnoreEndpoint(EP1); - Assert.assertNull(coordinator.getVersionInfoUnsafe(V1)); + wrapper.coordinator.removeAndIgnoreEndpoint(EP1); + Assert.assertNull(wrapper.coordinator.getVersionInfoUnsafe(V1)); Assert.assertTrue(signal.isSignalled()); } - private static void assertNoContact(InstrumentedCoordinator coordinator, InetAddressAndPort endpoint, UUID version, boolean startupShouldBeUnblocked) + private static void assertNoContact(Wrapper wrapper, InetAddressAndPort endpoint, UUID version, boolean startupShouldBeUnblocked) { - Assert.assertTrue(coordinator.requests.isEmpty()); - Future future = coordinator.reportEndpointVersion(EP1, V1); + Assert.assertTrue(wrapper.requests.isEmpty()); + Future future = wrapper.coordinator.reportEndpointVersion(EP1, V1); if (future != null) getUnchecked(future); - Assert.assertTrue(coordinator.requests.isEmpty()); + Assert.assertTrue(wrapper.requests.isEmpty()); - Assert.assertEquals(startupShouldBeUnblocked, coordinator.awaitSchemaRequests(1)); + Assert.assertEquals(startupShouldBeUnblocked, wrapper.coordinator.awaitSchemaRequests(1)); } - private static void assertNoContact(InstrumentedCoordinator coordinator, boolean startupShouldBeUnblocked) + private static void assertNoContact(Wrapper coordinator, boolean startupShouldBeUnblocked) { assertNoContact(coordinator, EP1, V1, startupShouldBeUnblocked); } @@ -231,91 +270,85 @@ public class MigrationCoordinatorTest @Test public void dontContactNodesWithSameSchema() { - InstrumentedCoordinator coordinator = new InstrumentedCoordinator(); + Wrapper wrapper = new Wrapper(); - coordinator.localVersion = V1; - assertNoContact(coordinator, true); + wrapper.localSchemaVersion = V1; + assertNoContact(wrapper, true); } @Test public void dontContactIncompatibleNodes() { - InstrumentedCoordinator coordinator = new InstrumentedCoordinator(); + Wrapper wrapper = new Wrapper(); - coordinator.shouldPullFromEndpoint = false; - assertNoContact(coordinator, false); + when(wrapper.gossiper.getEndpointStateForEndpoint(any())).thenReturn(null); // shouldPullFromEndpoint should return false in this case + assertNoContact(wrapper, false); } @Test public void dontContactDeadNodes() { - InstrumentedCoordinator coordinator = new InstrumentedCoordinator(); + Wrapper wrapper = new Wrapper(); - coordinator.deadNodes.add(EP1); - assertNoContact(coordinator, EP1, V1, false); + when(wrapper.gossiper.isAlive(ArgumentMatchers.eq(EP1))).thenReturn(false); + assertNoContact(wrapper, EP1, V1, false); } /** - * If a node has become incompativle between when the task was scheduled and when it + * If a node has become incompatible between when the task was scheduled and when it * was run, we should detect that and fail the task */ @Test public void testGossipRace() { - InstrumentedCoordinator coordinator = new InstrumentedCoordinator() { - protected boolean shouldPullImmediately(InetAddressAndPort endpoint, UUID version) - { - // this is the last thing that gets called before scheduling the pull, so set this flag here - shouldPullFromEndpoint = false; - return super.shouldPullImmediately(endpoint, version); - } - }; + Wrapper wrapper = new Wrapper(); + when(wrapper.gossiper.getEndpointStateForEndpoint(any())).thenReturn(validEndpointState, (EndpointState) null); - Assert.assertTrue(coordinator.shouldPullFromEndpoint(EP1)); - assertNoContact(coordinator, EP1, V1, false); + assertNoContact(wrapper, EP1, V1, false); } @Test public void testWeKeepSendingRequests() throws Exception { - InstrumentedCoordinator coordinator = new InstrumentedCoordinator(); + Wrapper wrapper = new Wrapper(); - getUnchecked(coordinator.reportEndpointVersion(EP3, V2)); - coordinator.requests.remove().response(Collections.emptyList()); + getUnchecked(wrapper.coordinator.reportEndpointVersion(EP3, V2)); + Pair>> cb = wrapper.requests.remove(); + cb.right.onResponse(Message.remoteResponse(cb.left, Verb.SCHEMA_PULL_RSP, Collections.emptyList())); - getUnchecked(coordinator.reportEndpointVersion(EP1, V1)); - getUnchecked(coordinator.reportEndpointVersion(EP2, V1)); + getUnchecked(wrapper.coordinator.reportEndpointVersion(EP1, V1)); + getUnchecked(wrapper.coordinator.reportEndpointVersion(EP2, V1)); - MigrationCoordinator.Callback prev = null; + Pair>> prev = null; Set EPs = Sets.newHashSet(EP1, EP2); int ep1requests = 0; int ep2requests = 0; - for (int i=0; i<10; i++) + for (int i = 0; i < 10; i++) { - Assert.assertEquals(String.format("%s", i), 2, coordinator.requests.size()); + Assert.assertEquals(String.format("%s", i), 2, wrapper.requests.size()); - MigrationCoordinator.Callback next = coordinator.requests.remove(); + Pair>> next = wrapper.requests.remove(); // we should be contacting endpoints in a round robin fashion - Assert.assertTrue(EPs.contains(next.endpoint)); - if (prev != null && prev.endpoint.equals(next.endpoint)) - Assert.fail(String.format("Not expecting prev %s to be equal to next %s", prev.endpoint, next.endpoint)); + Assert.assertTrue(EPs.contains(next.left)); + if (prev != null && prev.left.equals(next.left)) + Assert.fail(String.format("Not expecting prev %s to be equal to next %s", prev.left, next.left)); // should send a new request - next.fail().get(); + next.right.onFailure(null, null); prev = next; - Assert.assertFalse(coordinator.awaitSchemaRequests(1)); + Assert.assertFalse(wrapper.coordinator.awaitSchemaRequests(1)); - Assert.assertEquals(2, coordinator.requests.size()); + Assert.assertEquals(2, wrapper.requests.size()); } logger.info("{} -> {}", EP1, ep1requests); logger.info("{} -> {}", EP2, ep2requests); // a single success should unblock startup though - coordinator.requests.remove().response(Collections.emptyList()); - Assert.assertTrue(coordinator.awaitSchemaRequests(1)); - + cb = wrapper.requests.remove(); + cb.right.onResponse(Message.remoteResponse(cb.left, Verb.SCHEMA_PULL_RSP, Collections.emptyList())); + Assert.assertTrue(wrapper.coordinator.awaitSchemaRequests(1)); } /** @@ -325,15 +358,100 @@ public class MigrationCoordinatorTest @Test public void pullUnreceived() { - InstrumentedCoordinator coordinator = new InstrumentedCoordinator(); + Wrapper wrapper = new Wrapper(); - coordinator.shouldPullFromEndpoint = false; - assertNoContact(coordinator, false); + when(wrapper.gossiper.getEndpointStateForEndpoint(any())).thenReturn(null); // shouldPullFromEndpoint should return false in this case + assertNoContact(wrapper, false); + + when(wrapper.gossiper.getEndpointStateForEndpoint(any())).thenReturn(validEndpointState); + Assert.assertEquals(0, wrapper.requests.size()); + wrapper.coordinator.start(); + Assert.assertEquals(1, wrapper.requests.size()); + } + + @Test + public void pushSchemaMutationsOnlyToViableNodes() throws UnknownHostException + { + Wrapper wrapper = new Wrapper(); + Collection mutations = Arrays.asList(mock(Mutation.class)); + + EndpointState validState = mock(EndpointState.class); + + InetAddressAndPort thisNode = wrapper.configureMocksForEndpoint(FBUtilities.getBroadcastAddressAndPort(), validState, MessagingService.current_version, false); + InetAddressAndPort unkonwnNode = wrapper.configureMocksForEndpoint("10.0.0.1:8000", validState, null, false); + InetAddressAndPort invalidMessagingVersionNode = wrapper.configureMocksForEndpoint("10.0.0.2:8000", validState, MessagingService.VERSION_30, false); + InetAddressAndPort regularNode = wrapper.configureMocksForEndpoint("10.0.0.3:8000", validState, MessagingService.current_version, false); + + when(wrapper.gossiper.getLiveMembers()).thenReturn(Sets.newHashSet(thisNode, unkonwnNode, invalidMessagingVersionNode, regularNode)); + + ArgumentCaptor msg = ArgumentCaptor.forClass(Message.class); + ArgumentCaptor targetEndpoint = ArgumentCaptor.forClass(InetAddressAndPort.class); + doNothing().when(wrapper.messagingService).send(msg.capture(), targetEndpoint.capture()); + + Pair, Set> result = wrapper.coordinator.pushSchemaMutations(mutations); + assertThat(result.left()).containsExactlyInAnyOrder(regularNode); + assertThat(result.right()).containsExactlyInAnyOrder(thisNode, unkonwnNode, invalidMessagingVersionNode); + assertThat(msg.getAllValues()).hasSize(1); + assertThat(msg.getValue().payload).isEqualTo(mutations); + assertThat(msg.getValue().verb()).isEqualTo(Verb.SCHEMA_PUSH_REQ); + assertThat(targetEndpoint.getValue()).isEqualTo(regularNode); + } + + @Test + public void pullSchemaFromAnyNode() throws UnknownHostException + { + Collection mutations = Arrays.asList(mock(Mutation.class)); + + Wrapper wrapper = new Wrapper(); + + // no live nodes + when(wrapper.gossiper.getLiveMembers()).thenReturn(Collections.emptySet()); + Collection result = wrapper.coordinator.pullSchemaFromAnyNode().syncThrowUncheckedOnInterrupt().getNow(); + assertThat(result).isEmpty(); + + EndpointState invalidVersionState = mock(EndpointState.class); + when(invalidVersionState.getApplicationState(ApplicationState.RELEASE_VERSION)).thenReturn(VersionedValue.unsafeMakeVersionedValue("3.0", 0)); + + EndpointState validVersionState = mock(EndpointState.class); + when(validVersionState.getApplicationState(ApplicationState.RELEASE_VERSION)).thenReturn(VersionedValue.unsafeMakeVersionedValue(FBUtilities.getReleaseVersionString(), 0)); + + // some nodes + InetAddressAndPort thisNode = wrapper.configureMocksForEndpoint(FBUtilities.getBroadcastAddressAndPort(), validVersionState, MessagingService.current_version, false); + InetAddressAndPort noStateNode = wrapper.configureMocksForEndpoint("10.0.0.1:8000", null, MessagingService.current_version, false); + InetAddressAndPort diffMajorVersionNode = wrapper.configureMocksForEndpoint("10.0.0.2:8000", invalidVersionState, MessagingService.current_version, false); + InetAddressAndPort unkonwnNode = wrapper.configureMocksForEndpoint("10.0.0.2:8000", validVersionState, null, false); + InetAddressAndPort invalidMessagingVersionNode = wrapper.configureMocksForEndpoint("10.0.0.3:8000", validVersionState, MessagingService.VERSION_30, false); + InetAddressAndPort gossipOnlyMemberNode = wrapper.configureMocksForEndpoint("10.0.0.4:8000", validVersionState, MessagingService.current_version, true); + InetAddressAndPort regularNode1 = wrapper.configureMocksForEndpoint("10.0.0.5:8000", validVersionState, MessagingService.current_version, false); + InetAddressAndPort regularNode2 = wrapper.configureMocksForEndpoint("10.0.0.6:8000", validVersionState, MessagingService.current_version, false); + Set nodes = new LinkedHashSet<>(Arrays.asList(thisNode, noStateNode, diffMajorVersionNode, unkonwnNode, invalidMessagingVersionNode, gossipOnlyMemberNode, regularNode1, regularNode2)); + when(wrapper.gossiper.getLiveMembers()).thenReturn(nodes); + doAnswer(a -> { + Message msg = a.getArgument(0, Message.class); + InetAddressAndPort endpoint = a.getArgument(1, InetAddressAndPort.class); + RequestCallback callback = a.getArgument(2, RequestCallback.class); + + assertThat(msg.verb()).isEqualTo(Verb.SCHEMA_PULL_REQ); + assertThat(endpoint).isEqualTo(regularNode1); + callback.onResponse(Message.remoteResponse(regularNode1, Verb.SCHEMA_PULL_RSP, mutations)); + return null; + }).when(wrapper.messagingService).sendWithCallback(any(Message.class), any(InetAddressAndPort.class), any(RequestCallback.class)); + result = wrapper.coordinator.pullSchemaFromAnyNode().syncThrowUncheckedOnInterrupt().getNow(); + assertThat(result).isEqualTo(mutations); + + // failures + doAnswer(a -> { + Message msg = a.getArgument(0, Message.class); + InetAddressAndPort endpoint = a.getArgument(1, InetAddressAndPort.class); + RequestCallback callback = a.getArgument(2, RequestCallback.class); + + assertThat(msg.verb()).isEqualTo(Verb.SCHEMA_PULL_REQ); + assertThat(endpoint).isEqualTo(regularNode1); + callback.onFailure(regularNode1, RequestFailureReason.UNKNOWN); + return null; + }).when(wrapper.messagingService).sendWithCallback(any(Message.class), any(InetAddressAndPort.class), any(RequestCallback.class)); + assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> wrapper.coordinator.pullSchemaFromAnyNode().syncThrowUncheckedOnInterrupt().getNow()) + .withMessageContaining("Failed to get schema from"); - coordinator.shouldPullFromEndpoint = true; - Assert.assertEquals(0, coordinator.requests.size()); - List> futures = coordinator.pullUnreceivedSchemaVersions(); - futures.forEach(Futures::getUnchecked); - Assert.assertEquals(1, coordinator.requests.size()); } } \ No newline at end of file diff --git a/test/unit/org/apache/cassandra/schema/MigrationManagerDropKSTest.java b/test/unit/org/apache/cassandra/schema/MigrationManagerDropKSTest.java index e28a0dfb89..a75116671e 100644 --- a/test/unit/org/apache/cassandra/schema/MigrationManagerDropKSTest.java +++ b/test/unit/org/apache/cassandra/schema/MigrationManagerDropKSTest.java @@ -54,6 +54,7 @@ public class MigrationManagerDropKSTest SchemaLoader.standardCFMD(KEYSPACE1, TABLE1), SchemaLoader.standardCFMD(KEYSPACE1, TABLE2)); } + @Test public void dropKS() throws ConfigurationException { @@ -73,7 +74,7 @@ public class MigrationManagerDropKSTest cfs.forceBlockingFlush(); assertTrue(!cfs.getDirectories().sstableLister(Directories.OnTxnErr.THROW).list().isEmpty()); - MigrationManager.announceKeyspaceDrop(ks.name); + SchemaTestUtil.announceKeyspaceDrop(ks.name); assertNull(Schema.instance.getKeyspaceMetadata(ks.name)); diff --git a/test/unit/org/apache/cassandra/schema/MigrationManagerTest.java b/test/unit/org/apache/cassandra/schema/MigrationManagerTest.java index 389e0a7d30..3dd889d2b8 100644 --- a/test/unit/org/apache/cassandra/schema/MigrationManagerTest.java +++ b/test/unit/org/apache/cassandra/schema/MigrationManagerTest.java @@ -21,7 +21,6 @@ package org.apache.cassandra.schema; import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Map; -import java.util.Optional; import java.util.function.Supplier; import com.google.common.collect.ImmutableMap; @@ -39,7 +38,6 @@ import org.apache.cassandra.cql3.UntypedResultSet; import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.db.Directories; import org.apache.cassandra.db.Keyspace; -import org.apache.cassandra.db.Mutation; import org.apache.cassandra.db.lifecycle.LifecycleTransaction; import org.apache.cassandra.db.marshal.ByteType; import org.apache.cassandra.db.marshal.BytesType; @@ -50,7 +48,6 @@ import org.apache.cassandra.io.sstable.Descriptor; import org.apache.cassandra.locator.NetworkTopologyStrategy; import org.apache.cassandra.utils.FBUtilities; -import static java.util.Collections.singleton; import static org.apache.cassandra.Util.throwAssert; import static org.apache.cassandra.cql3.CQLTester.assertRows; import static org.apache.cassandra.cql3.CQLTester.row; @@ -85,7 +82,7 @@ public class MigrationManagerTest SchemaLoader.standardCFMD(KEYSPACE1, TABLE2)); SchemaLoader.createKeyspace(KEYSPACE3, KeyspaceParams.simple(5), - SchemaLoader.standardCFMD(KEYSPACE1, TABLE1), + SchemaLoader.standardCFMD(KEYSPACE3, TABLE1), SchemaLoader.compositeIndexCFMD(KEYSPACE3, TABLE1i, true)); SchemaLoader.createKeyspace(KEYSPACE6, KeyspaceParams.simple(1), @@ -148,7 +145,7 @@ public class MigrationManagerTest TableMetadata newCf = addTestTable("MadeUpKeyspace", "NewCF", "new cf"); try { - MigrationManager.announceNewTable(newCf); + SchemaTestUtil.announceNewTable(newCf); throw new AssertionError("You shouldn't be able to do anything to a keyspace that doesn't exist."); } catch (ConfigurationException expected) @@ -166,7 +163,7 @@ public class MigrationManagerTest TableMetadata cfm = addTestTable(original.name, tableName, "A New Table"); assertFalse(Schema.instance.getKeyspaceMetadata(ksName).tables.get(cfm.name).isPresent()); - MigrationManager.announceNewTable(cfm); + SchemaTestUtil.announceNewTable(cfm); assertTrue(Schema.instance.getKeyspaceMetadata(ksName).tables.get(cfm.name).isPresent()); assertEquals(cfm, Schema.instance.getKeyspaceMetadata(ksName).tables.get(cfm.name).get()); @@ -205,7 +202,7 @@ public class MigrationManagerTest store.forceBlockingFlush(); assertTrue(store.getDirectories().sstableLister(Directories.OnTxnErr.THROW).list().size() > 0); - MigrationManager.announceTableDrop(ks.name, cfm.name, false); + SchemaTestUtil.announceTableDrop(ks.name, cfm.name); assertFalse(Schema.instance.getKeyspaceMetadata(ks.name).tables.get(cfm.name).isPresent()); @@ -241,7 +238,7 @@ public class MigrationManagerTest { TableMetadata cfm = addTestTable("newkeyspace1", "newstandard1", "A new cf for a new ks"); KeyspaceMetadata newKs = KeyspaceMetadata.create(cfm.keyspace, KeyspaceParams.simple(5), Tables.of(cfm)); - MigrationManager.announceNewKeyspace(newKs); + SchemaTestUtil.announceNewKeyspace(newKs); assertNotNull(Schema.instance.getKeyspaceMetadata(cfm.keyspace)); assertEquals(Schema.instance.getKeyspaceMetadata(cfm.keyspace), newKs); @@ -272,7 +269,7 @@ public class MigrationManagerTest KEYSPACE3, TABLE1), "dropKs", "col" + i, "anyvalue"); - MigrationManager.announceKeyspaceDrop(ks.name); + SchemaTestUtil.announceKeyspaceDrop(ks.name); assertNull(Schema.instance.getKeyspaceMetadata(ks.name)); } @@ -282,7 +279,7 @@ public class MigrationManagerTest { assertNull(Schema.instance.getKeyspaceMetadata(EMPTY_KEYSPACE)); KeyspaceMetadata newKs = KeyspaceMetadata.create(EMPTY_KEYSPACE, KeyspaceParams.simple(5)); - MigrationManager.announceNewKeyspace(newKs); + SchemaTestUtil.announceNewKeyspace(newKs); assertNotNull(Schema.instance.getKeyspaceMetadata(EMPTY_KEYSPACE)); String tableName = "added_later"; @@ -292,7 +289,7 @@ public class MigrationManagerTest assertFalse(Schema.instance.getKeyspaceMetadata(newKs.name).tables.get(newCf.name).isPresent()); //add the new CF to the empty space - MigrationManager.announceNewTable(newCf); + SchemaTestUtil.announceNewTable(newCf); assertTrue(Schema.instance.getKeyspaceMetadata(newKs.name).tables.get(newCf.name).isPresent()); assertEquals(Schema.instance.getKeyspaceMetadata(newKs.name).tables.get(newCf.name).get(), newCf); @@ -317,7 +314,7 @@ public class MigrationManagerTest TableMetadata cf = addTestTable("UpdatedKeyspace", "AddedStandard1", "A new cf for a new ks"); KeyspaceMetadata oldKs = KeyspaceMetadata.create(cf.keyspace, KeyspaceParams.simple(5), Tables.of(cf)); - MigrationManager.announceNewKeyspace(oldKs); + SchemaTestUtil.announceNewKeyspace(oldKs); assertNotNull(Schema.instance.getKeyspaceMetadata(cf.keyspace)); assertEquals(Schema.instance.getKeyspaceMetadata(cf.keyspace), oldKs); @@ -326,7 +323,7 @@ public class MigrationManagerTest KeyspaceMetadata newBadKs2 = KeyspaceMetadata.create(cf.keyspace + "trash", KeyspaceParams.simple(4)); try { - MigrationManager.announceKeyspaceUpdate(newBadKs2); + SchemaTestUtil.announceKeyspaceUpdate(newBadKs2); throw new AssertionError("Should not have been able to update a KS with an invalid KS name."); } catch (ConfigurationException ex) @@ -339,7 +336,7 @@ public class MigrationManagerTest replicationMap.put("replication_factor", "1"); KeyspaceMetadata newKs = KeyspaceMetadata.create(cf.keyspace, KeyspaceParams.create(true, replicationMap)); - MigrationManager.announceKeyspaceUpdate(newKs); + SchemaTestUtil.announceKeyspaceUpdate(newKs); KeyspaceMetadata newFetchedKs = Schema.instance.getKeyspaceMetadata(newKs.name); assertEquals(newFetchedKs.params.replication.klass, newKs.params.replication.klass); @@ -471,7 +468,7 @@ public class MigrationManagerTest .get(indexName) .orElseThrow(throwAssert("Index not found")); - MigrationManager.announceTableUpdate(meta.unbuild().indexes(meta.indexes.without(existing.name)).build()); + SchemaTestUtil.announceTableUpdate(meta.unbuild().indexes(meta.indexes.without(existing.name)).build()); // check assertTrue(cfs.indexManager.listIndexes().isEmpty()); @@ -520,11 +517,14 @@ public class MigrationManagerTest TableMetadata table = addTestTable("ks0", "t", ""); KeyspaceMetadata keyspace = KeyspaceMetadata.create("ks0", KeyspaceParams.simple(1), Tables.of(table)); - Optional mutation = MigrationManager.evolveSystemKeyspace(keyspace, 0); - assertTrue(mutation.isPresent()); + SchemaTransformation transformation = SchemaTransformations.updateSystemKeyspace(keyspace, 0); + Keyspaces before = Keyspaces.none(); + Keyspaces after = transformation.apply(before); + Keyspaces.KeyspacesDiff diff = Keyspaces.diff(before, after); - Schema.instance.merge(singleton(mutation.get())); - assertEquals(keyspace, Schema.instance.getKeyspaceMetadata("ks0")); + assertTrue(diff.altered.isEmpty()); + assertTrue(diff.dropped.isEmpty()); + assertEquals(keyspace, diff.created.getNullable("ks0")); } @Test @@ -533,12 +533,12 @@ public class MigrationManagerTest TableMetadata table = addTestTable("ks1", "t", ""); KeyspaceMetadata keyspace = KeyspaceMetadata.create("ks1", KeyspaceParams.simple(1), Tables.of(table)); - // create the keyspace, verify it's there - Schema.instance.merge(singleton(SchemaKeyspace.makeCreateKeyspaceMutation(keyspace, 0).build())); - assertEquals(keyspace, Schema.instance.getKeyspaceMetadata("ks1")); + SchemaTransformation transformation = SchemaTransformations.updateSystemKeyspace(keyspace, 0); + Keyspaces before = Keyspaces.of(keyspace); + Keyspaces after = transformation.apply(before); + Keyspaces.KeyspacesDiff diff = Keyspaces.diff(before, after); - Optional mutation = MigrationManager.evolveSystemKeyspace(keyspace, 0); - assertFalse(mutation.isPresent()); + assertTrue(diff.isEmpty()); } @Test @@ -547,18 +547,18 @@ public class MigrationManagerTest TableMetadata table0 = addTestTable("ks2", "t", ""); KeyspaceMetadata keyspace0 = KeyspaceMetadata.create("ks2", KeyspaceParams.simple(1), Tables.of(table0)); - // create the keyspace, verify it's there - Schema.instance.merge(singleton(SchemaKeyspace.makeCreateKeyspaceMutation(keyspace0, 0).build())); - assertEquals(keyspace0, Schema.instance.getKeyspaceMetadata("ks2")); - TableMetadata table1 = table0.unbuild().comment("comment").build(); KeyspaceMetadata keyspace1 = KeyspaceMetadata.create("ks2", KeyspaceParams.simple(1), Tables.of(table1)); - Optional mutation = MigrationManager.evolveSystemKeyspace(keyspace1, 1); - assertTrue(mutation.isPresent()); + SchemaTransformation transformation = SchemaTransformations.updateSystemKeyspace(keyspace1, 1); + Keyspaces before = Keyspaces.of(keyspace0); + Keyspaces after = transformation.apply(before); + Keyspaces.KeyspacesDiff diff = Keyspaces.diff(before, after); - Schema.instance.merge(singleton(mutation.get())); - assertEquals(keyspace1, Schema.instance.getKeyspaceMetadata("ks2")); + assertTrue(diff.created.isEmpty()); + assertTrue(diff.dropped.isEmpty()); + assertEquals(1, diff.altered.size()); + assertEquals(keyspace1, diff.altered.get(0).after); } private TableMetadata addTestTable(String ks, String cf, String comment) diff --git a/test/unit/org/apache/cassandra/schema/SchemaKeyspaceTest.java b/test/unit/org/apache/cassandra/schema/SchemaKeyspaceTest.java index 4a1ee5749e..e80773047a 100644 --- a/test/unit/org/apache/cassandra/schema/SchemaKeyspaceTest.java +++ b/test/unit/org/apache/cassandra/schema/SchemaKeyspaceTest.java @@ -19,10 +19,7 @@ package org.apache.cassandra.schema; import java.io.IOException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; import java.nio.ByteBuffer; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; @@ -31,6 +28,7 @@ import java.util.concurrent.CyclicBarrier; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import java.util.stream.Stream; import com.google.common.collect.ImmutableMap; @@ -49,8 +47,14 @@ import org.apache.cassandra.db.Mutation; import org.apache.cassandra.db.partitions.PartitionUpdate; import org.apache.cassandra.db.rows.UnfilteredRowIterators; import org.apache.cassandra.exceptions.ConfigurationException; +import org.apache.cassandra.net.Message; +import org.apache.cassandra.net.MessagingService; +import org.apache.cassandra.net.NoPayload; +import org.apache.cassandra.net.RequestCallback; +import org.apache.cassandra.net.Verb; import org.apache.cassandra.service.reads.repair.ReadRepairStrategy; import org.apache.cassandra.utils.FBUtilities; +import org.apache.cassandra.utils.concurrent.AsyncPromise; import org.jboss.byteman.contrib.bmunit.BMRule; import org.jboss.byteman.contrib.bmunit.BMUnitRunner; @@ -71,25 +75,8 @@ public class SchemaKeyspaceTest SchemaLoader.createKeyspace(KEYSPACE1, KeyspaceParams.simple(1), SchemaLoader.standardCFMD(KEYSPACE1, CF_STANDARD1)); - } - /** See CASSANDRA-16856/16996. Make sure schema pulls are synchronized to prevent concurrent schema pull/writes */ - @Test - public void testSchemaPullSynchronicity() throws Exception - { - for (String methodName : Arrays.asList("schemaKeyspaceAsMutations", - "truncateSchemaKeyspace", - "saveSystemKeyspace", - "updateVersion")) - { - Method method = Schema.class.getDeclaredMethod(methodName); - assertTrue(Modifier.isSynchronized(method.getModifiers())); - } - - Method method = Schema.class.getDeclaredMethod("merge", Collection.class); - assertTrue(Modifier.isSynchronized(method.getModifiers())); - method = Schema.class.getDeclaredMethod("transform", SchemaTransformation.class, boolean.class, long.class); - assertTrue(Modifier.isSynchronized(method.getModifiers())); + MessagingService.instance().listen(); } /** See CASSANDRA-16856/16996. Make sure schema pulls are synchronized to prevent concurrent schema pull/writes */ @@ -104,7 +91,7 @@ public class SchemaKeyspaceTest String keyspace = "sandbox"; ExecutorService pool = Executors.newFixedThreadPool(2); - Schema.instance.truncateSchemaKeyspace();; // Make sure there's nothing but the create we're about to do + SchemaKeyspace.truncate(); // Make sure there's nothing but the create we're about to do CyclicBarrier barrier = new CyclicBarrier(2); Future creation = pool.submit(() -> { @@ -115,19 +102,13 @@ public class SchemaKeyspaceTest Future> mutationsFromThread = pool.submit(() -> { barrier.await(); - - Collection mutations = Schema.instance.schemaKeyspaceAsMutations(); - // Make sure we actually have a mutation to check for partial modification. - while (mutations.size() == 0) - mutations = Schema.instance.schemaKeyspaceAsMutations(); - - return mutations; + return Stream.generate(this::getSchemaMutations).filter(m -> !m.isEmpty()).findFirst().get(); }); creation.get(); // make sure the creation is finished Collection mutationsFromConcurrentAccess = mutationsFromThread.get(); - Collection settledMutations = Schema.instance.schemaKeyspaceAsMutations(); + Collection settledMutations = getSchemaMutations(); // If the worker thread picked up the creation at all, it should have the same modifications. // In other words, we should see all modifications or none. @@ -144,10 +125,20 @@ public class SchemaKeyspaceTest pool.shutdownNow(); } + private Collection getSchemaMutations() + { + AsyncPromise> p = new AsyncPromise<>(); + MessagingService.instance().sendWithCallback(Message.out(Verb.SCHEMA_PULL_REQ, NoPayload.noPayload), + FBUtilities.getBroadcastAddressAndPort(), + (RequestCallback>) msg -> p.setSuccess(msg.payload)); + p.syncUninterruptibly(); + return p.getNow(); + } + @Test public void testConversionsInverses() throws Exception { - for (String keyspaceName : Schema.instance.getNonSystemKeyspaces()) + for (String keyspaceName : Schema.instance.getNonSystemKeyspaces().names()) { for (ColumnFamilyStore cfs : Keyspace.open(keyspaceName).getColumnFamilyStores()) { @@ -194,7 +185,7 @@ public class SchemaKeyspaceTest { KeyspaceMetadata ksm = Schema.instance.getKeyspaceInstance(keyspace).getMetadata(); Mutation mutation = SchemaKeyspace.makeUpdateTableMutation(ksm, oldTable, newTable, FBUtilities.timestampMicros()).build(); - Schema.instance.merge(Collections.singleton(mutation)); + SchemaTestUtil.mergeAndAnnounceLocally(Collections.singleton(mutation)); } private static void createTable(String keyspace, String cql) @@ -203,7 +194,7 @@ public class SchemaKeyspaceTest KeyspaceMetadata ksm = KeyspaceMetadata.create(keyspace, KeyspaceParams.simple(1), Tables.of(table)); Mutation mutation = SchemaKeyspace.makeCreateTableMutation(ksm, table, FBUtilities.timestampMicros()).build(); - Schema.instance.merge(Collections.singleton(mutation)); + SchemaTestUtil.mergeAndAnnounceLocally(Collections.singleton(mutation)); } private static void checkInverses(TableMetadata metadata) throws Exception diff --git a/test/unit/org/apache/cassandra/schema/SchemaMutationsSerializerTest.java b/test/unit/org/apache/cassandra/schema/SchemaMutationsSerializerTest.java new file mode 100644 index 0000000000..7680697199 --- /dev/null +++ b/test/unit/org/apache/cassandra/schema/SchemaMutationsSerializerTest.java @@ -0,0 +1,56 @@ +/* + * 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 java.io.IOException; +import java.util.Collection; + +import org.junit.BeforeClass; +import org.junit.Test; + +import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.cql3.CQLTester; +import org.apache.cassandra.db.Mutation; +import org.apache.cassandra.io.util.DataInputBuffer; +import org.apache.cassandra.io.util.DataOutputBuffer; +import org.apache.cassandra.net.MessagingService; +import org.assertj.core.api.Assertions; + +public class SchemaMutationsSerializerTest extends CQLTester +{ + @BeforeClass + public static void beforeClass() + { + DatabaseDescriptor.daemonInitialization(); + } + + @Test + public void testSerDe() throws IOException + { + createTable("CREATE TABLE %s (id INT PRIMARY KEY, v INT)"); + Collection mutations = SchemaKeyspace.convertSchemaToMutations(); + DataOutputBuffer out = new DataOutputBuffer(); + SchemaMutationsSerializer.instance.serialize(mutations, out, MessagingService.current_version); + DataInputBuffer in = new DataInputBuffer(out.toByteArray()); + Collection deserializedMutations = SchemaMutationsSerializer.instance.deserialize(in, MessagingService.current_version); + DataOutputBuffer out2 = new DataOutputBuffer(); + SchemaMutationsSerializer.instance.serialize(deserializedMutations, out2, MessagingService.current_version); + Assertions.assertThat(out2.toByteArray()).isEqualTo(out.toByteArray()); + } +} \ No newline at end of file diff --git a/test/unit/org/apache/cassandra/schema/SchemaTest.java b/test/unit/org/apache/cassandra/schema/SchemaTest.java index 64b1341a7c..eb2cf044c3 100644 --- a/test/unit/org/apache/cassandra/schema/SchemaTest.java +++ b/test/unit/org/apache/cassandra/schema/SchemaTest.java @@ -19,6 +19,8 @@ package org.apache.cassandra.schema; import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; import org.junit.BeforeClass; import org.junit.Test; @@ -26,8 +28,10 @@ import org.junit.Test; import org.apache.cassandra.SchemaLoader; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.db.Keyspace; +import org.apache.cassandra.db.Mutation; import org.apache.cassandra.db.commitlog.CommitLog; import org.apache.cassandra.gms.Gossiper; +import org.apache.cassandra.utils.FBUtilities; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertEquals; @@ -49,25 +53,25 @@ public class SchemaTest Schema.instance.loadFromDisk(); assertEquals(0, Schema.instance.getNonSystemKeyspaces().size()); - Gossiper.instance.start((int)(System.currentTimeMillis() / 1000)); + Gossiper.instance.start((int) (System.currentTimeMillis() / 1000)); Keyspace.setInitialized(); try { // add a few. - MigrationManager.announceNewKeyspace(KeyspaceMetadata.create("ks0", KeyspaceParams.simple(3))); - MigrationManager.announceNewKeyspace(KeyspaceMetadata.create("ks1", KeyspaceParams.simple(3))); + saveKeyspaces(); + Schema.instance.reloadSchemaAndAnnounceVersion(); assertNotNull(Schema.instance.getKeyspaceMetadata("ks0")); assertNotNull(Schema.instance.getKeyspaceMetadata("ks1")); - Schema.instance.unload(Schema.instance.getKeyspaceMetadata("ks0")); - Schema.instance.unload(Schema.instance.getKeyspaceMetadata("ks1")); + Schema.instance.transform(keyspaces -> keyspaces.without(Arrays.asList("ks0", "ks1"))); assertNull(Schema.instance.getKeyspaceMetadata("ks0")); assertNull(Schema.instance.getKeyspaceMetadata("ks1")); - Schema.instance.loadFromDisk(); + saveKeyspaces(); + Schema.instance.reloadSchemaAndAnnounceVersion(); assertNotNull(Schema.instance.getKeyspaceMetadata("ks0")); assertNotNull(Schema.instance.getKeyspaceMetadata("ks1")); @@ -78,4 +82,10 @@ public class SchemaTest } } + private void saveKeyspaces() + { + Collection mutations = Arrays.asList(SchemaKeyspace.makeCreateKeyspaceMutation(KeyspaceMetadata.create("ks0", KeyspaceParams.simple(3)), FBUtilities.timestampMicros()).build(), + SchemaKeyspace.makeCreateKeyspaceMutation(KeyspaceMetadata.create("ks1", KeyspaceParams.simple(3)), FBUtilities.timestampMicros()).build()); + SchemaKeyspace.applyChanges(mutations); + } } diff --git a/test/unit/org/apache/cassandra/schema/SchemaTestUtil.java b/test/unit/org/apache/cassandra/schema/SchemaTestUtil.java new file mode 100644 index 0000000000..b937d15e0c --- /dev/null +++ b/test/unit/org/apache/cassandra/schema/SchemaTestUtil.java @@ -0,0 +1,146 @@ +/* + * 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 java.util.Collection; +import java.util.Collections; +import java.util.concurrent.TimeUnit; + +import org.junit.Assert; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.cassandra.concurrent.Stage; +import org.apache.cassandra.db.Mutation; +import org.apache.cassandra.exceptions.AlreadyExistsException; +import org.apache.cassandra.exceptions.ConfigurationException; +import org.apache.cassandra.net.Message; +import org.apache.cassandra.utils.concurrent.Future; + +import static org.apache.cassandra.net.Verb.SCHEMA_PUSH_REQ; + +public class SchemaTestUtil +{ + private final static Logger logger = LoggerFactory.getLogger(SchemaTestUtil.class); + + public static void announceNewKeyspace(KeyspaceMetadata ksm) throws ConfigurationException + { + ksm.validate(); + + if (Schema.instance.getKeyspaceMetadata(ksm.name) != null) + throw new AlreadyExistsException(ksm.name); + + logger.info("Create new Keyspace: {}", ksm); + Schema.instance.transform(schema -> schema.withAddedOrUpdated(ksm)); + } + + public static void announceNewTable(TableMetadata cfm) + { + announceNewTable(cfm, true); + } + + private static void announceNewTable(TableMetadata cfm, boolean throwOnDuplicate) + { + cfm.validate(); + + KeyspaceMetadata ksm = Schema.instance.getKeyspaceMetadata(cfm.keyspace); + if (ksm == null) + throw new ConfigurationException(String.format("Cannot add table '%s' to non existing keyspace '%s'.", cfm.name, cfm.keyspace)); + // If we have a table or a view which has the same name, we can't add a new one + else if (throwOnDuplicate && ksm.getTableOrViewNullable(cfm.name) != null) + throw new AlreadyExistsException(cfm.keyspace, cfm.name); + + logger.info("Create new table: {}", cfm); + Schema.instance.transform(schema -> schema.withAddedOrUpdated(ksm.withSwapped(ksm.tables.with(cfm)))); + } + + static void announceKeyspaceUpdate(KeyspaceMetadata ksm) + { + ksm.validate(); + + KeyspaceMetadata oldKsm = Schema.instance.getKeyspaceMetadata(ksm.name); + if (oldKsm == null) + throw new ConfigurationException(String.format("Cannot update non existing keyspace '%s'.", ksm.name)); + + logger.info("Update Keyspace '{}' From {} To {}", ksm.name, oldKsm, ksm); + Schema.instance.transform(schema -> schema.withAddedOrUpdated(ksm)); + } + + public static void announceTableUpdate(TableMetadata updated) + { + updated.validate(); + + TableMetadata current = Schema.instance.getTableMetadata(updated.keyspace, updated.name); + if (current == null) + throw new ConfigurationException(String.format("Cannot update non existing table '%s' in keyspace '%s'.", updated.name, updated.keyspace)); + KeyspaceMetadata ksm = Schema.instance.getKeyspaceMetadata(current.keyspace); + + updated.validateCompatibility(current); + + logger.info("Update table '{}/{}' From {} To {}", current.keyspace, current.name, current, updated); + Schema.instance.transform(schema -> schema.withAddedOrUpdated(ksm.withSwapped(ksm.tables.withSwapped(updated)))); + } + + static void announceKeyspaceDrop(String ksName) + { + KeyspaceMetadata oldKsm = Schema.instance.getKeyspaceMetadata(ksName); + if (oldKsm == null) + throw new ConfigurationException(String.format("Cannot drop non existing keyspace '%s'.", ksName)); + + logger.info("Drop Keyspace '{}'", oldKsm.name); + Schema.instance.transform(schema -> schema.without(ksName)); + } + + public static SchemaTransformation dropTable(String ksName, String cfName) + { + return schema -> { + KeyspaceMetadata ksm = schema.getNullable(ksName); + TableMetadata tm = ksm != null ? ksm.getTableOrViewNullable(cfName) : null; + if (tm == null) + throw new ConfigurationException(String.format("Cannot drop non existing table '%s' in keyspace '%s'.", cfName, ksName)); + + return schema.withAddedOrUpdated(ksm.withSwapped(ksm.tables.without(cfName))); + }; + } + + public static void announceTableDrop(String ksName, String cfName) + { + logger.info("Drop table '{}/{}'", ksName, cfName); + Schema.instance.transform(dropTable(ksName, cfName)); + } + + public static void addOrUpdateKeyspace(KeyspaceMetadata ksm, boolean locally) + { + Schema.instance.transform(current -> current.withAddedOrUpdated(ksm)); + } + + public static void dropKeyspaceIfExist(String ksName, boolean locally) + { + Schema.instance.transform(current -> current.without(Collections.singletonList(ksName))); + } + + public static void mergeAndAnnounceLocally(Collection schemaMutations) + { + SchemaPushVerbHandler.instance.doVerb(Message.out(SCHEMA_PUSH_REQ, schemaMutations)); + Future f = Stage.MIGRATION.submit(() -> {}); + Assert.assertTrue(f.awaitThrowUncheckedOnInterrupt(10, TimeUnit.SECONDS)); + f.rethrowIfFailed(); + } + +} diff --git a/test/unit/org/apache/cassandra/service/LeaveAndBootstrapTest.java b/test/unit/org/apache/cassandra/service/LeaveAndBootstrapTest.java index 02d625c059..b6d039b439 100644 --- a/test/unit/org/apache/cassandra/service/LeaveAndBootstrapTest.java +++ b/test/unit/org/apache/cassandra/service/LeaveAndBootstrapTest.java @@ -123,7 +123,7 @@ public class LeaveAndBootstrapTest PendingRangeCalculatorService.instance.blockUntilFinished(); AbstractReplicationStrategy strategy; - for (String keyspaceName : Schema.instance.getNonLocalStrategyKeyspaces()) + for (String keyspaceName : Schema.instance.getNonLocalStrategyKeyspaces().names()) { strategy = getStrategy(keyspaceName, tmd); for (Token token : keyTokens) diff --git a/test/unit/org/apache/cassandra/service/MoveTest.java b/test/unit/org/apache/cassandra/service/MoveTest.java index ddfd4f3c8c..6dce8f3398 100644 --- a/test/unit/org/apache/cassandra/service/MoveTest.java +++ b/test/unit/org/apache/cassandra/service/MoveTest.java @@ -43,7 +43,7 @@ import org.junit.Test; import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.locator.Replica; import org.apache.cassandra.locator.ReplicaCollection; -import org.apache.cassandra.schema.MigrationManager; +import org.apache.cassandra.schema.SchemaTestUtil; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.db.marshal.BytesType; @@ -188,7 +188,7 @@ public class MoveTest .addPartitionKeyColumn("key", BytesType.instance) .build())); - MigrationManager.announceNewKeyspace(keyspace); + SchemaTestUtil.announceNewKeyspace(keyspace); } private static Object[] configOptions(Integer[] replicas) @@ -557,7 +557,7 @@ public class MoveTest private void assertPendingRanges(TokenMetadata tmd, Map, EndpointsForRange> pendingRanges, String keyspaceName) throws ConfigurationException { boolean keyspaceFound = false; - for (String nonSystemKeyspaceName : Schema.instance.getNonLocalStrategyKeyspaces()) + for (String nonSystemKeyspaceName : Schema.instance.getNonLocalStrategyKeyspaces().names()) { if(!keyspaceName.equals(nonSystemKeyspaceName)) continue; @@ -626,7 +626,7 @@ public class MoveTest assertTrue(tmd.isMoving(hosts.get(MOVING_NODE))); AbstractReplicationStrategy strategy; - for (String keyspaceName : Schema.instance.getNonLocalStrategyKeyspaces()) + for (String keyspaceName : Schema.instance.getNonLocalStrategyKeyspaces().names()) { strategy = getStrategy(keyspaceName, tmd); if(strategy instanceof NetworkTopologyStrategy) diff --git a/test/unit/org/apache/cassandra/service/OptionalTasksTest.java b/test/unit/org/apache/cassandra/service/OptionalTasksTest.java index 156706bb48..0ccf72b57c 100644 --- a/test/unit/org/apache/cassandra/service/OptionalTasksTest.java +++ b/test/unit/org/apache/cassandra/service/OptionalTasksTest.java @@ -26,10 +26,11 @@ import org.junit.Test; import org.apache.cassandra.SchemaLoader; import org.apache.cassandra.db.ColumnFamilyStore; -import org.apache.cassandra.db.Keyspace; import org.apache.cassandra.exceptions.ConfigurationException; +import org.apache.cassandra.schema.KeyspaceMetadata; import org.apache.cassandra.schema.KeyspaceParams; import org.apache.cassandra.schema.Schema; +import org.apache.cassandra.schema.SchemaTestUtil; import org.apache.cassandra.schema.TableMetadata; import static org.apache.cassandra.SchemaLoader.standardCFMD; @@ -48,7 +49,7 @@ public class OptionalTasksTest SchemaLoader.prepareServer(); SchemaLoader.createKeyspace(KEYSPACE, KeyspaceParams.simple(1), standardCFMD(KEYSPACE, TABLE)); } - + @Test public void shouldIgnoreDroppedKeyspace() { @@ -56,9 +57,10 @@ public class OptionalTasksTest TableMetadata metadata = Schema.instance.getTableMetadata(KEYSPACE, TABLE); ColumnFamilyStore cfs = Schema.instance.getColumnFamilyStoreInstance(Objects.requireNonNull(metadata).id); Objects.requireNonNull(cfs).metric.coordinatorReadLatency.update(100, TimeUnit.NANOSECONDS); - + // Remove the Keyspace name to make it invisible to the updater... - Keyspace removed = Schema.instance.maybeRemoveKeyspaceInstance(KEYSPACE); + KeyspaceMetadata ksm = Schema.instance.getKeyspaceMetadata(KEYSPACE); + SchemaTestUtil.dropKeyspaceIfExist(KEYSPACE, true); try { @@ -72,7 +74,7 @@ public class OptionalTasksTest finally { // Restore the removed Keyspace to put things back the way we found them. - Schema.instance.maybeAddKeyspaceInstance(removed.getName(), () -> removed); + SchemaTestUtil.addOrUpdateKeyspace(ksm, true); } } @@ -85,10 +87,10 @@ public class OptionalTasksTest Objects.requireNonNull(cfs).metric.coordinatorReadLatency.update(100, TimeUnit.NANOSECONDS); long originalValue = cfs.sampleReadLatencyNanos; - + // ...and ensure that the speculation threshold updater runs. SPECULATION_THRESHOLD_UPDATER.run(); - + assertNotEquals(originalValue, cfs.sampleReadLatencyNanos); } } diff --git a/test/unit/org/apache/cassandra/service/PartitionDenylistTest.java b/test/unit/org/apache/cassandra/service/PartitionDenylistTest.java index fd318cc1e7..13649d092c 100644 --- a/test/unit/org/apache/cassandra/service/PartitionDenylistTest.java +++ b/test/unit/org/apache/cassandra/service/PartitionDenylistTest.java @@ -32,6 +32,7 @@ import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.schema.KeyspaceMetadata; import org.apache.cassandra.schema.KeyspaceParams; import org.apache.cassandra.schema.Schema; +import org.apache.cassandra.schema.SchemaTestUtil; import org.apache.cassandra.schema.Tables; import static org.apache.cassandra.cql3.QueryProcessor.process; @@ -69,7 +70,7 @@ public class PartitionDenylistTest + "value text, " + "PRIMARY KEY((keyone, keytwo), keythree) ) ", ks_cql).build() )); - Schema.instance.load(schema); + SchemaTestUtil.addOrUpdateKeyspace(schema, false); DatabaseDescriptor.setPartitionDenylistEnabled(true); DatabaseDescriptor.setDenylistRangeReadsEnabled(true); DatabaseDescriptor.setDenylistConsistencyLevel(ConsistencyLevel.ONE); diff --git a/test/unit/org/apache/cassandra/service/StorageServiceServerTest.java b/test/unit/org/apache/cassandra/service/StorageServiceServerTest.java index 7bef3a5570..c8739ae1bc 100644 --- a/test/unit/org/apache/cassandra/service/StorageServiceServerTest.java +++ b/test/unit/org/apache/cassandra/service/StorageServiceServerTest.java @@ -53,6 +53,8 @@ import org.apache.cassandra.schema.*; import org.apache.cassandra.utils.FBUtilities; import org.assertj.core.api.Assertions; +import static org.apache.cassandra.ServerTestUtils.cleanup; +import static org.apache.cassandra.ServerTestUtils.mkdirs; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -67,6 +69,9 @@ public class StorageServiceServerTest IEndpointSnitch snitch = new PropertyFileSnitch(); DatabaseDescriptor.setEndpointSnitch(snitch); Keyspace.setInitialized(); + mkdirs(); + cleanup(); + StorageService.instance.initServer(0); } @Test @@ -116,9 +121,9 @@ public class StorageServiceServerTest configOptions.put("DC2", "2"); configOptions.put(ReplicationParams.CLASS, "NetworkTopologyStrategy"); - Schema.instance.maybeRemoveKeyspaceInstance("Keyspace1"); + SchemaTestUtil.dropKeyspaceIfExist("Keyspace1", false); KeyspaceMetadata meta = KeyspaceMetadata.create("Keyspace1", KeyspaceParams.create(false, configOptions)); - Schema.instance.load(meta); + SchemaTestUtil.addOrUpdateKeyspace(meta, false); Collection> primaryRanges = StorageService.instance.getLocalPrimaryRangeForEndpoint(InetAddressAndPort.getByName("127.0.0.1")); Assertions.assertThat(primaryRanges.size()).as(primaryRanges.toString()).isEqualTo(1); @@ -156,9 +161,9 @@ public class StorageServiceServerTest configOptions.put("DC2", "1"); configOptions.put(ReplicationParams.CLASS, "NetworkTopologyStrategy"); - Schema.instance.maybeRemoveKeyspaceInstance("Keyspace1"); + SchemaTestUtil.dropKeyspaceIfExist("Keyspace1", false); KeyspaceMetadata meta = KeyspaceMetadata.create("Keyspace1", KeyspaceParams.create(false, configOptions)); - Schema.instance.load(meta); + SchemaTestUtil.addOrUpdateKeyspace(meta, false); Collection> primaryRanges = StorageService.instance.getPrimaryRangeForEndpointWithinDC(meta.name, InetAddressAndPort.getByName("127.0.0.1")); @@ -199,9 +204,9 @@ public class StorageServiceServerTest configOptions.put("DC2", "1"); configOptions.put(ReplicationParams.CLASS, "NetworkTopologyStrategy"); - Schema.instance.maybeRemoveKeyspaceInstance("Keyspace1"); + SchemaTestUtil.dropKeyspaceIfExist("Keyspace1", false); KeyspaceMetadata meta = KeyspaceMetadata.create("Keyspace1", KeyspaceParams.create(false, configOptions)); - Schema.instance.load(meta); + SchemaTestUtil.addOrUpdateKeyspace(meta, false); Collection> primaryRanges = StorageService.instance.getPrimaryRangesForEndpoint(meta.name, InetAddressAndPort.getByName("127.0.0.1")); Assertions.assertThat(primaryRanges.size()).as(primaryRanges.toString()).isEqualTo(1); @@ -236,9 +241,9 @@ public class StorageServiceServerTest configOptions.put("DC2", "2"); configOptions.put(ReplicationParams.CLASS, "NetworkTopologyStrategy"); - Schema.instance.maybeRemoveKeyspaceInstance("Keyspace1"); + SchemaTestUtil.dropKeyspaceIfExist("Keyspace1", false); KeyspaceMetadata meta = KeyspaceMetadata.create("Keyspace1", KeyspaceParams.create(false, configOptions)); - Schema.instance.load(meta); + SchemaTestUtil.addOrUpdateKeyspace(meta, false); // endpoints in DC1 should not have primary range Collection> primaryRanges = StorageService.instance.getPrimaryRangesForEndpoint(meta.name, InetAddressAndPort.getByName("127.0.0.1")); @@ -275,9 +280,9 @@ public class StorageServiceServerTest configOptions.put("DC2", "2"); configOptions.put(ReplicationParams.CLASS, "NetworkTopologyStrategy"); - Schema.instance.maybeRemoveKeyspaceInstance("Keyspace1"); + SchemaTestUtil.dropKeyspaceIfExist("Keyspace1", false); KeyspaceMetadata meta = KeyspaceMetadata.create("Keyspace1", KeyspaceParams.create(false, configOptions)); - Schema.instance.load(meta); + SchemaTestUtil.addOrUpdateKeyspace(meta, false); // endpoints in DC1 should not have primary range Collection> primaryRanges = StorageService.instance.getPrimaryRangeForEndpointWithinDC(meta.name, InetAddressAndPort.getByName("127.0.0.1")); @@ -327,9 +332,9 @@ public class StorageServiceServerTest configOptions.put("DC2", "2"); configOptions.put(ReplicationParams.CLASS, "NetworkTopologyStrategy"); - Schema.instance.maybeRemoveKeyspaceInstance("Keyspace1"); + SchemaTestUtil.dropKeyspaceIfExist("Keyspace1", false); KeyspaceMetadata meta = KeyspaceMetadata.create("Keyspace1", KeyspaceParams.create(false, configOptions)); - Schema.instance.load(meta); + SchemaTestUtil.addOrUpdateKeyspace(meta, false); // endpoints in DC1 should not have primary range Collection> primaryRanges = StorageService.instance.getPrimaryRangesForEndpoint(meta.name, InetAddressAndPort.getByName("127.0.0.1")); @@ -394,9 +399,9 @@ public class StorageServiceServerTest configOptions.put("DC2", "2"); configOptions.put(ReplicationParams.CLASS, "NetworkTopologyStrategy"); - Schema.instance.maybeRemoveKeyspaceInstance("Keyspace1"); + SchemaTestUtil.dropKeyspaceIfExist("Keyspace1", false); KeyspaceMetadata meta = KeyspaceMetadata.create("Keyspace1", KeyspaceParams.create(false, configOptions)); - Schema.instance.load(meta); + SchemaTestUtil.addOrUpdateKeyspace(meta, false); // endpoints in DC1 should have primary ranges which also cover DC2 Collection> primaryRanges = StorageService.instance.getPrimaryRangeForEndpointWithinDC(meta.name, InetAddressAndPort.getByName("127.0.0.1")); @@ -453,9 +458,9 @@ public class StorageServiceServerTest metadata.updateNormalToken(new StringToken("B"), InetAddressAndPort.getByName("127.0.0.2")); metadata.updateNormalToken(new StringToken("C"), InetAddressAndPort.getByName("127.0.0.3")); - Schema.instance.maybeRemoveKeyspaceInstance("Keyspace1"); + SchemaTestUtil.dropKeyspaceIfExist("Keyspace1", false); KeyspaceMetadata meta = KeyspaceMetadata.create("Keyspace1", KeyspaceParams.simpleTransient(2)); - Schema.instance.load(meta); + SchemaTestUtil.addOrUpdateKeyspace(meta, false); Collection> primaryRanges = StorageService.instance.getPrimaryRangesForEndpoint(meta.name, InetAddressAndPort.getByName("127.0.0.1")); Assertions.assertThat(primaryRanges.size()).as(primaryRanges.toString()).isEqualTo(1); @@ -484,9 +489,9 @@ public class StorageServiceServerTest Map configOptions = new HashMap<>(); configOptions.put("replication_factor", "2"); - Schema.instance.maybeRemoveKeyspaceInstance("Keyspace1"); + SchemaTestUtil.dropKeyspaceIfExist("Keyspace1", false); KeyspaceMetadata meta = KeyspaceMetadata.create("Keyspace1", KeyspaceParams.simpleTransient(2)); - Schema.instance.load(meta); + SchemaTestUtil.addOrUpdateKeyspace(meta, false); Collection> primaryRanges = StorageService.instance.getPrimaryRangeForEndpointWithinDC(meta.name, InetAddressAndPort.getByName("127.0.0.1")); Assertions.assertThat(primaryRanges.size()).as(primaryRanges.toString()).isEqualTo(1); diff --git a/test/unit/org/apache/cassandra/service/paxos/uncommitted/PaxosRowsTest.java b/test/unit/org/apache/cassandra/service/paxos/uncommitted/PaxosRowsTest.java index 96fe154d01..911ac72d82 100644 --- a/test/unit/org/apache/cassandra/service/paxos/uncommitted/PaxosRowsTest.java +++ b/test/unit/org/apache/cassandra/service/paxos/uncommitted/PaxosRowsTest.java @@ -94,7 +94,6 @@ public class PaxosRowsTest public static void setUpClass() throws Exception { SchemaLoader.prepareServer(); - SystemKeyspace.finishStartup(); ks = "coordinatorsessiontest"; metadata = CreateTableStatement.parse("CREATE TABLE tbl (k INT PRIMARY KEY, v INT)", ks).build(); diff --git a/test/unit/org/apache/cassandra/service/reads/repair/AbstractReadRepairTest.java b/test/unit/org/apache/cassandra/service/reads/repair/AbstractReadRepairTest.java index 0ce1b90e8c..0fcc5788ab 100644 --- a/test/unit/org/apache/cassandra/service/reads/repair/AbstractReadRepairTest.java +++ b/test/unit/org/apache/cassandra/service/reads/repair/AbstractReadRepairTest.java @@ -71,7 +71,7 @@ import org.apache.cassandra.locator.ReplicaUtils; import org.apache.cassandra.net.Message; import org.apache.cassandra.schema.KeyspaceMetadata; import org.apache.cassandra.schema.KeyspaceParams; -import org.apache.cassandra.schema.MigrationManager; +import org.apache.cassandra.schema.SchemaTestUtil; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.schema.Tables; import org.apache.cassandra.service.StorageService; @@ -223,7 +223,7 @@ public abstract class AbstractReadRepairTest cfm = CreateTableStatement.parse(ddl, ksName).build(); assert cfm.params.readRepair == repairStrategy; KeyspaceMetadata ksm = KeyspaceMetadata.create(ksName, KeyspaceParams.simple(3), Tables.of(cfm)); - MigrationManager.announceNewKeyspace(ksm, false); + SchemaTestUtil.announceNewKeyspace(ksm); ks = Keyspace.open(ksName); cfs = ks.getColumnFamilyStore("tbl"); diff --git a/test/unit/org/apache/cassandra/service/reads/repair/ReadRepairTest.java b/test/unit/org/apache/cassandra/service/reads/repair/ReadRepairTest.java index a6b6918f43..14c89b5348 100644 --- a/test/unit/org/apache/cassandra/service/reads/repair/ReadRepairTest.java +++ b/test/unit/org/apache/cassandra/service/reads/repair/ReadRepairTest.java @@ -53,7 +53,7 @@ import org.apache.cassandra.locator.Replica; import org.apache.cassandra.net.Message; import org.apache.cassandra.schema.KeyspaceMetadata; import org.apache.cassandra.schema.KeyspaceParams; -import org.apache.cassandra.schema.MigrationManager; +import org.apache.cassandra.schema.SchemaTestUtil; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.schema.Tables; import org.apache.cassandra.utils.ByteBufferUtil; @@ -119,7 +119,7 @@ public class ReadRepairTest cfm = CreateTableStatement.parse("CREATE TABLE tbl (k int primary key, v text)", ksName).build(); KeyspaceMetadata ksm = KeyspaceMetadata.create(ksName, KeyspaceParams.simple(3), Tables.of(cfm)); - MigrationManager.announceNewKeyspace(ksm, false); + SchemaTestUtil.announceNewKeyspace(ksm); ks = Keyspace.open(ksName); cfs = ks.getColumnFamilyStore("tbl"); diff --git a/test/unit/org/apache/cassandra/transport/ClientNotificiationsTest.java b/test/unit/org/apache/cassandra/transport/ClientNotificiationsTest.java index 4ff844a72d..a130e3a234 100644 --- a/test/unit/org/apache/cassandra/transport/ClientNotificiationsTest.java +++ b/test/unit/org/apache/cassandra/transport/ClientNotificiationsTest.java @@ -28,6 +28,8 @@ import org.junit.runners.Parameterized; import org.apache.cassandra.cql3.CQLTester; import org.apache.cassandra.locator.InetAddressAndPort; +import org.apache.cassandra.schema.KeyspaceMetadata; +import org.apache.cassandra.schema.KeyspaceParams; import org.apache.cassandra.service.StorageService; import org.apache.cassandra.transport.messages.RegisterMessage; import org.apache.cassandra.utils.FBUtilities; @@ -73,6 +75,7 @@ public class ClientNotificiationsTest extends CQLTester InetAddressAndPort broadcastAddress = FBUtilities.getBroadcastAddressAndPort(); InetAddressAndPort nativeAddress = FBUtilities.getBroadcastNativeAddressAndPort(); + KeyspaceMetadata ks = KeyspaceMetadata.create("ks", KeyspaceParams.simple(1)); // Necessary or else the NEW_NODE notification is deferred (CASSANDRA-11038) // (note: this works because the notifications are for the local address) @@ -83,9 +86,9 @@ public class ClientNotificiationsTest extends CQLTester notifier.onJoinCluster(broadcastAddress); notifier.onMove(broadcastAddress); notifier.onLeaveCluster(broadcastAddress); - notifier.onCreateKeyspace("ks"); - notifier.onAlterKeyspace("ks"); - notifier.onDropKeyspace("ks"); + notifier.onCreateKeyspace(ks); + notifier.onAlterKeyspace(ks, ks); + notifier.onDropKeyspace(ks); handler.assertNextEvent(Event.StatusChange.nodeUp(nativeAddress)); handler.assertNextEvent(Event.StatusChange.nodeDown(nativeAddress)); diff --git a/test/unit/org/apache/cassandra/triggers/TriggersSchemaTest.java b/test/unit/org/apache/cassandra/triggers/TriggersSchemaTest.java index cd59aa18d4..6b875a37f1 100644 --- a/test/unit/org/apache/cassandra/triggers/TriggersSchemaTest.java +++ b/test/unit/org/apache/cassandra/triggers/TriggersSchemaTest.java @@ -22,15 +22,15 @@ import org.junit.Test; import org.apache.cassandra.SchemaLoader; import org.apache.cassandra.cql3.statements.schema.CreateTableStatement; -import org.apache.cassandra.schema.TableMetadata; -import org.apache.cassandra.schema.Schema; import org.apache.cassandra.exceptions.ConfigurationException; +import org.apache.cassandra.schema.Schema; +import org.apache.cassandra.schema.SchemaTestUtil; +import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.schema.KeyspaceMetadata; import org.apache.cassandra.schema.KeyspaceParams; import org.apache.cassandra.schema.Tables; import org.apache.cassandra.schema.TriggerMetadata; import org.apache.cassandra.schema.Triggers; -import org.apache.cassandra.schema.MigrationManager; import static org.apache.cassandra.utils.Clock.Global.nanoTime; import static org.junit.Assert.*; @@ -58,7 +58,7 @@ public class TriggersSchemaTest .build(); KeyspaceMetadata ksm = KeyspaceMetadata.create(ksName, KeyspaceParams.simple(1), Tables.of(tm)); - MigrationManager.announceNewKeyspace(ksm); + SchemaTestUtil.announceNewKeyspace(ksm); TableMetadata tm2 = Schema.instance.getTableMetadata(ksName, cfName); assertFalse(tm2.triggers.isEmpty()); @@ -70,14 +70,14 @@ public class TriggersSchemaTest public void addNewCfWithTriggerToKs() throws Exception { KeyspaceMetadata ksm = KeyspaceMetadata.create(ksName, KeyspaceParams.simple(1)); - MigrationManager.announceNewKeyspace(ksm); + SchemaTestUtil.announceNewKeyspace(ksm); TableMetadata metadata = CreateTableStatement.parse(String.format("CREATE TABLE %s (k int PRIMARY KEY, v int)", cfName), ksName) .triggers(Triggers.of(TriggerMetadata.create(triggerName, triggerClass))) .build(); - MigrationManager.announceNewTable(metadata); + SchemaTestUtil.announceNewTable(metadata); metadata = Schema.instance.getTableMetadata(ksName, cfName); assertFalse(metadata.triggers.isEmpty()); @@ -92,7 +92,7 @@ public class TriggersSchemaTest CreateTableStatement.parse(String.format("CREATE TABLE %s (k int PRIMARY KEY, v int)", cfName), ksName) .build(); KeyspaceMetadata ksm = KeyspaceMetadata.create(ksName, KeyspaceParams.simple(1), Tables.of(tm1)); - MigrationManager.announceNewKeyspace(ksm); + SchemaTestUtil.announceNewKeyspace(ksm); TriggerMetadata td = TriggerMetadata.create(triggerName, triggerClass); TableMetadata tm2 = @@ -101,7 +101,7 @@ public class TriggersSchemaTest .unbuild() .triggers(Triggers.of(td)) .build(); - MigrationManager.announceTableUpdate(tm2); + SchemaTestUtil.announceTableUpdate(tm2); TableMetadata tm3 = Schema.instance.getTableMetadata(ksName, cfName); assertFalse(tm3.triggers.isEmpty()); @@ -118,14 +118,14 @@ public class TriggersSchemaTest .triggers(Triggers.of(td)) .build(); KeyspaceMetadata ksm = KeyspaceMetadata.create(ksName, KeyspaceParams.simple(1), Tables.of(tm)); - MigrationManager.announceNewKeyspace(ksm); + SchemaTestUtil.announceNewKeyspace(ksm); TableMetadata tm1 = Schema.instance.getTableMetadata(ksName, cfName); TableMetadata tm2 = tm1.unbuild() .triggers(tm1.triggers.without(triggerName)) .build(); - MigrationManager.announceTableUpdate(tm2); + SchemaTestUtil.announceTableUpdate(tm2); TableMetadata tm3 = Schema.instance.getTableMetadata(ksName, cfName); assertTrue(tm3.triggers.isEmpty()); diff --git a/test/unit/org/apache/cassandra/utils/CassandraGenerators.java b/test/unit/org/apache/cassandra/utils/CassandraGenerators.java index 169363a1aa..ad9cb6b6aa 100644 --- a/test/unit/org/apache/cassandra/utils/CassandraGenerators.java +++ b/test/unit/org/apache/cassandra/utils/CassandraGenerators.java @@ -283,7 +283,7 @@ public final class CassandraGenerators { // to make sure the correct indents are taken, convert to CQL, then replace newlines with the indents // then prefix with the indents. - String cql = SchemaCQLHelper.getTableMetadataAsCQL((TableMetadata) value); + String cql = SchemaCQLHelper.getTableMetadataAsCQL((TableMetadata) value, null); cql = NEWLINE_PATTERN.matcher(cql).replaceAll(Matcher.quoteReplacement("\n " + spacer)); cql = "\n " + spacer + cql; value = cql; diff --git a/tools/stress/src/org/apache/cassandra/io/sstable/StressCQLSSTableWriter.java b/tools/stress/src/org/apache/cassandra/io/sstable/StressCQLSSTableWriter.java index 205b695399..4827ebd03d 100644 --- a/tools/stress/src/org/apache/cassandra/io/sstable/StressCQLSSTableWriter.java +++ b/tools/stress/src/org/apache/cassandra/io/sstable/StressCQLSSTableWriter.java @@ -35,6 +35,7 @@ import org.apache.cassandra.schema.TableId; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.schema.Schema; +import org.apache.cassandra.schema.SchemaTransformations; import org.apache.cassandra.cql3.CQLFragmentParser; import org.apache.cassandra.cql3.ColumnSpecification; import org.apache.cassandra.cql3.CqlParser; @@ -570,15 +571,14 @@ public class StressCQLSSTableWriter implements Closeable } } - private static void createTypes(String keyspace, List typeStatements) + private static Types createTypes(String keyspace, List typeStatements) { KeyspaceMetadata ksm = Schema.instance.getKeyspaceMetadata(keyspace); Types.RawBuilder builder = Types.rawBuilder(keyspace); for (CreateTypeStatement.Raw st : typeStatements) st.addToRawBuilder(builder); - ksm = ksm.withSwapped(builder.build()); - Schema.instance.load(ksm); + return builder.build(); } public static ColumnFamilyStore createOfflineTable(String schema, List directoryList) @@ -594,10 +594,10 @@ public class StressCQLSSTableWriter implements Closeable { String keyspace = schemaStatement.keyspace(); - if (Schema.instance.getKeyspaceMetadata(keyspace) == null) - Schema.instance.load(KeyspaceMetadata.create(keyspace, KeyspaceParams.simple(1))); + Schema.instance.transform(SchemaTransformations.addKeyspace(KeyspaceMetadata.create(keyspace, KeyspaceParams.simple(1)), true)); - createTypes(keyspace, typeStatements); + Types types = createTypes(keyspace, typeStatements); + Schema.instance.transform(SchemaTransformations.addTypes(types, true)); KeyspaceMetadata ksm = Schema.instance.getKeyspaceMetadata(keyspace); @@ -621,7 +621,7 @@ public class StressCQLSSTableWriter implements Closeable ColumnFamilyStore cfs = ColumnFamilyStore.createColumnFamilyStore(ks, tableMetadata.name, TableMetadataRef.forOfflineTools(tableMetadata), directories, false, false, true); ks.initCfCustom(cfs); - Schema.instance.load(ksm.withSwapped(ksm.tables.with(cfs.metadata()))); + Schema.instance.transform(SchemaTransformations.addTable(tableMetadata, true)); return cfs; } diff --git a/tools/stress/src/org/apache/cassandra/stress/CompactionStress.java b/tools/stress/src/org/apache/cassandra/stress/CompactionStress.java index 25ad29a35e..a681dd8b3e 100644 --- a/tools/stress/src/org/apache/cassandra/stress/CompactionStress.java +++ b/tools/stress/src/org/apache/cassandra/stress/CompactionStress.java @@ -216,7 +216,6 @@ public abstract class CompactionStress implements Runnable public void run() { //Setup - SystemKeyspace.finishStartup(); //needed for early-open CompactionManager.instance.setMaximumCompactorThreads(threads); CompactionManager.instance.setCoreCompactorThreads(threads); CompactionManager.instance.setRate(0);