This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch cassandra-6.0
in repository https://gitbox.apache.org/repos/asf/cassandra.git


The following commit(s) were added to refs/heads/cassandra-6.0 by this push:
     new 896d1d6415 Defer the creation of system_cluster_metadata keyspace 
until CMS initialization
896d1d6415 is described below

commit 896d1d64157e2d4064b5931cdb907267b629846e
Author: Sam Tunnicliffe <[email protected]>
AuthorDate: Mon Jun 29 16:54:02 2026 +0100

    Defer the creation of system_cluster_metadata keyspace until CMS 
initialization
    
    * Insert the actual PreInitialize entry in the distribute metadata log
      table using a callback after the log is bootstrapped.
    * Remove the implicit insert on subsequent commit, i.e. of the Initialize 
entry
    * Enables the full specifics of PreInit to be encoded in the serialized 
form,
      removing the special casing for the first CMS member and other nodes
    * Correctly invalidate cached KeyspaceMetadata when in a pre-initialized 
state
    * Update Host ids in the system.peers_v2 table directly following CMS 
initialization
    * Initialise gossip/local host id after CMS initialization completes
    * Clean up CMS initialization errors which occur after the PreInitialize 
stage
    
    Co-authored-by: Alex Petrov <[email protected]>
    Co-authored-by: Marcus Eriksson <[email protected]>
---
 CHANGES.txt                                        |   1 +
 .../apache/cassandra/db/SystemPeersValidator.java  |   3 +
 .../schema/DistributedMetadataLogKeyspace.java     |  31 ++--
 .../apache/cassandra/schema/DistributedSchema.java |  63 ++++----
 .../apache/cassandra/service/StorageService.java   |  14 +-
 .../cassandra/tcm/AbstractLocalProcessor.java      |   2 +-
 .../org/apache/cassandra/tcm/CMSOperations.java    |   4 +-
 .../org/apache/cassandra/tcm/ClusterMetadata.java  |  23 ++-
 .../cassandra/tcm/ClusterMetadataService.java      |  64 +++++++-
 src/java/org/apache/cassandra/tcm/Startup.java     |  33 +++--
 .../cassandra/tcm/compatibility/GossipHelper.java  |   4 +-
 .../tcm/listeners/LegacyStateListener.java         |   7 +-
 .../tcm/listeners/UpgradeMigrationListener.java    |  17 ++-
 .../org/apache/cassandra/tcm/log/LocalLog.java     |  32 ++--
 .../apache/cassandra/tcm/membership/Directory.java |  26 ++++
 .../tcm/migration/CMSInitializationException.java  |  27 ++++
 .../tcm/migration/CMSInitializationRequest.java    |  10 ++
 .../apache/cassandra/tcm/migration/Election.java   |  20 +++
 .../cassandra/tcm/ownership/DataPlacements.java    |   6 +
 .../tcm/transformations/cms/Initialize.java        |   4 +-
 .../tcm/transformations/cms/PreInitialize.java     |  41 +++---
 .../ClusterMetadataSingleNodeUpgradeTest.java      |  12 +-
 ...> ClusterMetadataUpgradeDC2InitializeTest.java} |  41 ++++--
 .../upgrade/ClusterMetadataUpgradeHarryTest.java   |   2 +
 ...sterMetadataUpgradeInconsistentPeersV2Test.java | 114 ++++++++++++++
 .../ClusterMetadataUpgradePeersHostIdsTest.java    | 109 ++++++++++++++
 .../upgrade/ClusterMetadataUpgradeTest.java        |  10 +-
 ...lusterMetadataUpgradeUnexpectedFailureTest.java | 163 +++++++++++++++++----
 .../unit/org/apache/cassandra/ServerTestUtils.java |   8 +-
 .../cassandra/schema/DistributedSchemaTest.java    |   5 +-
 .../cassandra/tcm/log/DistributedLogStateTest.java |   7 +-
 .../tcm/log/LogListenerNotificationTest.java       |   2 +-
 .../cassandra/utils/CassandraGenerators.java       |   2 +-
 33 files changed, 728 insertions(+), 179 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index cbfa9a393e..990378b6f3 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 6.0-alpha2
+ * Defer creation of the system_cluster_metadata keyspace until CMS 
initialization (CASSANDRA-21477)
  * Support direct I/O for background SSTable writes (CASSANDRA-21134)
  * Relax assertion on partitioner instances in SinglePartitionReadCommand 
(CASSANDRA-21251)
  * Report cancelled read command execution to coordinator as a 
RequestFailure.TIMEOUT (CASSANDRA-21468)
diff --git a/src/java/org/apache/cassandra/db/SystemPeersValidator.java 
b/src/java/org/apache/cassandra/db/SystemPeersValidator.java
index 2f490a4bb4..21dbd4b305 100644
--- a/src/java/org/apache/cassandra/db/SystemPeersValidator.java
+++ b/src/java/org/apache/cassandra/db/SystemPeersValidator.java
@@ -34,6 +34,7 @@ import org.apache.cassandra.db.virtual.PeersTable;
 import org.apache.cassandra.locator.InetAddressAndPort;
 import org.apache.cassandra.schema.SchemaConstants;
 import org.apache.cassandra.tcm.ClusterMetadata;
+import org.apache.cassandra.tcm.Epoch;
 import org.apache.cassandra.tcm.membership.Location;
 import org.apache.cassandra.tcm.membership.NodeAddresses;
 import org.apache.cassandra.tcm.membership.NodeId;
@@ -68,6 +69,8 @@ public final class SystemPeersValidator
 
     public static void validateAndRepair(ClusterMetadata metadata)
     {
+        if (metadata.epoch.isBefore(Epoch.FIRST))
+            return;
         Map<InetAddressAndPort, UntypedResultSet.Row> peersV2Rows = 
getPeersV2Rows();
         Map<InetAddress, UntypedResultSet.Row> peersRows = getPeersRows();
 
diff --git 
a/src/java/org/apache/cassandra/schema/DistributedMetadataLogKeyspace.java 
b/src/java/org/apache/cassandra/schema/DistributedMetadataLogKeyspace.java
index 02c710ae49..ce8ebd5e10 100644
--- a/src/java/org/apache/cassandra/schema/DistributedMetadataLogKeyspace.java
+++ b/src/java/org/apache/cassandra/schema/DistributedMetadataLogKeyspace.java
@@ -46,6 +46,7 @@ import org.apache.cassandra.tcm.log.LogState;
 import org.apache.cassandra.tcm.transformations.cms.PreInitialize;
 import org.apache.cassandra.utils.JVMStabilityInspector;
 
+import static 
org.apache.cassandra.schema.SchemaConstants.METADATA_KEYSPACE_NAME;
 import static org.apache.cassandra.tcm.Epoch.FIRST;
 
 public final class DistributedMetadataLogKeyspace
@@ -65,7 +66,7 @@ public final class DistributedMetadataLogKeyspace
      */
     public static final long GENERATION = 0;
 
-    public static final TableId LOG_TABLE_ID = 
TableId.unsafeDeterministic(SchemaConstants.METADATA_KEYSPACE_NAME, TABLE_NAME);
+    public static final TableId LOG_TABLE_ID = 
TableId.unsafeDeterministic(METADATA_KEYSPACE_NAME, TABLE_NAME);
     public static final String LOG_TABLE_CQL = "CREATE TABLE %s.%s ("
                                                + "epoch bigint,"
                                                + "entry_id bigint,"
@@ -80,16 +81,16 @@ public final class DistributedMetadataLogKeyspace
                                                           
"compaction_window_size","1")))
         .build();
 
-    public static boolean initialize() throws IOException
+    public static boolean insertPreInitialize(PreInitialize preInit) throws 
IOException
     {
         try
         {
             String init = String.format("INSERT INTO %s.%s (epoch, 
transformation, kind, entry_id) " +
                                         "VALUES(?, ?, ?, ?) " +
-                                        "IF NOT EXISTS", 
SchemaConstants.METADATA_KEYSPACE_NAME, TABLE_NAME);
+                                        "IF NOT EXISTS", 
METADATA_KEYSPACE_NAME, TABLE_NAME);
             UntypedResultSet result = QueryProcessor.execute(init, 
ConsistencyLevel.QUORUM,
                                                              FIRST.getEpoch(),
-                                                             
Transformation.Kind.PRE_INITIALIZE_CMS.toVersionedBytes(PreInitialize.blank()),
+                                                             
Transformation.Kind.PRE_INITIALIZE_CMS.toVersionedBytes(preInit),
                                                              
Transformation.Kind.PRE_INITIALIZE_CMS.id,
                                                              
Entry.Id.NONE.entryId);
 
@@ -124,21 +125,25 @@ public final class DistributedMetadataLogKeyspace
     {
         try
         {
-            if (previousEpoch.is(FIRST) && !initialize())
+            // log is not initialized yet this is unexpected
+            if (previousEpoch.isBefore(FIRST))
+            {
+                logger.warn("Previous epoch {} indicates that the {} has not 
been initialized yet, " +
+                            "not committing entry {}/{} at epoch {}",
+                            previousEpoch, METADATA_KEYSPACE_NAME, entryId, 
transform, nextEpoch);
                 return false;
+            }
 
-            // TODO get lowest supported metadata version from ClusterMetadata
-            ByteBuffer serializedEvent = 
transform.kind().toVersionedBytes(transform);
-
+            ByteBuffer serializedTransform = 
transform.kind().toVersionedBytes(transform);
             String query = String.format("INSERT INTO %s.%s (epoch, entry_id, 
transformation, kind) " +
                                          "VALUES (?, ?, ?, ?) " +
                                          "IF NOT EXISTS;",
-                                         
SchemaConstants.METADATA_KEYSPACE_NAME, TABLE_NAME);
+                                         METADATA_KEYSPACE_NAME, TABLE_NAME);
             UntypedResultSet result = QueryProcessor.execute(query,
                                                              
ConsistencyLevel.QUORUM,
                                                              
nextEpoch.getEpoch(),
                                                              entryId.entryId,
-                                                             serializedEvent,
+                                                             
serializedTransform,
                                                              
transform.kind().id);
 
             return result.one().getBoolean("[applied]");
@@ -186,7 +191,7 @@ public final class DistributedMetadataLogKeyspace
             // note that we want all entries with epoch >= since - but since 
we use a reverse partitioner, we actually
             // want all entries where the token is less than token(since)
             UntypedResultSet resultSet = execute(String.format("SELECT epoch, 
kind, transformation, entry_id FROM %s.%s WHERE token(epoch) <= token(?)",
-                                                               
SchemaConstants.METADATA_KEYSPACE_NAME, TABLE_NAME),
+                                                               
METADATA_KEYSPACE_NAME, TABLE_NAME),
                                                  consistencyLevel, 
since.getEpoch());
             EntryHolder entryHolder = new EntryHolder(since);
             for (UntypedResultSet.Row row : resultSet)
@@ -237,7 +242,7 @@ public final class DistributedMetadataLogKeyspace
 
     private static TableMetadata.Builder parse(String cql, String table, 
String description)
     {
-        return CreateTableStatement.parse(String.format(cql, 
SchemaConstants.METADATA_KEYSPACE_NAME, table), 
SchemaConstants.METADATA_KEYSPACE_NAME)
+        return CreateTableStatement.parse(String.format(cql, 
METADATA_KEYSPACE_NAME, table), METADATA_KEYSPACE_NAME)
                                    .id(LOG_TABLE_ID)
                                    .epoch(FIRST)
                                    .comment(description);
@@ -245,7 +250,7 @@ public final class DistributedMetadataLogKeyspace
 
     public static KeyspaceMetadata initialMetadata(Set<String> 
knownDatacenters)
     {
-        return KeyspaceMetadata.create(SchemaConstants.METADATA_KEYSPACE_NAME, 
new KeyspaceParams(true, ReplicationParams.simpleMeta(1, knownDatacenters), 
FastPathStrategy.simple()), Tables.of(Log));
+        return KeyspaceMetadata.create(METADATA_KEYSPACE_NAME, new 
KeyspaceParams(true, ReplicationParams.simpleMeta(1, knownDatacenters), 
FastPathStrategy.simple()), Tables.of(Log));
     }
 
     public static KeyspaceMetadata initialMetadata(String datacenter)
diff --git a/src/java/org/apache/cassandra/schema/DistributedSchema.java 
b/src/java/org/apache/cassandra/schema/DistributedSchema.java
index 763632fd66..59859d7578 100644
--- a/src/java/org/apache/cassandra/schema/DistributedSchema.java
+++ b/src/java/org/apache/cassandra/schema/DistributedSchema.java
@@ -21,14 +21,13 @@ package org.apache.cassandra.schema;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
 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.stream.Collectors;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
@@ -65,25 +64,6 @@ public class DistributedSchema implements 
MetadataValue<DistributedSchema>
         return new DistributedSchema(Keyspaces.none(), Epoch.EMPTY);
     }
 
-    public static DistributedSchema first(Set<String> knownDatacenters)
-    {
-        // During upgrades from pre-6.0 versions, the replication params of 
the system_cluster_metadata
-        // keyspace using one of the existing DCs. This is so that this 
keyspace does not cause issues
-        // for tooling, clients or control plane systems which may inspect 
schema and have specific
-        // expectations about DC layout. This keyspace is unused until the CMS 
is initialized.
-        // For new clusters which start out on 6.0 or later, this is not 
necessary to the initial
-        // replication params use a empty string for the placeholder DC name.
-
-        // During CMS initialization, the replication of this keyspace will be 
set for real using
-        // the DC of the first node to become a CMS member. This happens in 
the PreInitialize
-        // transformation when executed on the first CMS member.
-
-        if (knownDatacenters.isEmpty())
-                knownDatacenters = Collections.singleton("");
-
-        return new 
DistributedSchema(Keyspaces.of(DistributedMetadataLogKeyspace.initialMetadata(knownDatacenters)),
 Epoch.FIRST);
-    }
-
     private static ImmutableMap<TableId, TableMetadata> 
keyspacesToTableMap(Keyspaces keyspaces)
     {
         ImmutableMap.Builder<TableId, TableMetadata> builder = 
ImmutableMap.builder();
@@ -159,28 +139,23 @@ public class DistributedSchema implements 
MetadataValue<DistributedSchema>
      * @deprecated since TCM, used on upgrade from gossip to populate system 
schema tables with the correct generation
      */
     @Deprecated(since = "TCM")
-    public static List<Pair<KeyspaceMetadata, Long>> 
distributedKeyspacesWithGeneration(Set<String> knownDatacenters)
+    public static List<Pair<KeyspaceMetadata, Long>> 
distributedKeyspacesWithGeneration()
     {
-        return 
ImmutableList.of(Pair.create(DistributedMetadataLogKeyspace.initialMetadata(knownDatacenters),
 DistributedMetadataLogKeyspace.GENERATION),
-                                Pair.create(TraceKeyspace.metadata(), 
TraceKeyspace.GENERATION),
+        return ImmutableList.of(Pair.create(TraceKeyspace.metadata(), 
TraceKeyspace.GENERATION),
                                 
Pair.create(SystemDistributedKeyspace.metadata(), 
SystemDistributedKeyspace.GENERATION),
                                 
Pair.create(AuthKeyspace.metadata(),AuthKeyspace.GENERATION));
     }
 
-    public static DistributedSchema fromSystemTables(Keyspaces keyspaces, 
Set<String> knownDatacenters)
+    public static DistributedSchema fromSystemTables(Keyspaces keyspaces)
     {
-        if 
(!keyspaces.containsKeyspace(SchemaConstants.METADATA_KEYSPACE_NAME))
-        {
-            Keyspaces kss = Keyspaces.none();
-            for (Pair<KeyspaceMetadata, Long> ksmGen : 
distributedKeyspacesWithGeneration(knownDatacenters))
-                kss = kss.with(ksmGen.left);
-            for (KeyspaceMetadata ksm : keyspaces) // on disk keyspaces
-                kss = kss.withAddedOrUpdated(kss.get(ksm.name)
-                                                .map(k -> merged(ksm, k))
-                                                .orElse(ksm));
-            keyspaces = kss;
-        }
-        return new DistributedSchema(keyspaces, Epoch.UPGRADE_GOSSIP);
+        Keyspaces kss = Keyspaces.none();
+        for (Pair<KeyspaceMetadata, Long> ksmGen : 
distributedKeyspacesWithGeneration())
+            kss = kss.with(ksmGen.left);
+        for (KeyspaceMetadata ksm : keyspaces) // on disk keyspaces
+            kss = kss.withAddedOrUpdated(kss.get(ksm.name)
+                                            .map(k -> merged(ksm, k))
+                                            .orElse(ksm));
+        return new DistributedSchema(kss, Epoch.UPGRADE_GOSSIP);
     }
 
     /**
@@ -268,7 +243,8 @@ public class DistributedSchema implements 
MetadataValue<DistributedSchema>
             }
         });
 
-        // Avoid system table side effects during initialization
+        // Avoid system table side effects before initialization, otherwise 
mismatching schema can block CMS
+        // progress as nodes report disagreement. Also, tooling should not 
mutate system tables.
         if (epoch.isEqualOrAfter(Epoch.FIRST) && 
!DatabaseDescriptor.isClientOrToolInitialized())
         {
             Collection<Mutation> mutations = 
SchemaKeyspace.convertSchemaDiffToMutations(ksDiff, 
FBUtilities.timestampMicros());
@@ -481,6 +457,17 @@ public class DistributedSchema implements 
MetadataValue<DistributedSchema>
         });
     }
 
+    public String conciseToString()
+    {
+        return keyspaces.stream()
+                        .map(ksm -> ksm.name + ": {" +
+                                    ksm.tables.stream()
+                                              .map(t -> t.name)
+                                              .collect(Collectors.joining(","))
+                                    + '}')
+                 .collect(Collectors.joining(","));
+    }
+
     public static class Serializer implements 
MetadataSerializer<DistributedSchema>
     {
         public void serialize(DistributedSchema t, DataOutputPlus out, Version 
version) throws IOException
diff --git a/src/java/org/apache/cassandra/service/StorageService.java 
b/src/java/org/apache/cassandra/service/StorageService.java
index 0b46109334..2ad2a5bf27 100644
--- a/src/java/org/apache/cassandra/service/StorageService.java
+++ b/src/java/org/apache/cassandra/service/StorageService.java
@@ -2151,9 +2151,17 @@ public class StorageService extends 
NotificationBroadcasterSupport implements IE
         else
         {
             // Handling the keyspaces which are not handled by CMS like system 
keyspace which uses LocalStrategy.
-            AbstractReplicationStrategy strategy = 
Keyspace.open(keyspace).getReplicationStrategy();
-            for (Range<Token> range : ranges)
-                rangeToEndpointMap.put(range, 
strategy.calculateNaturalReplicas(range.right, metadata));
+            Keyspace ks = Keyspace.openIfExists(keyspace);
+            if (ks != null)
+            {
+                AbstractReplicationStrategy strategy = 
ks.getReplicationStrategy();
+                for (Range<Token> range : ranges)
+                    rangeToEndpointMap.put(range, 
strategy.calculateNaturalReplicas(range.right, metadata));
+            }
+            else
+            {
+                throw new IllegalArgumentException("Unknown keyspace " + 
keyspace);
+            }
         }
 
         return new EndpointsByRange(rangeToEndpointMap);
diff --git a/src/java/org/apache/cassandra/tcm/AbstractLocalProcessor.java 
b/src/java/org/apache/cassandra/tcm/AbstractLocalProcessor.java
index 0f42bae462..a245377f91 100644
--- a/src/java/org/apache/cassandra/tcm/AbstractLocalProcessor.java
+++ b/src/java/org/apache/cassandra/tcm/AbstractLocalProcessor.java
@@ -196,4 +196,4 @@ public abstract class AbstractLocalProcessor implements 
Processor
 
     public abstract ClusterMetadata fetchLogAndWait(Epoch waitFor, Retry 
retryPolicy);
     protected abstract boolean tryCommitOne(Entry.Id entryId, Transformation 
transform, Epoch previousEpoch, Epoch nextEpoch);
-}
+}
\ No newline at end of file
diff --git a/src/java/org/apache/cassandra/tcm/CMSOperations.java 
b/src/java/org/apache/cassandra/tcm/CMSOperations.java
index aa49a8ac3b..d2b18ce361 100644
--- a/src/java/org/apache/cassandra/tcm/CMSOperations.java
+++ b/src/java/org/apache/cassandra/tcm/CMSOperations.java
@@ -154,14 +154,14 @@ public class CMSOperations implements CMSOperationsMBean
         ClusterMetadata metadata = ClusterMetadata.current();
         String members = 
metadata.fullCMSMembers().stream().sorted().map(Object::toString).collect(Collectors.joining(","));
         info.put(MEMBERS, members);
-        info.put(NEEDS_RECONFIGURATION, 
Boolean.toString(needsReconfiguration(metadata)));
+        info.put(NEEDS_RECONFIGURATION, 
Boolean.toString(metadata.epoch.isBefore(Epoch.FIRST) || 
needsReconfiguration(metadata)));
         info.put(IS_MEMBER, 
Boolean.toString(cms.isCurrentMember(FBUtilities.getBroadcastAddressAndPort())));
         info.put(SERVICE_STATE, 
ClusterMetadataService.state(metadata).toString());
         info.put(IS_MIGRATING, Boolean.toString(cms.isMigrating()));
         info.put(EPOCH, Long.toString(metadata.epoch.getEpoch()));
         info.put(LOCAL_PENDING, 
Integer.toString(cms.log().pendingBufferSize()));
         info.put(COMMITS_PAUSED, Boolean.toString(cms.commitsPaused()));
-        info.put(REPLICATION_FACTOR, 
ReplicationParams.meta(metadata).toString());
+        info.put(REPLICATION_FACTOR, metadata.epoch.isBefore(Epoch.FIRST) ? "" 
: ReplicationParams.meta(metadata).toString());
         info.put(CMS_ID, Integer.toString(metadata.metadataIdentifier));
         return info;
     }
diff --git a/src/java/org/apache/cassandra/tcm/ClusterMetadata.java 
b/src/java/org/apache/cassandra/tcm/ClusterMetadata.java
index 5645b439e4..40dcb7fb4c 100644
--- a/src/java/org/apache/cassandra/tcm/ClusterMetadata.java
+++ b/src/java/org/apache/cassandra/tcm/ClusterMetadata.java
@@ -21,6 +21,7 @@ package org.apache.cassandra.tcm;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -131,7 +132,7 @@ public class ClusterMetadata
     @VisibleForTesting
     public ClusterMetadata(IPartitioner partitioner, Directory directory)
     {
-        this(partitioner, directory, 
DistributedSchema.first(directory.knownDatacenters()));
+        this(partitioner, directory, DistributedSchema.empty());
     }
 
     @VisibleForTesting
@@ -216,6 +217,9 @@ public class ClusterMetadata
 
     public Set<InetAddressAndPort> fullCMSMembers()
     {
+        if (epoch.isBefore(Epoch.FIRST))
+            return Collections.emptySet();
+
         if (fullCMSEndpoints == null)
             this.fullCMSEndpoints = 
ImmutableSet.copyOf(placements.get(ReplicationParams.meta(this)).reads.byEndpoint().keySet());
         return fullCMSEndpoints;
@@ -223,6 +227,9 @@ public class ClusterMetadata
 
     public Set<NodeId> fullCMSMemberIds()
     {
+        if (epoch.isBefore(Epoch.FIRST))
+            return Collections.emptySet();
+
         if (fullCMSIds == null)
             this.fullCMSIds = 
placements.get(ReplicationParams.meta(this)).reads.byEndpoint().keySet().stream().map(directory::peerId).collect(toImmutableSet());
         return fullCMSIds;
@@ -230,6 +237,9 @@ public class ClusterMetadata
 
     public EndpointsForRange fullCMSMembersAsReplicas()
     {
+        if (epoch.isBefore(Epoch.FIRST))
+            return EndpointsForRange.empty(MetaStrategy.entireRange);
+
         if (fullCMSReplicas == null)
             fullCMSReplicas = 
placements.get(ReplicationParams.meta(this)).reads.forRange(MetaStrategy.entireRange).get();
         return fullCMSReplicas;
@@ -309,7 +319,7 @@ public class ClusterMetadata
     @SuppressWarnings("unchecked")
     private static <V> V capLastModified(MetadataValue<V> value, Epoch 
maxEpoch)
     {
-        return value == null || value.lastModified().isEqualOrBefore(maxEpoch)
+        return value == null || 
(value.lastModified().isEqualOrAfter(Epoch.EMPTY) && 
value.lastModified().isEqualOrBefore(maxEpoch))
                ? (V)value
                : value.withLastModified(maxEpoch);
     }
@@ -946,6 +956,15 @@ public class ClusterMetadata
                '}';
     }
 
+    public String conciseToString()
+    {
+        return "ClusterMetadata{" + "epoch=" + epoch +
+               ", schema=" + schema.conciseToString() +
+               ", directory=" + directory.conciseToString(tokenMap.asMap()) +
+               ", placements=" + placements.conciseToString() +
+               '}';
+    }
+
     @Override
     public boolean equals(Object o)
     {
diff --git a/src/java/org/apache/cassandra/tcm/ClusterMetadataService.java 
b/src/java/org/apache/cassandra/tcm/ClusterMetadataService.java
index 9c2c4ca0bf..6caea0001f 100644
--- a/src/java/org/apache/cassandra/tcm/ClusterMetadataService.java
+++ b/src/java/org/apache/cassandra/tcm/ClusterMetadataService.java
@@ -29,6 +29,7 @@ import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Supplier;
 
@@ -52,6 +53,7 @@ import org.apache.cassandra.metrics.TCMMetrics;
 import org.apache.cassandra.net.IVerbHandler;
 import org.apache.cassandra.net.Message;
 import org.apache.cassandra.net.MessageDelivery;
+import org.apache.cassandra.schema.DistributedMetadataLogKeyspace;
 import org.apache.cassandra.schema.DistributedSchema;
 import org.apache.cassandra.schema.Keyspaces;
 import org.apache.cassandra.schema.ReplicationParams;
@@ -79,6 +81,7 @@ import 
org.apache.cassandra.tcm.serialization.VerboseMetadataSerializer;
 import org.apache.cassandra.tcm.serialization.Version;
 import org.apache.cassandra.tcm.transformations.ForceSnapshot;
 import org.apache.cassandra.tcm.transformations.TriggerSnapshot;
+import org.apache.cassandra.tcm.transformations.cms.PreInitialize;
 import org.apache.cassandra.tcm.transformations.cms.PrepareCMSReconfiguration;
 import org.apache.cassandra.utils.FBUtilities;
 import org.apache.cassandra.utils.JVMStabilityInspector;
@@ -138,6 +141,14 @@ public class ClusterMetadataService
     private final LocalLog log;
     private final MetadataSnapshots snapshots;
 
+    /*
+     * Special callback for execution when the PreInitialize transformation is 
used to bootstrap the cluster metadata
+     * log. In practice, this is only relevant when using PaxosBackedProcessor 
which needs to insert the PreInitialize
+     * entry into the distributed log table, but is unable to do so until it 
has been enacted, the keyspace created and
+     * the replication configured.
+     */
+    private final Consumer<PreInitialize> logBootstrapCallback;
+
     private final IVerbHandler<LogState> replicationHandler;
     private final IVerbHandler<LogState> logNotifyHandler;
     private final IVerbHandler<FetchCMSLog> fetchLogHandler;
@@ -181,13 +192,17 @@ public class ClusterMetadataService
         if 
(CassandraRelevantProperties.TCM_USE_ATOMIC_LONG_PROCESSOR.getBoolean())
         {
             log = logSpec.sync().withStorage(new 
AtomicLongBackedProcessor.InMemoryStorage()).createLog();
-            localProcessor = wrapProcessor.apply(new 
AtomicLongBackedProcessor(log, logSpec.isReset()));
+            AtomicLongBackedProcessor processor =  new 
AtomicLongBackedProcessor(log, logSpec.isReset());
+            logBootstrapCallback = logBootstrapCallback(processor);
+            localProcessor = wrapProcessor.apply(processor);
             fetchLogHandler = new FetchCMSLog.Handler((e, ignored) -> 
logSpec.storage().getLogState(e));
         }
         else
         {
             log = logSpec.async().createLog();
-            localProcessor = wrapProcessor.apply(new 
PaxosBackedProcessor(log));
+            PaxosBackedProcessor processor = new PaxosBackedProcessor(log);
+            logBootstrapCallback = logBootstrapCallback(processor);
+            localProcessor = wrapProcessor.apply(processor);
             fetchLogHandler = new FetchCMSLog.Handler();
         }
 
@@ -234,8 +249,12 @@ public class ClusterMetadataService
         commitRequestHandler = isMemberOfOwnershipGroup ? new 
Commit.Handler(processor, replicator, () -> LOCAL) : null;
 
         peerLogFetcher = new PeerLogFetcher(log);
+        logBootstrapCallback = logBootstrapCallback(processor);
     }
 
+    /**
+     * Only called from initializeForTools
+     */
     private ClusterMetadataService(PlacementProvider placementProvider,
                                    MetadataSnapshots snapshots,
                                    LocalLog log,
@@ -257,6 +276,7 @@ public class ClusterMetadataService
         this.fetchLogHandler = fetchLogHandler;
         this.commitRequestHandler = commitRequestHandler;
         this.peerLogFetcher = peerLogFetcher;
+        this.logBootstrapCallback = logBootstrapCallback(processor);
     }
 
     @SuppressWarnings("resource")
@@ -265,8 +285,7 @@ public class ClusterMetadataService
         if (instance != null)
             return;
         String localDC = DatabaseDescriptor.getLocalDataCenter();
-        ClusterMetadata emptyFromSystemTables = 
emptyWithSchemaFromSystemTables(Collections.singleton(localDC))
-                                                .forceEpoch(Epoch.EMPTY);
+        ClusterMetadata emptyFromSystemTables = 
emptyWithSchemaFromSystemTables().forceEpoch(Epoch.EMPTY);
 
         LocalLog.LogSpec logSpec = LocalLog.logSpec()
                                            
.withInitialState(emptyFromSystemTables)
@@ -294,7 +313,7 @@ public class ClusterMetadataService
                                                                 new 
PeerLogFetcher(log));
 
         log.readyUnchecked();
-        log.bootstrap(FBUtilities.getBroadcastAddressAndPort(), localDC);
+        log.bootstrap(FBUtilities.getBroadcastAddressAndPort(), localDC, (p) 
-> {});
         ClusterMetadataService.setInstance(cms);
     }
 
@@ -336,10 +355,43 @@ public class ClusterMetadataService
                                                                 null);
 
         log.readyUnchecked();
-        log.bootstrap(FBUtilities.getBroadcastAddressAndPort(), localDC);
+        log.bootstrap(FBUtilities.getBroadcastAddressAndPort(), localDC, (p) 
-> {});
         ClusterMetadataService.setInstance(cms);
     }
 
+    /*
+     * Hook to be executed when the LocalLog is bootstrapped with the 
PreInitialize transformation. This is done on
+     * the first CMS member to set up the initial replication and data 
placements for the metadata keyspace.
+     */
+    public Consumer<PreInitialize> logBootstrapCallback()
+    {
+        return logBootstrapCallback;
+    }
+
+    private static Consumer<PreInitialize> logBootstrapCallback(Processor 
processor)
+    {
+        if (processor instanceof PaxosBackedProcessor)
+        {
+            // Insert an entry containing the PRE_INITIALIZE_CMS transform at 
Epoch.FIRST in the distributed
+            // log table. This can only be done after the log is bootstrapped 
as it depends on the effects of
+            // that transform on ClusterMetadata.
+            return preInit -> {
+                try
+                {
+                    if 
(DistributedMetadataLogKeyspace.insertPreInitialize(preInit))
+                        logger.info("Successfully inserted pre-initialize 
entry into distributed metadata log");
+                    else
+                        throw new IllegalStateException("Failed to insert 
pre-initialize entry into distributed metadata log. Check server for details");
+                }
+                catch (IOException e)
+                {
+                    throw new IllegalStateException("Unable to pre-initialize 
distributed metadata log table", e);
+                }
+            };
+        }
+        // otherwise, this is a noop.
+        return preInit -> {};
+    }
 
     @SuppressWarnings("resource")
     public static void initializeForClients()
diff --git a/src/java/org/apache/cassandra/tcm/Startup.java 
b/src/java/org/apache/cassandra/tcm/Startup.java
index 2bfe4b4cc9..57169d9876 100644
--- a/src/java/org/apache/cassandra/tcm/Startup.java
+++ b/src/java/org/apache/cassandra/tcm/Startup.java
@@ -65,6 +65,7 @@ import org.apache.cassandra.tcm.log.LogStorage;
 import org.apache.cassandra.tcm.log.SystemKeyspaceStorage;
 import org.apache.cassandra.tcm.membership.NodeId;
 import org.apache.cassandra.tcm.membership.NodeState;
+import org.apache.cassandra.tcm.migration.CMSInitializationException;
 import org.apache.cassandra.tcm.migration.CMSInitializationRequest;
 import org.apache.cassandra.tcm.migration.Election;
 import org.apache.cassandra.tcm.ownership.UniformRangePlacement;
@@ -134,11 +135,14 @@ import static 
org.apache.cassandra.utils.FBUtilities.getBroadcastAddressAndPort;
     }
 
     /**
-     * Make this node a _first_ CMS node.
+     * Make this node the _first_ CMS node.
      * <p>
-     * (1) Append PreInitialize transformation to local in-memory log. When 
distributed metadata keyspace is initialized, a no-op transformation will
-     * be added to other nodes. This is required since as of now, no node 
actually owns distributed metadata keyspace.
-     * (2) Commit Initialize transformation, which holds a snapshot of 
metadata as of now.
+     * (1) Append PreInitialize transformation to local in-memory log.
+     * (1a) Once this enacted and the distributed metadata keyspace is 
initialized, the PreInitialize transformation
+     * will be inserted into the log table. This is required since as before 
this point, the keyspace was not availble
+     * or configured with any replication or placements.
+     * (2) Commit Initialize transformation, which holds a complete snapshot 
of metadata as of now.
+     * Other nodes in the cluster, if there are any, will receive both of 
these log entries and enact them locally.
      * <p>
      * This process is applicable for gossip upgrades as well as regular 
vote-and-startup process.
      */
@@ -146,11 +150,17 @@ import static 
org.apache.cassandra.utils.FBUtilities.getBroadcastAddressAndPort;
     {
         InetAddressAndPort addr = FBUtilities.getBroadcastAddressAndPort();
         String datacenter = DatabaseDescriptor.getLocator().local().datacenter;
-        ClusterMetadataService.instance().log().bootstrap(addr, datacenter);
+        ClusterMetadataService cms = ClusterMetadataService.instance();
+        cms.log().bootstrap(addr, datacenter, cms.logBootstrapCallback());
         ClusterMetadata metadata =  ClusterMetadata.current();
         assert ClusterMetadataService.state() == LOCAL : String.format("Can't 
initialize as node hasn't transitioned to CMS state. State: %s.\n%s", 
ClusterMetadataService.state(),  metadata);
         Initialize initialize = new 
Initialize(metadata.initializeClusterIdentifier(addr.hashCode()));
-        ClusterMetadataService.instance().commit(initialize);
+        ClusterMetadataService.instance().commit(initialize,
+                                                 m -> { 
logger.info("INITIALIZE_CMS committed successfully"); return m;},
+                                                 (code, message) -> {
+                                                     
logger.info("INITIALIZE_CMS commit failure: ({}) {}", code, message);
+                                                     throw new 
CMSInitializationException();
+                                                 });
     }
 
     public static void initializeAsNonCmsNode(Function<Processor, Processor> 
wrapProcessor) throws StartupException
@@ -262,9 +272,9 @@ import static 
org.apache.cassandra.utils.FBUtilities.getBroadcastAddressAndPort;
         Election.instance.migrated();
     }
 
-    private static void updateSystemSchemaTables(Set<String> knownDatacenters)
+    private static void updateSystemSchemaTables()
     {
-        List<Pair<KeyspaceMetadata, Long>> kss = 
DistributedSchema.distributedKeyspacesWithGeneration(knownDatacenters);
+        List<Pair<KeyspaceMetadata, Long>> kss = 
DistributedSchema.distributedKeyspacesWithGeneration();
         List<Mutation> mutations = new ArrayList<>();
         for (Pair<KeyspaceMetadata, Long> ksm : kss)
         {
@@ -279,9 +289,8 @@ import static 
org.apache.cassandra.utils.FBUtilities.getBroadcastAddressAndPort;
      */
     public static void initializeFromGossip(Function<Processor, Processor> 
wrapProcessor, Runnable initMessaging) throws StartupException
     {
-        Set<String> knownDcs = SystemKeyspace.allKnownDatacenters();
-        updateSystemSchemaTables(knownDcs);
-        ClusterMetadata emptyFromSystemTables = 
emptyWithSchemaFromSystemTables(knownDcs);
+        updateSystemSchemaTables();
+        ClusterMetadata emptyFromSystemTables = 
emptyWithSchemaFromSystemTables();
         LocalLog.LogSpec logSpec = LocalLog.logSpec()
                                            
.withInitialState(emptyFromSystemTables)
                                            
.afterReplay(Startup::scrubDataDirectories,
@@ -332,7 +341,7 @@ import static 
org.apache.cassandra.utils.FBUtilities.getBroadcastAddressAndPort;
 
         logger.debug("Got epStates {}", epStates);
         ClusterMetadata initial = 
fromEndpointStates(emptyFromSystemTables.schema, epStates);
-        logger.debug("Created initial ClusterMetadata {}", initial);
+        logger.info("Created initial ClusterMetadata {}", 
initial.conciseToString());
         ClusterMetadataService.instance().setFromGossip(initial);
         Gossiper.instance.clearUnsafe();
 
diff --git a/src/java/org/apache/cassandra/tcm/compatibility/GossipHelper.java 
b/src/java/org/apache/cassandra/tcm/compatibility/GossipHelper.java
index 4ba870837a..965751c554 100644
--- a/src/java/org/apache/cassandra/tcm/compatibility/GossipHelper.java
+++ b/src/java/org/apache/cassandra/tcm/compatibility/GossipHelper.java
@@ -287,7 +287,7 @@ public class GossipHelper
         return NodeVersion.fromCassandraVersion(cassandraVersion);
     }
 
-    public static ClusterMetadata emptyWithSchemaFromSystemTables(Set<String> 
allKnownDatacenters)
+    public static ClusterMetadata emptyWithSchemaFromSystemTables()
     {
         // If this instance was previously upgraded then subsequently 
downgraded, the metadata keyspace may have been
         // added to system_schema tables. If so, don't include it in the 
initial schema as this will cause it to be
@@ -297,7 +297,7 @@ public class GossipHelper
                                             .filter(k -> 
!k.name.equals(SchemaConstants.METADATA_KEYSPACE_NAME));
         return new ClusterMetadata(Epoch.UPGRADE_STARTUP,
                                    DatabaseDescriptor.getPartitioner(),
-                                   
DistributedSchema.fromSystemTables(keyspaces, allKnownDatacenters),
+                                   
DistributedSchema.fromSystemTables(keyspaces),
                                    Directory.EMPTY,
                                    new 
TokenMap(DatabaseDescriptor.getPartitioner()),
                                    DataPlacements.empty(),
diff --git 
a/src/java/org/apache/cassandra/tcm/listeners/LegacyStateListener.java 
b/src/java/org/apache/cassandra/tcm/listeners/LegacyStateListener.java
index 52606dc115..d8b9dd746e 100644
--- a/src/java/org/apache/cassandra/tcm/listeners/LegacyStateListener.java
+++ b/src/java/org/apache/cassandra/tcm/listeners/LegacyStateListener.java
@@ -41,6 +41,7 @@ import org.apache.cassandra.net.MessagingService;
 import org.apache.cassandra.schema.Schema;
 import org.apache.cassandra.service.StorageService;
 import org.apache.cassandra.tcm.ClusterMetadata;
+import org.apache.cassandra.tcm.Epoch;
 import org.apache.cassandra.tcm.MultiStepOperation;
 import org.apache.cassandra.tcm.compatibility.GossipHelper;
 import org.apache.cassandra.tcm.membership.Directory;
@@ -73,8 +74,12 @@ public class LegacyStateListener implements ChangeListener
         Set<NodeId> changed = new HashSet<>();
         for (NodeId node : next.directory.peerIds())
         {
-            if (directoryEntryChangedFor(node, prev.directory, next.directory) 
|| !prev.tokenMap.tokens(node).equals(next.tokenMap.tokens(node)))
+            if (prev.epoch.isEqualOrBefore(Epoch.FIRST)
+                || directoryEntryChangedFor(node, prev.directory, 
next.directory)
+                || 
!prev.tokenMap.tokens(node).equals(next.tokenMap.tokens(node)))
+            {
                 changed.add(node);
+            }
         }
 
         // next.myNodeId() can be null during replay (before we have 
registered) but if it is present and
diff --git 
a/src/java/org/apache/cassandra/tcm/listeners/UpgradeMigrationListener.java 
b/src/java/org/apache/cassandra/tcm/listeners/UpgradeMigrationListener.java
index e66e36a4d2..feb191d32a 100644
--- a/src/java/org/apache/cassandra/tcm/listeners/UpgradeMigrationListener.java
+++ b/src/java/org/apache/cassandra/tcm/listeners/UpgradeMigrationListener.java
@@ -21,9 +21,11 @@ package org.apache.cassandra.tcm.listeners;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import org.apache.cassandra.db.SystemKeyspace;
 import org.apache.cassandra.gms.Gossiper;
 import org.apache.cassandra.tcm.ClusterMetadata;
 import org.apache.cassandra.tcm.Epoch;
+import org.apache.cassandra.tcm.membership.NodeId;
 import org.apache.cassandra.utils.CassandraVersion;
 
 /**
@@ -42,11 +44,20 @@ public class UpgradeMigrationListener implements 
ChangeListener
     {
         if (prev.epoch.equals(Epoch.UPGRADE_GOSSIP))
         {
-            logger.info("Detected upgrade from gossip mode, updating my host 
id in gossip to {}", next.myNodeId());
-            Gossiper.instance.mergeNodeToGossip(next.myNodeId(), next);
+            logger.info("Detected upgrade from gossip mode");
+            return;
+        }
+        else if (prev.epoch.equals(Epoch.FIRST) && !next.directory.isEmpty()) 
// directory is non-empty after initialization during gossip upgrade
+        {
+            NodeId localId = next.myNodeId();
+            if (localId != null)
+            {
+                logger.info("Initialized CMS, updating local host id to {}", 
next.myNodeId());
+                SystemKeyspace.setLocalHostId(next.myNodeId().toUUID());
+                Gossiper.instance.mergeNodeToGossip(next.myNodeId(), next);
+            }
             if (Gossiper.instance.getQuarantineDisabled())
                 Gossiper.instance.clearQuarantinedEndpoints();
-            return;
         }
 
         CassandraVersion prevMinVersion = 
prev.directory.clusterMinVersion.cassandraVersion;
diff --git a/src/java/org/apache/cassandra/tcm/log/LocalLog.java 
b/src/java/org/apache/cassandra/tcm/log/LocalLog.java
index b3069eb029..9e283b3244 100644
--- a/src/java/org/apache/cassandra/tcm/log/LocalLog.java
+++ b/src/java/org/apache/cassandra/tcm/log/LocalLog.java
@@ -32,6 +32,7 @@ import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
@@ -277,22 +278,24 @@ public abstract class LocalLog implements Closeable
     @VisibleForTesting
     public void unsafeBootstrapForTesting(InetAddressAndPort addr)
     {
-        bootstrap(addr, "");
+        bootstrap(addr, "", (p) -> {});
     }
 
     /**
-     *
-     * @param addr
-     * @param datacenter
+     * Set up the initial state of the local ClusterMetadata, enacting the 
PreInitialize transform which sets up the
+     * distributed metadata log keyspace, its replication, and data placements.
+     * @param addr Address of the first CMS member, this should be the local 
node address
+     * @param datacenter DC of the first CMS member
      */
-    public void bootstrap(InetAddressAndPort addr, String datacenter)
+    public void bootstrap(InetAddressAndPort addr, String datacenter, 
Consumer<PreInitialize> postBootstrap)
     {
         ClusterMetadata metadata = metadata();
         assert metadata.epoch.isBefore(FIRST) : String.format("Metadata epoch 
%s should be before first", metadata.epoch);
-        Transformation transform = PreInitialize.withFirstCMS(addr, 
datacenter);
+        PreInitialize transform = PreInitialize.withFirstCMS(addr, datacenter);
         append(new Entry(Entry.Id.NONE, FIRST, transform));
         metadata = waitForHighestConsecutive();
         assert metadata.epoch.is(Epoch.FIRST) : String.format("Epoch: %s. CMS: 
%s", metadata.epoch, metadata.fullCMSMembers());
+        postBootstrap.accept(transform);
     }
 
     public LogStorage storage()
@@ -475,8 +478,15 @@ public abstract class LocalLog implements Closeable
 
             ClusterMetadata prev = committed.get();
             // ForceSnapshot + Bootstrap entries can "jump" epoch
-            boolean isPreInit = pendingEntry.transform.kind() == 
Transformation.Kind.PRE_INITIALIZE_CMS;
-            boolean isSnapshot = pendingEntry.transform.kind() == 
Transformation.Kind.FORCE_SNAPSHOT;
+            Transformation.Kind kind = pendingEntry.transform.kind();
+            boolean isPreInit = kind == Transformation.Kind.PRE_INITIALIZE_CMS;
+            boolean isInit = kind == Transformation.Kind.INITIALIZE_CMS;
+            boolean isSnapshot = kind == Transformation.Kind.FORCE_SNAPSHOT ;
+            // only a PRE_INITIALIZE_CMS or a snapshot is allowed to skip over 
gaps.
+            // Note: INITIALIZE_CMS is not allowed to do this as during 
upgrades it can allow us to jump over the
+            // PRE_INITIALIZE_CMS entry if the INITIALIZE_CMS is received 
first. This then creates a gap at Epoch.FIRST
+            // which can never be resolved. In turn, that makes it impossible 
to build a LogState for replay purposes
+            // with a correct and consecutive set of entries if the node is 
bounced before applying a later snapshot.
             if (pendingEntry.epoch.isDirectlyAfter(prev.epoch)
                 || ((isPreInit || isSnapshot) && 
pendingEntry.epoch.isAfter(prev.epoch)))
             {
@@ -512,7 +522,7 @@ public abstract class LocalLog implements Closeable
                     if (replayComplete.get() && pendingEntry.transform.kind() 
!= Transformation.Kind.FORCE_SNAPSHOT)
                         storage.append(pendingEntry.maybeUnwrapExecuted());
 
-                    notifyPreCommit(prev, next, isSnapshot);
+                    notifyPreCommit(prev, next, isSnapshot || isInit);
 
                     if (committed.compareAndSet(prev, next))
                     {
@@ -528,7 +538,7 @@ public abstract class LocalLog implements Closeable
                                                                       
next.epoch, prev.epoch, metadata().epoch));
                     }
 
-                    notifyPostCommit(prev, next, isSnapshot);
+                    notifyPostCommit(prev, next, isSnapshot || isInit);
                 }
                 catch (StopProcessingException t)
                 {
@@ -917,7 +927,7 @@ public abstract class LocalLog implements Closeable
     private LogListener snapshotListener()
     {
         return (entry, metadata) -> {
-            if (ClusterMetadataService.state() != 
ClusterMetadataService.State.LOCAL)
+            if (entry.epoch.isEqualOrBefore(Epoch.FIRST) || 
ClusterMetadataService.state() != ClusterMetadataService.State.LOCAL)
                 return;
 
             if ((entry.epoch.getEpoch() % 
DatabaseDescriptor.getMetadataSnapshotFrequency()) == 0)
diff --git a/src/java/org/apache/cassandra/tcm/membership/Directory.java 
b/src/java/org/apache/cassandra/tcm/membership/Directory.java
index 55a5e7ac96..f86481f141 100644
--- a/src/java/org/apache/cassandra/tcm/membership/Directory.java
+++ b/src/java/org/apache/cassandra/tcm/membership/Directory.java
@@ -42,6 +42,7 @@ import accord.utils.Invariants;
 import accord.utils.btree.BTreeSet;
 
 import org.apache.cassandra.db.TypeSizes;
+import org.apache.cassandra.dht.Token;
 import org.apache.cassandra.io.util.DataInputPlus;
 import org.apache.cassandra.io.util.DataOutputPlus;
 import org.apache.cassandra.locator.InetAddressAndPort;
@@ -50,6 +51,7 @@ import org.apache.cassandra.tcm.Epoch;
 import org.apache.cassandra.tcm.MetadataValue;
 import org.apache.cassandra.tcm.serialization.MetadataSerializer;
 import org.apache.cassandra.tcm.serialization.Version;
+import org.apache.cassandra.utils.SortedBiMultiValMap;
 import org.apache.cassandra.utils.UUIDSerializer;
 import org.apache.cassandra.utils.btree.BTreeBiMap;
 import org.apache.cassandra.utils.btree.BTreeMap;
@@ -524,6 +526,30 @@ public class Directory implements MetadataValue<Directory>
                     .collect(Collectors.joining("\n"));
     }
 
+    /**
+     * nodeId(address): {state, tokens, ser ver, cass ver, dc:rack}
+     */
+    public String conciseToString(SortedBiMultiValMap<Token, NodeId> tokenMap)
+    {
+        return peers.entrySet().stream().map((peerEntry) -> {
+            StringBuilder sb = new StringBuilder();
+            NodeId nodeId = peerEntry.getKey();
+            
sb.append(nodeId.id()).append('(').append(peerEntry.getValue()).append("): ");
+            sb.append('{');
+            sb.append(states.get(nodeId).ordinal());
+            if (tokenMap != null)
+                sb.append(',').append(tokenMap.inverse().get(nodeId));
+            NodeVersion version = versions.get(nodeId);
+            if (version != null)
+                
sb.append(',').append(version.serializationVersion).append(',').append(version.cassandraVersion);
+            Location loc = locations.get(nodeId);
+            if (loc != null)
+                
sb.append(',').append(loc.datacenter).append(':').append(loc.rack);
+            sb.append('}');
+            return sb.toString();
+        }).collect(Collectors.joining(","));
+    }
+
     private static class Node
     {
         public static final Serializer serializer = new Serializer();
diff --git 
a/src/java/org/apache/cassandra/tcm/migration/CMSInitializationException.java 
b/src/java/org/apache/cassandra/tcm/migration/CMSInitializationException.java
new file mode 100644
index 0000000000..c0a1629d78
--- /dev/null
+++ 
b/src/java/org/apache/cassandra/tcm/migration/CMSInitializationException.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.tcm.migration;
+
+public class CMSInitializationException extends RuntimeException
+{
+    public CMSInitializationException()
+    {
+        super("CMS initialization failed, see logs for details");
+    }
+}
diff --git 
a/src/java/org/apache/cassandra/tcm/migration/CMSInitializationRequest.java 
b/src/java/org/apache/cassandra/tcm/migration/CMSInitializationRequest.java
index 599bfca0da..a76d35ca70 100644
--- a/src/java/org/apache/cassandra/tcm/migration/CMSInitializationRequest.java
+++ b/src/java/org/apache/cassandra/tcm/migration/CMSInitializationRequest.java
@@ -68,6 +68,16 @@ public class CMSInitializationRequest
         this.schemaVersion = schemaVersion;
     }
 
+    @Override
+    public String toString()
+    {
+        return "CMSInitializationRequest{" +
+               "initiator=" + initiator +
+               ", directory=" + directory.conciseToString(tokenMap.asMap()) +
+               ", schemaVersion=" + schemaVersion +
+               '}';
+    }
+
     public static class Serializer implements 
IVersionedSerializer<CMSInitializationRequest>
     {
         private final Version serializationVersion;
diff --git a/src/java/org/apache/cassandra/tcm/migration/Election.java 
b/src/java/org/apache/cassandra/tcm/migration/Election.java
index 34ce077583..906b647488 100644
--- a/src/java/org/apache/cassandra/tcm/migration/Election.java
+++ b/src/java/org/apache/cassandra/tcm/migration/Election.java
@@ -33,6 +33,8 @@ import com.google.common.collect.Sets;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import org.apache.cassandra.db.Keyspace;
+import org.apache.cassandra.db.Mutation;
 import org.apache.cassandra.db.SystemKeyspace;
 import org.apache.cassandra.locator.InetAddressAndPort;
 import org.apache.cassandra.net.IVerbHandler;
@@ -41,8 +43,11 @@ import org.apache.cassandra.net.MessageDelivery;
 import org.apache.cassandra.net.MessagingService;
 import org.apache.cassandra.net.Verb;
 import org.apache.cassandra.schema.DistributedMetadataLogKeyspace;
+import org.apache.cassandra.schema.Keyspaces;
+import org.apache.cassandra.schema.SchemaConstants;
 import org.apache.cassandra.schema.SchemaKeyspace;
 import org.apache.cassandra.tcm.ClusterMetadata;
+import org.apache.cassandra.tcm.ClusterMetadataService;
 import org.apache.cassandra.tcm.Epoch;
 import org.apache.cassandra.tcm.Startup;
 import org.apache.cassandra.tcm.membership.Directory;
@@ -87,6 +92,8 @@ public class Election
 
     public void nominateSelf(Set<InetAddressAndPort> candidates, 
Set<InetAddressAndPort> ignoredEndpoints, ClusterMetadata metadata, boolean 
verifyAllPeersMetadata)
     {
+        // Note: this is probably identical to the supplied metadata, but not 
guaranteed to be
+        ClusterMetadata priorState = ClusterMetadata.current();
         Set<InetAddressAndPort> sendTo = new HashSet<>(candidates);
         sendTo.removeAll(ignoredEndpoints);
         sendTo.remove(FBUtilities.getBroadcastAddressAndPort());
@@ -102,6 +109,19 @@ public class Election
         {
             logger.error("Got error nominating self", e);
             abort(initializationRequest.initiator, sendTo);
+            ClusterMetadata currentState = ClusterMetadata.current();
+
+            // Clean up the system_cluster_metadata keyspace which may have 
been created by PRE_INITIALIZE_CMS
+            Keyspace metaKeyspace = 
currentState.schema.getKeyspace(SchemaConstants.METADATA_KEYSPACE_NAME);
+            if (metaKeyspace != null)
+                metaKeyspace.unload(true);
+
+            // Remove entries from system_schema tables
+            Keyspaces.KeyspacesDiff diff = 
Keyspaces.diff(currentState.schema.getKeyspaces(), 
priorState.schema.getKeyspaces());
+            Collection<Mutation> mutations = 
SchemaKeyspace.convertSchemaDiffToMutations(diff, 
FBUtilities.timestampMicros());
+            SchemaKeyspace.applyChanges(mutations);
+
+            
ClusterMetadataService.instance().log().unsafeSetCommittedFromGossip(priorState);
             throw e;
         }
     }
diff --git a/src/java/org/apache/cassandra/tcm/ownership/DataPlacements.java 
b/src/java/org/apache/cassandra/tcm/ownership/DataPlacements.java
index a4dc95eef4..d813e8c287 100644
--- a/src/java/org/apache/cassandra/tcm/ownership/DataPlacements.java
+++ b/src/java/org/apache/cassandra/tcm/ownership/DataPlacements.java
@@ -24,6 +24,7 @@ import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
 import java.util.function.BiConsumer;
+import java.util.stream.Collectors;
 
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
@@ -219,6 +220,11 @@ public class DataPlacements extends 
ReplicationMap<DataPlacement> implements Met
         return new Builder(new HashMap<>(this.asMap()));
     }
 
+    public String conciseToString()
+    {
+        return 
keys().stream().map(ReplicationParams::toString).collect(Collectors.joining(","));
+    }
+
     public static class Builder
     {
         private final Map<ReplicationParams, DataPlacement> map;
diff --git 
a/src/java/org/apache/cassandra/tcm/transformations/cms/Initialize.java 
b/src/java/org/apache/cassandra/tcm/transformations/cms/Initialize.java
index 11c2ed4d31..9fabef54d6 100644
--- a/src/java/org/apache/cassandra/tcm/transformations/cms/Initialize.java
+++ b/src/java/org/apache/cassandra/tcm/transformations/cms/Initialize.java
@@ -23,12 +23,12 @@ import java.io.IOException;
 import org.apache.cassandra.auth.AuthKeyspace;
 import org.apache.cassandra.io.util.DataInputPlus;
 import org.apache.cassandra.io.util.DataOutputPlus;
-import org.apache.cassandra.locator.MetaStrategy;
 import org.apache.cassandra.schema.DistributedSchema;
 import org.apache.cassandra.schema.Keyspaces;
 import org.apache.cassandra.schema.SystemDistributedKeyspace;
 import org.apache.cassandra.tcm.ClusterMetadata;
 import org.apache.cassandra.tcm.Transformation;
+import org.apache.cassandra.tcm.sequences.LockedRanges;
 import org.apache.cassandra.tcm.serialization.AsymmetricMetadataSerializer;
 import org.apache.cassandra.tcm.serialization.Version;
 import org.apache.cassandra.tcm.transformations.ForceSnapshot;
@@ -75,7 +75,7 @@ public class Initialize extends ForceSnapshot
                                                                 ? 
setUpDistributedSystemKeyspaces(next)
                                                                 : 
next.schema.getKeyspaces());
         ClusterMetadata.Transformer transformer = 
next.transformer().with(initialSchema);
-        return Transformation.success(transformer, 
MetaStrategy.affectedRanges(prev));
+        return Transformation.success(transformer, 
LockedRanges.AffectedRanges.EMPTY);
     }
 
     public Keyspaces setUpDistributedSystemKeyspaces(ClusterMetadata next)
diff --git 
a/src/java/org/apache/cassandra/tcm/transformations/cms/PreInitialize.java 
b/src/java/org/apache/cassandra/tcm/transformations/cms/PreInitialize.java
index e07d1f1a29..7286743f91 100644
--- a/src/java/org/apache/cassandra/tcm/transformations/cms/PreInitialize.java
+++ b/src/java/org/apache/cassandra/tcm/transformations/cms/PreInitialize.java
@@ -52,11 +52,6 @@ public class PreInitialize implements Transformation
         this.datacenter = datacenter;
     }
 
-    public static PreInitialize forTesting()
-    {
-        return new PreInitialize(null, null);
-    }
-
     public static PreInitialize blank()
     {
         return new PreInitialize(null, null);
@@ -78,23 +73,17 @@ public class PreInitialize implements Transformation
 
         ClusterMetadata.Transformer transformer = metadata.transformer();
 
+        // This null check is a leftover from previous implementations. In 
earlier versions the address and datacenter
+        // were not be included in the serialized form of this transform and 
so were not written to the local or
+        // distributed logs nor included in the log entries sent over the wire 
between peers.
+        // The null check remains to handle log entries written in that legacy 
format. The log entry which immediately
+        // follows PRE_INITIALIZE_CMS must contain an INITIALIZE_CMS 
transform, which will necessarily include the
+        // distributed metadata keyspace definition with the replication 
settings bootstrapped by PRE_INITIALIZE. This
+        // full ClusterMetadata becomes the starting point upon which further 
log entries are applied. The ultimate
+        // effect of this sequence is that once INITIALIZE_CMS has been 
committed to the log, the actual content of
+        // PRE_INITIALIZE_CMS becomes irrelevant.
         if (addr != null)
         {
-            // If addr != null, then this is being executed on the peer which 
is actually initializing the log
-            // for the very first time.
-
-            // addr and datacenter are only used to bootstrap the replication 
of the distributed metatada
-            // keyspace on the first CMS node. They are never serialized into 
the distributed metadata log or
-            // passed to any other peer.
-            //
-            // PRE_INITIALIZE_CMS @ Epoch.FIRST, must be followed in the log 
by INITIALIZE_CMS @ (Epoch.FIRST + 1).
-            // The serialization of INITIALIZE_CMS includes the full 
ClusterMetadata at that point, which is
-            // obviously minimal, but will necessarily include the distributed 
metadata keyspace definition with
-            // the replication settings bootstrapped by PRE_INITIALIZE. This 
full ClusterMetadata becomes the
-            // starting point upon which further log entries are applied. So 
this means that once INITIALIZE_CMS
-            // has been committed to the log, the actual content of 
PRE_INITIALIZE_CMS is irrelevant, even on
-            // the first CMS node if it happens to replay it from its local 
storage after a restart.
-
             DataPlacement.Builder dataPlacementBuilder = 
DataPlacement.builder();
             Replica replica = new Replica(addr,
                                           
MetaStrategy.partitioner.getMinimumToken(),
@@ -107,11 +96,11 @@ public class PreInitialize implements Transformation
                                                                        
dataPlacementBuilder.build()).build();
 
             transformer.with(initialPlacement);
-            // re-initialise the schema distributed metadata keyspace so it 
gets the
-            // correct replication settings based on the DC of the initial CMS 
node
+            // create the distributed metadata keyspace in schema with 
replication settings based on the DC of the
+            // initial CMS node
             Keyspaces updated = metadata.schema.getKeyspaces()
                                                
.withAddedOrReplaced(DistributedMetadataLogKeyspace.initialMetadata(datacenter));
-            transformer.with(new DistributedSchema(updated));
+            transformer.with(new DistributedSchema(updated, Epoch.FIRST));
         }
 
         ClusterMetadata.Transformer.Transformed transformed = 
transformer.build();
@@ -121,9 +110,13 @@ public class PreInitialize implements Transformation
         return new Success(metadata, LockedRanges.AffectedRanges.EMPTY, 
transformed.modifiedKeys);
     }
 
+    @Override
     public String toString()
     {
-        return "PreInitialize";
+        return "PreInitialize{" +
+               "addr=" + addr +
+               ", datacenter='" + datacenter + '\'' +
+               '}';
     }
 
     public static class Serializer implements 
AsymmetricMetadataSerializer<Transformation, PreInitialize>
diff --git 
a/test/distributed/org/apache/cassandra/distributed/upgrade/ClusterMetadataSingleNodeUpgradeTest.java
 
b/test/distributed/org/apache/cassandra/distributed/upgrade/ClusterMetadataSingleNodeUpgradeTest.java
index 24cf020a73..3d6c3cfd02 100644
--- 
a/test/distributed/org/apache/cassandra/distributed/upgrade/ClusterMetadataSingleNodeUpgradeTest.java
+++ 
b/test/distributed/org/apache/cassandra/distributed/upgrade/ClusterMetadataSingleNodeUpgradeTest.java
@@ -26,6 +26,7 @@ import 
org.apache.cassandra.distributed.api.IInvokableInstance;
 import org.apache.cassandra.service.StorageService;
 
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 /**
  * @see org.apache.cassandra.tools.nodetool.CMSAdmin.InitializeCMS
@@ -46,8 +47,17 @@ public class ClusterMetadataSingleNodeUpgradeTest extends 
UpgradeTestBase
             cluster.schemaChange("CREATE TABLE " + KEYSPACE + ".tbl (pk int, 
ck int, v int, PRIMARY KEY (pk, ck))");
         })
         .runAfterClusterUpgrade((cluster) -> {
-            assertTrue(((IInvokableInstance)cluster.get(1)).callOnInstance(() 
-> 
StorageService.instance.getRangeToAddressMap("system_cluster_metadata").isEmpty()));
+            try
+            {
+                ((IInvokableInstance) cluster.get(1)).callOnInstance(() -> 
StorageService.instance.getRangeToAddressMap("system_cluster_metadata").isEmpty());
+                fail();
+            }
+            catch (Exception e)
+            {
+                assertTrue(e.getMessage().contains("Unknown keyspace 
system_cluster_metadata"));
+            }
             cluster.get(1).nodetoolResult("cms", 
"initialize").asserts().success();
+            ((IInvokableInstance) cluster.get(1)).callOnInstance(() -> 
StorageService.instance.getRangeToAddressMap("system_cluster_metadata").isEmpty());
             // make sure we can execute transformations:
             cluster.schemaChange(withKeyspace("ALTER TABLE %s.tbl with comment 
= 'hello123'"));
         }).run();
diff --git 
a/test/distributed/org/apache/cassandra/distributed/upgrade/ClusterMetadataSingleNodeUpgradeTest.java
 
b/test/distributed/org/apache/cassandra/distributed/upgrade/ClusterMetadataUpgradeDC2InitializeTest.java
similarity index 51%
copy from 
test/distributed/org/apache/cassandra/distributed/upgrade/ClusterMetadataSingleNodeUpgradeTest.java
copy to 
test/distributed/org/apache/cassandra/distributed/upgrade/ClusterMetadataUpgradeDC2InitializeTest.java
index 24cf020a73..48435ef789 100644
--- 
a/test/distributed/org/apache/cassandra/distributed/upgrade/ClusterMetadataSingleNodeUpgradeTest.java
+++ 
b/test/distributed/org/apache/cassandra/distributed/upgrade/ClusterMetadataUpgradeDC2InitializeTest.java
@@ -18,38 +18,47 @@
 
 package org.apache.cassandra.distributed.upgrade;
 
+import com.google.common.collect.ImmutableMap;
+
 import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import org.apache.cassandra.distributed.Constants;
 import org.apache.cassandra.distributed.api.Feature;
 import org.apache.cassandra.distributed.api.IInvokableInstance;
-import org.apache.cassandra.service.StorageService;
+import org.apache.cassandra.distributed.shared.ClusterUtils;
+import org.apache.cassandra.distributed.shared.NetworkTopology;
 
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
 
-/**
- * @see org.apache.cassandra.tools.nodetool.CMSAdmin.InitializeCMS
- */
-public class ClusterMetadataSingleNodeUpgradeTest extends UpgradeTestBase
+public class ClusterMetadataUpgradeDC2InitializeTest extends UpgradeTestBase
 {
+    public static final Logger logger = 
LoggerFactory.getLogger(ClusterMetadataUpgradeDC2InitializeTest.class);
     @Test
-    public void testSingleNodeUpgrade() throws Throwable
+    public void testCMSInitializeOnDC2NodeAfterUpgrade() throws Throwable
     {
         new TestCase()
-        .nodes(1)
-        .nodesToUpgrade(1)
+        .nodes(2)
+        .nodesToUpgrade(1, 2)
+        .withNodeIdTopology(ImmutableMap.of(1, 
NetworkTopology.dcAndRack("dc1", "rack1"),
+                                            2, 
NetworkTopology.dcAndRack("dc2", "rack2")))
         .withConfig((cfg) -> cfg.with(Feature.NETWORK, Feature.GOSSIP)
                                 .set(Constants.KEY_DTEST_FULL_STARTUP, true))
-        .upgradesToCurrentFrom(v50)
+        .upgradesToCurrentFrom(v41)
         .setup((cluster) -> {
-            cluster.schemaChange(withKeyspace("ALTER KEYSPACE %s WITH 
replication = {'class': 'SimpleStrategy', 'replication_factor':1}"));
-            cluster.schemaChange("CREATE TABLE " + KEYSPACE + ".tbl (pk int, 
ck int, v int, PRIMARY KEY (pk, ck))");
+        })
+        .runBeforeClusterUpgrade(cluster -> {
+            cluster.forEach(node -> {
+                node.flush("system");
+            });
         })
         .runAfterClusterUpgrade((cluster) -> {
-            assertTrue(((IInvokableInstance)cluster.get(1)).callOnInstance(() 
-> 
StorageService.instance.getRangeToAddressMap("system_cluster_metadata").isEmpty()));
-            cluster.get(1).nodetoolResult("cms", 
"initialize").asserts().success();
-            // make sure we can execute transformations:
-            cluster.schemaChange(withKeyspace("ALTER TABLE %s.tbl with comment 
= 'hello123'"));
+            // Run cms initialize on the node in dc2 (node 2) instead of dc1.
+            cluster.get(2).nodetoolResult("cms", 
"initialize").asserts().success();
+
+            cluster.forEach(i -> assertFalse("node " + i.config().num() + " is 
still in MIGRATING STATE",
+                                             
ClusterUtils.isMigrating((IInvokableInstance) i)));
         }).run();
     }
 }
diff --git 
a/test/distributed/org/apache/cassandra/distributed/upgrade/ClusterMetadataUpgradeHarryTest.java
 
b/test/distributed/org/apache/cassandra/distributed/upgrade/ClusterMetadataUpgradeHarryTest.java
index 64810654d5..81df43ca8e 100644
--- 
a/test/distributed/org/apache/cassandra/distributed/upgrade/ClusterMetadataUpgradeHarryTest.java
+++ 
b/test/distributed/org/apache/cassandra/distributed/upgrade/ClusterMetadataUpgradeHarryTest.java
@@ -24,6 +24,7 @@ import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.atomic.AtomicReference;
 
+import org.junit.Ignore;
 import org.junit.Test;
 
 import org.apache.cassandra.concurrent.Interruptible;
@@ -55,6 +56,7 @@ import static org.junit.Assert.assertEquals;
 public class ClusterMetadataUpgradeHarryTest extends UpgradeTestBase
 {
     @Test
+    @Ignore("CASSANDRA-21473")
     public void simpleUpgradeTest()
     {
         AtomicReference<Interruptible> executor = new AtomicReference<>();
diff --git 
a/test/distributed/org/apache/cassandra/distributed/upgrade/ClusterMetadataUpgradeInconsistentPeersV2Test.java
 
b/test/distributed/org/apache/cassandra/distributed/upgrade/ClusterMetadataUpgradeInconsistentPeersV2Test.java
new file mode 100644
index 0000000000..8724403ed7
--- /dev/null
+++ 
b/test/distributed/org/apache/cassandra/distributed/upgrade/ClusterMetadataUpgradeInconsistentPeersV2Test.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.distributed.upgrade;
+
+import java.util.Map;
+
+import org.junit.Test;
+
+import org.apache.cassandra.distributed.Constants;
+import org.apache.cassandra.distributed.UpgradeableCluster;
+import org.apache.cassandra.distributed.api.Feature;
+import org.apache.cassandra.distributed.api.IInvokableInstance;
+import org.apache.cassandra.distributed.api.IUpgradeableInstance;
+import org.apache.cassandra.distributed.api.SimpleQueryResult;
+import org.apache.cassandra.distributed.shared.NetworkTopology;
+import org.apache.cassandra.locator.MetaStrategy;
+import org.apache.cassandra.locator.NetworkTopologyStrategy;
+import org.apache.cassandra.schema.ReplicationParams;
+import org.apache.cassandra.tcm.ClusterMetadata;
+
+import static java.lang.String.format;
+import static 
org.apache.cassandra.schema.SchemaConstants.METADATA_KEYSPACE_NAME;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class ClusterMetadataUpgradeInconsistentPeersV2Test extends 
UpgradeTestBase
+{
+    @Test
+    public void upgradeWithInconsistentSystemPeersV2Test() throws Throwable
+    {
+        new TestCase()
+        .nodes(3)
+        .withNodeIdTopology(NetworkTopology.networkTopology(3, (i) -> 
NetworkTopology.dcAndRack("datacenter0" + i % 2, "rack0" + i)))
+        .nodesToUpgrade(1, 2, 3)
+        .withConfig((cfg) -> cfg.with(Feature.NETWORK, Feature.GOSSIP)
+                                .set(Constants.KEY_DTEST_FULL_STARTUP, true))
+        .upgradesToCurrentFrom(v41)
+        .setup((cluster) -> {
+            // insert mismatching entries into system.peers_v2 table
+            for (int i = 1; i <= 3; i++)
+            {
+                cluster.get(i).executeInternal(format("insert into 
system.peers_v2 (peer, peer_port, data_center) " +
+                                                      "values ('10.10.10.10', 
7000, 'a%s')", i));
+                cluster.get(i).flush("system");
+            }
+        })
+        .runAfterClusterUpgrade((cluster) -> {
+            // The system_cluster_metadata keyspace shouldn't be created until 
the CMS is intialized
+            assertNoMetaKeyspace(cluster);
+
+            cluster.get(3).nodetoolResult("cms", 
"initialize").asserts().success();
+
+            // post-initialization, the replication params for 
system_cluster_metadata should be RF 1 in the DC of the
+            // first CMS member - node3 / datacenter01
+            Map<String, String> actualReplication = Map.of("class", 
MetaStrategy.class.getName(), "datacenter01", "1");
+            Map<String, String> fromSystemTable = Map.of("class", 
NetworkTopologyStrategy.class.getName(), "datacenter01", "1");
+            assertReplicationParams(cluster, actualReplication, 
fromSystemTable);
+        }).run();
+    }
+
+    private static void assertNoMetaKeyspace(UpgradeableCluster cluster)
+    {
+        for (IUpgradeableInstance inst : cluster)
+        {
+            IInvokableInstance i = (IInvokableInstance) inst;
+            boolean found = i.callOnInstance(() -> 
ClusterMetadata.current().schema.getKeyspaces().containsKeyspace(METADATA_KEYSPACE_NAME));
+            assertFalse("Metadata keyspace present on node" + i + " when it 
should not be", found);
+
+            SimpleQueryResult res = inst.executeInternalWithResult("select 
replication from system_schema.keyspaces " +
+                                                                   "where 
keyspace_name = ?",
+                                                                   
METADATA_KEYSPACE_NAME);
+            assertFalse(res.hasNext());
+        }
+    }
+
+    private static void assertReplicationParams(UpgradeableCluster cluster,
+                                                Map<String, String> 
expectedActual,
+                                                Map<String, String> 
expectedInSystemTable)
+    {
+        for (IUpgradeableInstance inst : cluster)
+        {
+            IInvokableInstance i = (IInvokableInstance) inst;
+            Map<String, String> rs = i.callOnInstance(() -> {
+                ReplicationParams r = 
ClusterMetadata.current().schema.getKeyspaceMetadata("system_cluster_metadata").params.replication;
+                return r.asMap();
+            });
+            assertEquals(rs, expectedActual);
+
+            SimpleQueryResult res = inst.executeInternalWithResult("select 
replication from system_schema.keyspaces " +
+                                                                   "where 
keyspace_name = ?",
+                                                                   
METADATA_KEYSPACE_NAME);
+            assertTrue(res.hasNext());
+            Map<String, String> replication = res.next().get("replication");
+            assertEquals(replication, expectedInSystemTable);
+        }
+    }
+}
diff --git 
a/test/distributed/org/apache/cassandra/distributed/upgrade/ClusterMetadataUpgradePeersHostIdsTest.java
 
b/test/distributed/org/apache/cassandra/distributed/upgrade/ClusterMetadataUpgradePeersHostIdsTest.java
new file mode 100644
index 0000000000..e7f7a7a054
--- /dev/null
+++ 
b/test/distributed/org/apache/cassandra/distributed/upgrade/ClusterMetadataUpgradePeersHostIdsTest.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.distributed.upgrade;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import org.junit.Test;
+
+import org.apache.cassandra.distributed.Constants;
+import org.apache.cassandra.distributed.api.Feature;
+import org.apache.cassandra.distributed.api.IUpgradeableInstance;
+import org.apache.cassandra.distributed.api.Row;
+import org.apache.cassandra.distributed.api.SimpleQueryResult;
+import org.apache.cassandra.distributed.impl.TestEndpointCache;
+import org.apache.cassandra.locator.InetAddressAndPort;
+import org.apache.cassandra.tcm.membership.NodeId;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+public class ClusterMetadataUpgradePeersHostIdsTest extends UpgradeTestBase
+{
+    @Test
+    public void upgradeHostIdUpdateTest() throws Throwable
+    {
+        Map<InetAddressAndPort, UUID> preUpgradeIDs = new HashMap<>();
+        new TestCase()
+        .nodes(3)
+        .nodesToUpgrade(1, 2, 3)
+        .withConfig((cfg) -> cfg.with(Feature.NETWORK, Feature.GOSSIP)
+                                .set(Constants.KEY_DTEST_FULL_STARTUP, true))
+        .upgradesToCurrentFrom(v41)
+        .setup((cluster) -> {
+            // combine the system.peers_v2 entries from each instance 
pre-upgrade
+            for (int i = 1; i <= 3; i++)
+                
preUpgradeIDs.putAll(getHostIdsFromSystemPeersV2(cluster.get(i)));
+
+            assertEquals(3, preUpgradeIDs.size());
+
+            for (UUID hostId : preUpgradeIDs.values())
+                assertFalse(NodeId.isValidNodeId(hostId));
+        })
+        .runAfterClusterUpgrade((cluster) -> {
+            for (int i = 1; i <= 3; i++)
+            {
+                // system.peers/peers_v2 should still contain the pre-upgrade 
ids
+                IUpgradeableInstance inst = cluster.get(i);
+                Map<InetAddressAndPort, UUID> expected = new 
HashMap<>(preUpgradeIDs);
+                
expected.remove(TestEndpointCache.toCassandraInetAddressAndPort(inst.config().broadcastAddress()));
+                assertEquals(expected, 
getHostIdsFromSystemPeersV2(cluster.get(i)));
+            }
+
+            // initialize the CMS and fetch new ids from the ClusterMetadata 
Directory
+            cluster.get(1).nodetoolResult("cms", 
"initialize").asserts().success();
+            Map<InetAddressAndPort, UUID> postUpgradeIDs = 
getHostIdsFromClusterMetadata(cluster.get(1));
+
+            for (int i = 1; i <= 3; i++)
+            {
+                // assert system.peers/peers_v2 now contain the post-upgrade 
ids
+                IUpgradeableInstance inst = cluster.get(i);
+                Map<InetAddressAndPort, UUID> expected = new 
HashMap<>(postUpgradeIDs);
+                
expected.remove(TestEndpointCache.toCassandraInetAddressAndPort(inst.config().broadcastAddress()));
+                assertEquals(expected, 
getHostIdsFromSystemPeersV2(cluster.get(i)));
+            }
+        }).run();
+    }
+
+    private static Map<InetAddressAndPort, UUID> 
getHostIdsFromSystemPeersV2(IUpgradeableInstance instance)
+    {
+        Map<InetAddressAndPort, UUID> hostIds = new HashMap<>();
+        SimpleQueryResult res = instance.executeInternalWithResult("select 
peer, peer_port, host_id from system.peers_v2");
+        while(res.hasNext())
+        {
+            Row row = res.next();
+            
hostIds.put(InetAddressAndPort.getByAddressOverrideDefaults(row.get(0), 
row.getInteger(1)), row.getUUID(2));
+        }
+        return hostIds;
+    }
+
+    private static Map<InetAddressAndPort, UUID> 
getHostIdsFromClusterMetadata(IUpgradeableInstance instance)
+    {
+        Map<InetAddressAndPort, UUID> hostIds = new HashMap<>();
+        SimpleQueryResult res = instance.executeInternalWithResult("select 
broadcast_address, broadcast_port, host_id from 
system_views.cluster_metadata_directory");
+        while(res.hasNext())
+        {
+            Row row = res.next();
+            
hostIds.put(InetAddressAndPort.getByAddressOverrideDefaults(row.get(0), 
row.getInteger(1)), row.getUUID(2));
+        }
+        return hostIds;
+    }
+}
diff --git 
a/test/distributed/org/apache/cassandra/distributed/upgrade/ClusterMetadataUpgradeTest.java
 
b/test/distributed/org/apache/cassandra/distributed/upgrade/ClusterMetadataUpgradeTest.java
index c7e33b8228..7f590379af 100644
--- 
a/test/distributed/org/apache/cassandra/distributed/upgrade/ClusterMetadataUpgradeTest.java
+++ 
b/test/distributed/org/apache/cassandra/distributed/upgrade/ClusterMetadataUpgradeTest.java
@@ -56,9 +56,9 @@ public class ClusterMetadataUpgradeTest extends 
UpgradeTestBase
         })
         .runAfterClusterUpgrade((cluster) -> {
             Object [][] r = cluster.get(1).executeInternal("select 
keyspace_name from system_schema.keyspaces where 
keyspace_name='system_cluster_metadata'");
-            assertEquals(1, r.length);
+            assertEquals(0, r.length);
             r = cluster.get(1).executeInternal("select table_name from 
system_schema.tables where keyspace_name='system_cluster_metadata' and 
table_name='distributed_metadata_log'");
-            assertEquals(1, r.length);
+            assertEquals(0, r.length);
 
             
cluster.get(1).nodetoolResult("cms","initialize").asserts().success();
             cluster.forEach(i ->
@@ -67,6 +67,12 @@ public class ClusterMetadataUpgradeTest extends 
UpgradeTestBase
                 assertFalse("node " + i.config().num() + " is still in 
MIGRATING STATE",
                             ClusterUtils.isMigrating((IInvokableInstance) i));
             });
+
+            r = cluster.get(1).executeInternal("select keyspace_name from 
system_schema.keyspaces where keyspace_name='system_cluster_metadata'");
+            assertEquals(1, r.length);
+            r = cluster.get(1).executeInternal("select table_name from 
system_schema.tables where keyspace_name='system_cluster_metadata' and 
table_name='distributed_metadata_log'");
+            assertEquals(1, r.length);
+
             cluster.get(2).nodetoolResult("cms", "reconfigure", 
"3").asserts().success();
             cluster.schemaChange(withKeyspace("create table %s.xyz (id int 
primary key)"));
             cluster.forEach(i -> {
diff --git 
a/test/distributed/org/apache/cassandra/distributed/upgrade/ClusterMetadataUpgradeUnexpectedFailureTest.java
 
b/test/distributed/org/apache/cassandra/distributed/upgrade/ClusterMetadataUpgradeUnexpectedFailureTest.java
index 646d4b8222..d7bb080ca9 100644
--- 
a/test/distributed/org/apache/cassandra/distributed/upgrade/ClusterMetadataUpgradeUnexpectedFailureTest.java
+++ 
b/test/distributed/org/apache/cassandra/distributed/upgrade/ClusterMetadataUpgradeUnexpectedFailureTest.java
@@ -18,7 +18,6 @@
 
 package org.apache.cassandra.distributed.upgrade;
 
-import java.util.Set;
 import java.util.concurrent.Callable;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Consumer;
@@ -30,39 +29,124 @@ import 
net.bytebuddy.implementation.bind.annotation.SuperCall;
 
 import org.junit.Test;
 
+import org.apache.cassandra.db.SystemKeyspace;
 import org.apache.cassandra.distributed.Constants;
 import org.apache.cassandra.distributed.UpgradeableCluster;
+import org.apache.cassandra.distributed.api.ConsistencyLevel;
 import org.apache.cassandra.distributed.api.Feature;
+import org.apache.cassandra.distributed.api.IInvokableInstance;
+import org.apache.cassandra.distributed.api.IUpgradeableInstance;
 import org.apache.cassandra.locator.InetAddressAndPort;
-import org.apache.cassandra.tcm.migration.Election;
+import org.apache.cassandra.tcm.ClusterMetadata;
+import org.apache.cassandra.tcm.ClusterMetadataService;
+import org.apache.cassandra.tcm.log.LocalLog;
+import org.apache.cassandra.tcm.migration.CMSInitializationException;
+import org.apache.cassandra.utils.Shared;
 
 import static net.bytebuddy.matcher.ElementMatchers.named;
+import static 
org.apache.cassandra.schema.SchemaConstants.METADATA_KEYSPACE_NAME;
+import static org.apache.cassandra.schema.SchemaConstants.SCHEMA_KEYSPACE_NAME;
+import static org.apache.cassandra.schema.SchemaKeyspaceTables.COLUMNS;
+import static org.apache.cassandra.schema.SchemaKeyspaceTables.KEYSPACES;
+import static org.apache.cassandra.schema.SchemaKeyspaceTables.TABLES;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
 
 public class ClusterMetadataUpgradeUnexpectedFailureTest extends 
UpgradeTestBase
 {
     @Test
-    public void upgradeFailsUnexpectedlyAfterInitiation() throws Throwable
+    public void upgradeFailsUnexpectedlyBeforePreInitialize() throws Throwable
+    {
+        testCMSInitializationError(true, false, "Something unexpected went 
wrong");
+    }
+
+    @Test
+    public void upgradeFailsUnexpectedlyBeforeInitialize() throws Throwable
+    {
+        testCMSInitializationError(false, true, "CMS initialization failed, 
see logs for details");
+    }
+
+    private void testCMSInitializationError(boolean failBeforePreInit,
+                                            boolean failAfterPreInit,
+                                            String expectedError) throws 
Throwable
     {
         Consumer<UpgradeableCluster.Builder > builderUpdater = builder -> 
builder.withInstanceInitializer(BBInstaller::installUpgradeVersionBB);
         new TestCase()
-                .nodes(3)
-                .nodesToUpgrade(1, 2, 3)
-                .withConfig((cfg) -> cfg.with(Feature.NETWORK, Feature.GOSSIP)
-                        .set(Constants.KEY_DTEST_FULL_STARTUP, true))
-                .upgradesToCurrentFrom(v41)
-                .withBuilder(builderUpdater)
-                .setup((cluster) -> {
-                    cluster.schemaChange(withKeyspace("ALTER KEYSPACE %s WITH 
replication = {'class': 'SimpleStrategy', 'replication_factor':2}"));
-                    cluster.schemaChange("CREATE TABLE " + KEYSPACE + ".tbl 
(pk int, ck int, v int, PRIMARY KEY (pk, ck))");
-                })
-                .runAfterClusterUpgrade((cluster) -> {
-                    // we injected a BB helper to trigger an unexpected 
failure on the first attempt at initialization.
-                    // i.e. an exception that must be caught as opposed to a 
mismatch in metadata or down peer.
-                    cluster.get(1).nodetoolResult("cms", 
"initialize").asserts().failure().errorContains("Something unexpected went 
wrong");
-                    // handling the failure should have included cleaning up 
any state so that another attempt can be
-                    // made, which this time should succeed.
-                    cluster.get(1).nodetoolResult("cms", 
"initialize").asserts().success();
-                }).run();
+            .nodes(3)
+            .nodesToUpgrade(1, 2, 3)
+            .withConfig((cfg) -> cfg.with(Feature.NETWORK, Feature.GOSSIP)
+                                    .set(Constants.KEY_DTEST_FULL_STARTUP, 
true))
+            .upgradesToCurrentFrom(v41)
+            .withBuilder(builderUpdater)
+            .setup((cluster) -> {
+                BBState.failBeforePreInitialize.set(failBeforePreInit);
+                BBState.failAfterPreInitialize.set(failAfterPreInit);
+                cluster.schemaChange(withKeyspace("ALTER KEYSPACE %s WITH 
replication = {'class': 'SimpleStrategy', 'replication_factor':2}"));
+                cluster.schemaChange("CREATE TABLE " + KEYSPACE + ".tbl (pk 
int, ck int, v int, PRIMARY KEY (pk, ck))");
+                for (int i = 0; i < 10; i++)
+                {
+                    cluster.get(1).coordinator().execute("INSERT into " + 
KEYSPACE + ".tbl (pk, ck, v) VALUES (?, ?, ?)",
+                                                         ConsistencyLevel.ALL,
+                                                         i, i, i);
+                }
+            })
+            .runAfterClusterUpgrade((cluster) -> {
+                String oldHostId = getHostId(cluster.get(1));
+                // we injected a BB helper to trigger an unexpected failure on 
the first attempt at initialization.
+                // i.e. an exception that must be caught as opposed to a 
mismatch in metadata or down peer.
+                cluster.get(1).nodetoolResult("cms", 
"initialize").asserts().failure().errorContains(expectedError);
+                assertEquals(oldHostId, getHostId(cluster.get(1)));
+
+                // handling the failure should have included cleaning up any 
state so that another attempt can be
+                // made, which this time should succeed.
+                for(IUpgradeableInstance inst : cluster)
+                    
inst.nodetoolResult("cms").asserts().success().stdoutContains("Service State: 
GOSSIP");
+                // Basic smoke test
+                for (int i = 0; i < 10; i++)
+                {
+                    Object [][] rows = cluster.get(1)
+                                              .coordinator()
+                                              .execute("SELECT v from " + 
KEYSPACE + ".tbl WHERE pk = ?",
+                                                       ConsistencyLevel.ALL, 
i);
+                    assertEquals(1, rows.length);
+                    assertEquals(i, rows[0][0]);
+                }
+
+                // Make sure that no trace of the metadata keyspace is present 
after the CMS initialization failure
+                assertSchemaTablesContent(cluster, true);
+
+                Object[][] rows = 
cluster.get(1).coordinator().execute("DESCRIBE FULL SCHEMA", 
ConsistencyLevel.NODE_LOCAL);
+                for (Object[] row : rows)
+                    
assertFalse(row[0].toString().equalsIgnoreCase(METADATA_KEYSPACE_NAME));
+
+                assertEquals(oldHostId, getHostId(cluster.get(1)));
+                // A subsequent initialization should succeed
+                cluster.get(1).nodetoolResult("cms", 
"initialize").asserts().success();
+                assertNotEquals(oldHostId, getHostId(cluster.get(1)));
+                assertSchemaTablesContent(cluster, false);
+            }).run();
+    }
+
+    private static void assertSchemaTablesContent(UpgradeableCluster cluster, 
boolean expectEmpty)
+    {
+        for (String schemaTable : new String[] { KEYSPACES, TABLES, COLUMNS })
+        {
+            Object[][] rows = cluster.get(1)
+                                     .coordinator()
+                                     .execute("SELECT * FROM " + 
SCHEMA_KEYSPACE_NAME + "." + schemaTable + " WHERE keyspace_name = ?" ,
+                                              ConsistencyLevel.ALL, 
METADATA_KEYSPACE_NAME);
+            if (expectEmpty)
+                assertEquals(0, rows.length);
+            else
+                assertTrue(rows.length >= 1);
+        }
+    }
+
+    private static String getHostId(IUpgradeableInstance i)
+    {
+        return ((IInvokableInstance)i).callOnInstance(() -> 
SystemKeyspace.getLocalHostId().toString());
     }
 
     public static class BBInstaller
@@ -71,8 +155,15 @@ public class ClusterMetadataUpgradeUnexpectedFailureTest 
extends UpgradeTestBase
         {
             try
             {
-                new ByteBuddy().rebase(Election.class)
-                               .method(named("finish"))
+                // Fail before the LocalLog is initialized with the 
PRE_INITIALIZE_CMS
+                new ByteBuddy().rebase(LocalLog.class)
+                               .method(named("bootstrap"))
+                               
.intercept(MethodDelegation.to(BBInterceptor.class))
+                               .make()
+                               .load(classLoader, 
ClassLoadingStrategy.Default.INJECTION);
+                // Fail after the PRE_INITIALIZE_CMS has been enacted, as the 
INITIALIZE_CMS is being committed
+                new ByteBuddy().rebase(ClusterMetadataService.class)
+                               .method(named("commit"))
                                
.intercept(MethodDelegation.to(BBInterceptor.class))
                                .make()
                                .load(classLoader, 
ClassLoadingStrategy.Default.INJECTION);
@@ -88,14 +179,20 @@ public class ClusterMetadataUpgradeUnexpectedFailureTest 
extends UpgradeTestBase
         }
     }
 
+    @Shared
+    public static class BBState
+    {
+        public static AtomicBoolean failBeforePreInitialize = new 
AtomicBoolean(false);
+        public static AtomicBoolean failAfterPreInitialize = new 
AtomicBoolean(false);
+    }
+
     public static class BBInterceptor
     {
-        private static final AtomicBoolean firstAttempt = new 
AtomicBoolean(true);
 
         @SuppressWarnings("unused")
-        public static void finish(Set<InetAddressAndPort> sendTo, @SuperCall 
Callable<Void> zuper)
+        public static void bootstrap(InetAddressAndPort addr, String 
datacenterSet, @SuperCall Callable<Void> zuper)
         {
-            if (firstAttempt.getAndSet(false))
+            if (BBState.failBeforePreInitialize.getAndSet(false))
                 throw new IllegalStateException("Something unexpected went 
wrong");
 
             try
@@ -107,5 +204,19 @@ public class ClusterMetadataUpgradeUnexpectedFailureTest 
extends UpgradeTestBase
                 throw new RuntimeException(e);
             }
         }
+
+        public static ClusterMetadata commit(@SuperCall 
Callable<ClusterMetadata> zuper)
+        {
+            if (BBState.failAfterPreInitialize.getAndSet(false))
+                throw new CMSInitializationException();
+            try
+            {
+                return zuper.call();
+            }
+            catch (Exception e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
     }
 }
diff --git a/test/unit/org/apache/cassandra/ServerTestUtils.java 
b/test/unit/org/apache/cassandra/ServerTestUtils.java
index ebc5bf1df5..ea6c53003c 100644
--- a/test/unit/org/apache/cassandra/ServerTestUtils.java
+++ b/test/unit/org/apache/cassandra/ServerTestUtils.java
@@ -274,13 +274,7 @@ public final class ServerTestUtils
 
     public static void initCMS()
     {
-        // Effectively disable automatic snapshots using 
AtomicLongBackedProcessor and LocaLLog.Sync interacts
-        // badly with submitting SealPeriod transformations from the log 
listener. In this configuration, SealPeriod
-        // commits performed on NonPeriodicTasks threads end up actually 
performing the transformations as well as
-        // calling the pre and post commit listeners, which is not threadsafe. 
In a non-test setup the processing of
-        // log entries is always done by the dedicated log follower thread.
         DatabaseDescriptor.setMetadataSnapshotFrequency(Integer.MAX_VALUE);
-
         IPartitioner partitioner = DatabaseDescriptor.getPartitioner();
         Location location = DatabaseDescriptor.getLocator().local();
         boolean addListeners = true;
@@ -304,7 +298,7 @@ public final class ServerTestUtils
 
         ClusterMetadataService.setInstance(service);
         log.readyUnchecked();
-        log.bootstrap(FBUtilities.getBroadcastAddressAndPort(), 
location.datacenter);
+        log.bootstrap(FBUtilities.getBroadcastAddressAndPort(), 
location.datacenter, (p) -> {});
         service.commit(new Initialize(ClusterMetadata.current()));
         QueryProcessor.registerStatementInvalidatingListener();
         service.mark();
diff --git a/test/unit/org/apache/cassandra/schema/DistributedSchemaTest.java 
b/test/unit/org/apache/cassandra/schema/DistributedSchemaTest.java
index 4b74f5c38b..f5abb312f0 100644
--- a/test/unit/org/apache/cassandra/schema/DistributedSchemaTest.java
+++ b/test/unit/org/apache/cassandra/schema/DistributedSchemaTest.java
@@ -19,14 +19,12 @@
 package org.apache.cassandra.schema;
 
 import java.util.HashMap;
-import java.util.Set;
 
 import org.junit.BeforeClass;
 import org.junit.Test;
 
 import org.apache.cassandra.ServerTestUtils;
 import org.apache.cassandra.auth.AuthKeyspace;
-import org.apache.cassandra.config.DatabaseDescriptor;
 import org.apache.cassandra.db.marshal.Int32Type;
 import org.apache.cassandra.service.reads.PercentileSpeculativeRetryPolicy;
 import org.apache.cassandra.service.reads.repair.ReadRepairStrategy;
@@ -64,8 +62,7 @@ public class DistributedSchemaTest
                                                       KeyspaceParams.simple(3),
                                                       Tables.of(modifiedTable, 
nonstandard));
 
-        DistributedSchema schema = 
DistributedSchema.fromSystemTables(Keyspaces.of(km),
-                                                                      
Set.of(DatabaseDescriptor.getLocalDataCenter()));
+        DistributedSchema schema = 
DistributedSchema.fromSystemTables(Keyspaces.of(km));
         KeyspaceMetadata merged = 
schema.getKeyspaceMetadata(SchemaConstants.AUTH_KEYSPACE_NAME);
         
assertThat(merged.getTableOrViewNullable(AuthKeyspace.ROLES)).isEqualTo(modifiedTable);
         
assertThat(merged.getTableOrViewNullable("nonstandard")).isEqualTo(nonstandard);
diff --git 
a/test/unit/org/apache/cassandra/tcm/log/DistributedLogStateTest.java 
b/test/unit/org/apache/cassandra/tcm/log/DistributedLogStateTest.java
index 37cee72373..a4c884406c 100644
--- a/test/unit/org/apache/cassandra/tcm/log/DistributedLogStateTest.java
+++ b/test/unit/org/apache/cassandra/tcm/log/DistributedLogStateTest.java
@@ -35,6 +35,7 @@ import org.apache.cassandra.tcm.Epoch;
 import org.apache.cassandra.tcm.MetadataSnapshots;
 import org.apache.cassandra.tcm.transformations.CustomTransformation;
 import org.apache.cassandra.tcm.transformations.TriggerSnapshot;
+import org.apache.cassandra.tcm.transformations.cms.PreInitialize;
 
 import static org.apache.cassandra.cql3.QueryProcessor.executeInternal;
 import static org.apache.cassandra.db.ColumnFamilyStore.FlushReason.UNIT_TESTS;
@@ -76,8 +77,12 @@ public class DistributedLogStateTest extends LogStateTestBase
             }
 
             @Override
-            public void insertRegularEntry()
+            public void insertRegularEntry() throws IOException
             {
+
+                if (currentEpoch == Epoch.FIRST)
+                    
DistributedMetadataLogKeyspace.insertPreInitialize(PreInitialize.blank());
+
                 nextEpoch = currentEpoch.nextEpoch();
                 boolean applied = DistributedMetadataLogKeyspace.tryCommit(new 
Entry.Id(currentEpoch.getEpoch()),
                                                                    
CustomTransformation.make((int) currentEpoch.getEpoch()),
diff --git 
a/test/unit/org/apache/cassandra/tcm/log/LogListenerNotificationTest.java 
b/test/unit/org/apache/cassandra/tcm/log/LogListenerNotificationTest.java
index f37313ac4a..e4105b8e5f 100644
--- a/test/unit/org/apache/cassandra/tcm/log/LogListenerNotificationTest.java
+++ b/test/unit/org/apache/cassandra/tcm/log/LogListenerNotificationTest.java
@@ -101,7 +101,7 @@ public class LogListenerNotificationTest
                                .withLogListener(listener)
                                .createLog();
         log.readyUnchecked();
-        log.append(new Entry(Entry.Id.NONE, Epoch.FIRST, 
PreInitialize.forTesting()));
+        log.append(new Entry(Entry.Id.NONE, Epoch.FIRST, 
PreInitialize.blank()));
         log.append(input);
     }
 
diff --git a/test/unit/org/apache/cassandra/utils/CassandraGenerators.java 
b/test/unit/org/apache/cassandra/utils/CassandraGenerators.java
index e3d4017744..4c0a6758ec 100644
--- a/test/unit/org/apache/cassandra/utils/CassandraGenerators.java
+++ b/test/unit/org/apache/cassandra/utils/CassandraGenerators.java
@@ -1946,7 +1946,7 @@ public final class CassandraGenerators
                 Epoch epoch = epochGen.generate(rnd);
                 IPartitioner partitioner = partitionerGen.generate(rnd);
                 Directory directory = Directory.EMPTY;
-                DistributedSchema schema = 
DistributedSchema.first(directory.knownDatacenters());
+                DistributedSchema schema = DistributedSchema.empty();
                 TokenMap tokenMap = new TokenMap(partitioner);
                 DataPlacements placements = DataPlacements.EMPTY;
                 AccordFastPath accordFastPath = 
accordFastPathGen.generate(rnd);


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to