From 68ed7affe1d2509383175de5d0332e66c443bb05 Mon Sep 17 00:00:00 2001 From: Daniel Jatnieks Date: Fri, 8 May 2026 11:52:57 -0700 Subject: [PATCH 01/19] CNDB-17107 Use SCM HCD_1 as default for CC5 junit tests --- .../apache/cassandra/config/CassandraRelevantProperties.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java b/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java index f36e6501642b..c81b1190e9d6 100644 --- a/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java +++ b/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java @@ -1042,7 +1042,7 @@ public enum CassandraRelevantProperties * * This is a dev/CI only property. Do not use otherwise. */ - TEST_STORAGE_COMPATIBILITY_MODE("cassandra.test.storage_compatibility_mode", StorageCompatibilityMode.NONE.toString()), + TEST_STORAGE_COMPATIBILITY_MODE("cassandra.test.storage_compatibility_mode", StorageCompatibilityMode.HCD_1.toString()), TEST_STRICT_LCS_CHECKS("cassandra.test.strict_lcs_checks"), /** Turns some warnings into exceptions for testing. */ TEST_STRICT_RUNTIME_CHECKS("cassandra.strict.runtime.checks"), From 32083e2d2348aeb6164d3c4551d82b63452f0394 Mon Sep 17 00:00:00 2001 From: Daniel Jatnieks Date: Fri, 8 May 2026 18:12:13 -0700 Subject: [PATCH 02/19] Fix auth tests failing in storage compatibility mode Tests using CIDR permissions and identity-to-role mapping (5.0+ features) were failing in compatibility mode because underlying tables don't exist. Fixed by: - Skipping test classes entirely dedicated to 5.0 features and individual 5.0 test methods in mixed test classes - Adding guards in manager classes to prevent InvalidRequestException and NPE during test setup when tables/statements don't exist --- .../auth/CIDRGroupsMappingManager.java | 24 +++++++++++++++++++ .../auth/CIDRPermissionsManager.java | 13 ++++++++++ .../cassandra/auth/CassandraRoleManager.java | 8 +++++++ .../auth/AllowAllCIDRAuthorizerTest.java | 6 +++++ .../cassandra/auth/GrantAndRevokeTest.java | 20 +++++++++++++++- .../auth/MutualTlsAuthenticatorTest.java | 5 ++++ 6 files changed, 75 insertions(+), 1 deletion(-) diff --git a/src/java/org/apache/cassandra/auth/CIDRGroupsMappingManager.java b/src/java/org/apache/cassandra/auth/CIDRGroupsMappingManager.java index 6d68d5edee02..93987a0a6d86 100644 --- a/src/java/org/apache/cassandra/auth/CIDRGroupsMappingManager.java +++ b/src/java/org/apache/cassandra/auth/CIDRGroupsMappingManager.java @@ -32,10 +32,13 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.CIDR; import org.apache.cassandra.cql3.PageSize; +import org.apache.cassandra.utils.CassandraVersion; import org.apache.cassandra.cql3.QueryOptions; import org.apache.cassandra.cql3.QueryProcessor; import org.apache.cassandra.cql3.UntypedResultSet; @@ -58,6 +61,7 @@ public class CIDRGroupsMappingManager implements CIDRGroupsMappingManagerMBean { + private static final Logger logger = LoggerFactory.getLogger(CIDRGroupsMappingManager.class); public static final String MBEAN_NAME = "org.apache.cassandra.db:type=CIDRGroupsMappingManager"; private SelectStatement getCidrGroupsStatement; @@ -67,6 +71,14 @@ public class CIDRGroupsMappingManager implements CIDRGroupsMappingManagerMBean public void setup() { + // In compatibility mode, cidr_groups table doesn't exist + if (DatabaseDescriptor.getStorageCompatibilityMode().isBefore(CassandraVersion.CASSANDRA_5_0.major)) + { + logger.debug("Skipping CIDR groups statement preparation in compatibility mode {}", + DatabaseDescriptor.getStorageCompatibilityMode()); + return; + } + if (!MBeanWrapper.instance.isRegistered(MBEAN_NAME)) MBeanWrapper.instance.registerMBean(this, MBEAN_NAME); @@ -106,6 +118,10 @@ protected static String getCidrTuplesSetString(List cidrs) public UntypedResultSet getCidrGroupsTableEntries() { + // In compatibility mode, cidr_groups table doesn't exist + if (DatabaseDescriptor.getStorageCompatibilityMode().isBefore(CassandraVersion.CASSANDRA_5_0.major)) + return UntypedResultSet.create(Collections.emptyList()); + String getAllRowsQuery = String.format("SELECT * FROM %s.%s", SchemaConstants.AUTH_KEYSPACE_NAME, AuthKeyspace.CIDR_GROUPS); @@ -115,6 +131,10 @@ public UntypedResultSet getCidrGroupsTableEntries() public Set getAvailableCidrGroups() { + // In compatibility mode, statement is not prepared + if (getCidrGroupsStatement == null) + return Collections.emptySet(); + Set availableCidrGroups = new HashSet<>(); QueryOptions options = QueryOptions.forInternalCalls(CassandraAuthorizer.authReadConsistencyLevel(), @@ -158,6 +178,10 @@ public Set> retrieveCidrsFromRow(UntypedResultSet.Row r public Set getCidrsOfCidrGroupAsStrings(String cidrGroupName) { + // In compatibility mode, statement is not prepared + if (getCidrsForCidrGroupStatement == null) + return Collections.emptySet(); + QueryOptions options = QueryOptions.forInternalCalls(CassandraAuthorizer.authReadConsistencyLevel(), Lists.newArrayList(ByteBufferUtil.bytes(cidrGroupName))); ResultMessage.Rows rows = select(getCidrsForCidrGroupStatement, options); diff --git a/src/java/org/apache/cassandra/auth/CIDRPermissionsManager.java b/src/java/org/apache/cassandra/auth/CIDRPermissionsManager.java index 8476d7b8650c..160462c45f8c 100644 --- a/src/java/org/apache/cassandra/auth/CIDRPermissionsManager.java +++ b/src/java/org/apache/cassandra/auth/CIDRPermissionsManager.java @@ -35,6 +35,7 @@ import org.apache.cassandra.cql3.QueryProcessor; import org.apache.cassandra.cql3.UntypedResultSet; import org.apache.cassandra.cql3.statements.SelectStatement; +import org.apache.cassandra.utils.CassandraVersion; import org.apache.cassandra.db.ConsistencyLevel; import org.apache.cassandra.db.marshal.UTF8Type; import org.apache.cassandra.exceptions.RequestExecutionException; @@ -61,6 +62,14 @@ public class CIDRPermissionsManager implements CIDRPermissionsManagerMBean, Auth public void setup() { + // In compatibility mode, cidr_permissions table doesn't exist + if (DatabaseDescriptor.getStorageCompatibilityMode().isBefore(CassandraVersion.CASSANDRA_5_0.major)) + { + logger.debug("Skipping CIDR permissions statement preparation in compatibility mode {}", + DatabaseDescriptor.getStorageCompatibilityMode()); + return; + } + if (!MBeanWrapper.instance.isRegistered(MBEAN_NAME)) MBeanWrapper.instance.registerMBean(this, MBEAN_NAME); @@ -87,6 +96,10 @@ UntypedResultSet process(String query, ConsistencyLevel cl) throws RequestExecut private Set getAuthorizedCIDRGroups(String name) { + // In compatibility mode, statement is not prepared + if (getCidrPermissionsOfUserStatement == null) + return Collections.emptySet(); + QueryOptions options = QueryOptions.forInternalCalls(CassandraAuthorizer.authReadConsistencyLevel(), Lists.newArrayList(ByteBufferUtil.bytes(name))); diff --git a/src/java/org/apache/cassandra/auth/CassandraRoleManager.java b/src/java/org/apache/cassandra/auth/CassandraRoleManager.java index 9fc0404637a6..9145084f3a92 100644 --- a/src/java/org/apache/cassandra/auth/CassandraRoleManager.java +++ b/src/java/org/apache/cassandra/auth/CassandraRoleManager.java @@ -42,6 +42,7 @@ import org.apache.cassandra.config.CassandraRelevantProperties; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.schema.SchemaConstants; +import org.apache.cassandra.utils.CassandraVersion; import org.apache.cassandra.cql3.*; import org.apache.cassandra.cql3.statements.SelectStatement; import org.apache.cassandra.db.ConsistencyLevel; @@ -258,6 +259,13 @@ protected final void loadRoleStatement() protected void loadIdentityStatement() { + // Only prepare statement if identity_to_role table exists (5.0+) + if (DatabaseDescriptor.getStorageCompatibilityMode().isBefore(CassandraVersion.CASSANDRA_5_0.major)) + { + loadIdentityStatement = null; + return; + } + loadIdentityStatement = (SelectStatement) prepare("SELECT role from %s.%s where identity=?", SchemaConstants.AUTH_KEYSPACE_NAME, AuthKeyspace.IDENTITY_TO_ROLES); diff --git a/test/unit/org/apache/cassandra/auth/AllowAllCIDRAuthorizerTest.java b/test/unit/org/apache/cassandra/auth/AllowAllCIDRAuthorizerTest.java index b5c0e876eecd..a4cc0ba8e7b2 100644 --- a/test/unit/org/apache/cassandra/auth/AllowAllCIDRAuthorizerTest.java +++ b/test/unit/org/apache/cassandra/auth/AllowAllCIDRAuthorizerTest.java @@ -24,11 +24,13 @@ import com.google.common.collect.Iterables; import com.google.common.collect.Sets; import org.junit.Assert; +import org.junit.Assume; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.apache.cassandra.SchemaLoader; +import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.CQLTester; import org.apache.cassandra.cql3.QueryProcessor; import org.apache.cassandra.cql3.UntypedResultSet; @@ -36,6 +38,7 @@ import org.apache.cassandra.db.marshal.UTF8Type; import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.service.ClientState; +import org.apache.cassandra.utils.CassandraVersion; import static org.apache.cassandra.auth.AuthKeyspace.CIDR_GROUPS; import static org.apache.cassandra.auth.AuthKeyspace.CIDR_PERMISSIONS; @@ -60,6 +63,9 @@ private static void setupSuperUser() @BeforeClass public static void defineSchema() throws ConfigurationException { + Assume.assumeFalse("CIDR features require Cassandra 5.0+", + DatabaseDescriptor.getStorageCompatibilityMode().isBefore(CassandraVersion.CASSANDRA_5_0.major)); + SchemaLoader.prepareServer(); SchemaLoader.setupAuth(new AuthTestUtils.LocalCassandraRoleManager(), diff --git a/test/unit/org/apache/cassandra/auth/GrantAndRevokeTest.java b/test/unit/org/apache/cassandra/auth/GrantAndRevokeTest.java index 719c713555e1..46808804e9c5 100644 --- a/test/unit/org/apache/cassandra/auth/GrantAndRevokeTest.java +++ b/test/unit/org/apache/cassandra/auth/GrantAndRevokeTest.java @@ -23,6 +23,7 @@ import com.google.common.collect.Iterables; import org.junit.After; +import org.junit.Assume; import org.junit.BeforeClass; import org.junit.Test; @@ -38,6 +39,7 @@ import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.service.CassandraDaemon; import org.apache.cassandra.transport.ProtocolVersion; +import org.apache.cassandra.utils.CassandraVersion; import static java.lang.String.format; import static org.apache.cassandra.schema.SchemaConstants.LOCAL_SYSTEM_KEYSPACE_NAMES; @@ -68,7 +70,14 @@ public static void setUpClass() public void tearDown() throws Throwable { useSuperUser(); - executeNet("DROP ROLE " + user); + try + { + executeNet("DROP ROLE " + user); + } + catch (Exception e) + { + // Role may not exist if test was skipped - ignore + } } @Test @@ -490,6 +499,9 @@ public void testGrantOnVirtualKeyspaces() throws Throwable @Test public void testAddIdentityPermissions() throws Throwable { + Assume.assumeFalse("ADD IDENTITY requires Cassandra 5.0+", + DatabaseDescriptor.getStorageCompatibilityMode().isBefore(CassandraVersion.CASSANDRA_5_0.major)); + useSuperUser(); executeNet(String.format("CREATE ROLE %s WITH LOGIN = TRUE AND password='%s'", user, pass)); @@ -506,6 +518,9 @@ public void testAddIdentityPermissions() throws Throwable @Test public void testRemoveIdentityPermissionsWithSpecificRolePermission() throws Throwable { + Assume.assumeFalse("DROP IDENTITY requires Cassandra 5.0+", + DatabaseDescriptor.getStorageCompatibilityMode().isBefore(CassandraVersion.CASSANDRA_5_0.major)); + useSuperUser(); String simpleUser = "user_1"; @@ -528,6 +543,9 @@ public void testRemoveIdentityPermissionsWithSpecificRolePermission() throws Thr @Test public void testRemoveIdentityPermissions() { + Assume.assumeFalse("DROP IDENTITY requires Cassandra 5.0+", + DatabaseDescriptor.getStorageCompatibilityMode().isBefore(CassandraVersion.CASSANDRA_5_0.major)); + useSuperUser(); executeNet(String.format("CREATE ROLE %s WITH LOGIN = TRUE AND password='%s'", user, pass)); diff --git a/test/unit/org/apache/cassandra/auth/MutualTlsAuthenticatorTest.java b/test/unit/org/apache/cassandra/auth/MutualTlsAuthenticatorTest.java index 0dc7984f26a8..a513792e982c 100644 --- a/test/unit/org/apache/cassandra/auth/MutualTlsAuthenticatorTest.java +++ b/test/unit/org/apache/cassandra/auth/MutualTlsAuthenticatorTest.java @@ -27,6 +27,7 @@ import java.util.concurrent.TimeoutException; import org.junit.After; +import org.junit.Assume; import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; @@ -41,6 +42,7 @@ import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.schema.SchemaConstants; import org.apache.cassandra.service.StorageService; +import org.apache.cassandra.utils.CassandraVersion; import org.apache.cassandra.utils.MBeanWrapper; import static org.apache.cassandra.auth.AuthTestUtils.getMockInetAddress; @@ -71,6 +73,9 @@ public static Collection versions() @BeforeClass public static void setup() { + Assume.assumeFalse("Mutual TLS authentication with identity mapping requires Cassandra 5.0+", + DatabaseDescriptor.getStorageCompatibilityMode().isBefore(CassandraVersion.CASSANDRA_5_0.major)); + SchemaLoader.loadSchema(); DatabaseDescriptor.daemonInitialization(); StorageService.instance.initServer(0); From 2c8156ff4bedecf28849f2601512177cb1efc408 Mon Sep 17 00:00:00 2001 From: Daniel Jatnieks Date: Mon, 11 May 2026 09:06:23 -0700 Subject: [PATCH 03/19] Fix more tests when using storage compatibility mode --- .../auth/CIDRGroupsMappingManagerTest.java | 6 ++++ ...assandraCIDRAuthorizerEnforceModeTest.java | 5 +++ ...assandraCIDRAuthorizerMonitorModeTest.java | 5 +++ .../statements/AddIdentityStatementTest.java | 5 +++ .../statements/DescribeStatementTest.java | 36 +++++++++++++++---- .../statements/DropIdentityStatementTest.java | 5 +++ .../db/SystemKeyspaceMigrator41Test.java | 4 ++- .../CIDRFilteringMetricsTableTest.java | 5 +++ .../nodetool/CIDRFilteringStatsTest.java | 6 ++++ 9 files changed, 70 insertions(+), 7 deletions(-) diff --git a/test/unit/org/apache/cassandra/auth/CIDRGroupsMappingManagerTest.java b/test/unit/org/apache/cassandra/auth/CIDRGroupsMappingManagerTest.java index a3a899d65dcf..4ae18e373c5c 100644 --- a/test/unit/org/apache/cassandra/auth/CIDRGroupsMappingManagerTest.java +++ b/test/unit/org/apache/cassandra/auth/CIDRGroupsMappingManagerTest.java @@ -26,14 +26,17 @@ import java.util.Set; import com.google.common.collect.ImmutableSet; +import org.junit.Assume; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.apache.cassandra.SchemaLoader; +import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.CIDR; import org.apache.cassandra.cql3.QueryProcessor; import org.apache.cassandra.exceptions.ConfigurationException; +import org.apache.cassandra.utils.CassandraVersion; import static org.apache.cassandra.schema.SchemaConstants.AUTH_KEYSPACE_NAME; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -56,6 +59,9 @@ private static void setupSuperUser() @BeforeClass public static void defineSchema() throws ConfigurationException { + Assume.assumeFalse("CIDR features require Cassandra 5.0+", + DatabaseDescriptor.getStorageCompatibilityMode().isBefore(CassandraVersion.CASSANDRA_5_0.major)); + SchemaLoader.prepareServer(); SchemaLoader.setupAuth(new AuthTestUtils.LocalCassandraRoleManager(), diff --git a/test/unit/org/apache/cassandra/auth/CassandraCIDRAuthorizerEnforceModeTest.java b/test/unit/org/apache/cassandra/auth/CassandraCIDRAuthorizerEnforceModeTest.java index ccd187bb807e..5bf484593dd9 100644 --- a/test/unit/org/apache/cassandra/auth/CassandraCIDRAuthorizerEnforceModeTest.java +++ b/test/unit/org/apache/cassandra/auth/CassandraCIDRAuthorizerEnforceModeTest.java @@ -28,6 +28,7 @@ import com.google.common.collect.Iterables; import com.google.common.collect.Sets; import org.junit.Assert; +import org.junit.Assume; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; @@ -45,6 +46,7 @@ import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.exceptions.UnauthorizedException; import org.apache.cassandra.service.ClientState; +import org.apache.cassandra.utils.CassandraVersion; import org.assertj.core.api.Assertions; import static java.lang.String.format; @@ -72,6 +74,9 @@ private static void setupSuperUser() @BeforeClass public static void defineSchema() throws ConfigurationException { + Assume.assumeFalse("CIDR features require Cassandra 5.0+", + DatabaseDescriptor.getStorageCompatibilityMode().isBefore(CassandraVersion.CASSANDRA_5_0.major)); + SchemaLoader.prepareServer(); SchemaLoader.setupAuth(new AuthTestUtils.LocalCassandraRoleManager(), diff --git a/test/unit/org/apache/cassandra/auth/CassandraCIDRAuthorizerMonitorModeTest.java b/test/unit/org/apache/cassandra/auth/CassandraCIDRAuthorizerMonitorModeTest.java index fe92b2c886cc..459c50c7133a 100644 --- a/test/unit/org/apache/cassandra/auth/CassandraCIDRAuthorizerMonitorModeTest.java +++ b/test/unit/org/apache/cassandra/auth/CassandraCIDRAuthorizerMonitorModeTest.java @@ -23,6 +23,7 @@ import java.util.HashMap; import org.junit.Assert; +import org.junit.Assume; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; @@ -37,6 +38,7 @@ import org.apache.cassandra.db.Keyspace; import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.service.ClientState; +import org.apache.cassandra.utils.CassandraVersion; import static org.apache.cassandra.auth.AuthKeyspace.CIDR_GROUPS; import static org.apache.cassandra.auth.AuthKeyspace.CIDR_PERMISSIONS; @@ -59,6 +61,9 @@ private static void setupSuperUser() @BeforeClass public static void defineSchema() throws ConfigurationException { + Assume.assumeFalse("CIDR features require Cassandra 5.0+", + DatabaseDescriptor.getStorageCompatibilityMode().isBefore(CassandraVersion.CASSANDRA_5_0.major)); + SchemaLoader.prepareServer(); SchemaLoader.setupAuth(new AuthTestUtils.LocalCassandraRoleManager(), diff --git a/test/unit/org/apache/cassandra/cql3/statements/AddIdentityStatementTest.java b/test/unit/org/apache/cassandra/cql3/statements/AddIdentityStatementTest.java index 424ea46ed076..14a9c34fdbb8 100644 --- a/test/unit/org/apache/cassandra/cql3/statements/AddIdentityStatementTest.java +++ b/test/unit/org/apache/cassandra/cql3/statements/AddIdentityStatementTest.java @@ -18,6 +18,7 @@ package org.apache.cassandra.cql3.statements; +import org.junit.Assume; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Rule; @@ -42,6 +43,7 @@ import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.QueryState; import org.apache.cassandra.transport.Dispatcher; +import org.apache.cassandra.utils.CassandraVersion; import static org.apache.cassandra.auth.AuthKeyspace.IDENTITY_TO_ROLES; import static org.apache.cassandra.schema.SchemaConstants.AUTH_KEYSPACE_NAME; @@ -69,6 +71,9 @@ private static void setupPrivilegedUser() @BeforeClass public static void defineSchema() throws ConfigurationException { + Assume.assumeFalse("Mutual TLS authentication with identity mapping requires Cassandra 5.0+", + DatabaseDescriptor.getStorageCompatibilityMode().isBefore(CassandraVersion.CASSANDRA_5_0.major)); + SchemaLoader.prepareServer(); SchemaLoader.setupAuth(new AuthTestUtils.LocalCassandraRoleManager(), new AuthTestUtils.LocalPasswordAuthenticator(), diff --git a/test/unit/org/apache/cassandra/cql3/statements/DescribeStatementTest.java b/test/unit/org/apache/cassandra/cql3/statements/DescribeStatementTest.java index 2a56a8d7211c..023e02ba7871 100644 --- a/test/unit/org/apache/cassandra/cql3/statements/DescribeStatementTest.java +++ b/test/unit/org/apache/cassandra/cql3/statements/DescribeStatementTest.java @@ -1078,20 +1078,32 @@ private static String testTableOutput() private static String tableParametersCql() { + // In compatibility mode (pre-5.0), some properties differ + boolean isCompatibilityMode = DatabaseDescriptor.getStorageCompatibilityMode().isBefore(5); + + String memtableFormat = isCompatibilityMode + ? " AND memtable = {}\n" + : " AND memtable = 'default'\n"; + + // allow_auto_snapshot and incremental_backups are not present in pre-5.0 compatibility mode + String autoSnapshotAndIncrementalBackups = isCompatibilityMode + ? "" + : " AND allow_auto_snapshot = true\n" + + " AND incremental_backups = true\n"; + return "additional_write_policy = '99p'\n" + - " AND allow_auto_snapshot = true\n" + + autoSnapshotAndIncrementalBackups + " AND bloom_filter_fp_chance = 0.01\n" + " AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'}\n" + " AND cdc = false\n" + " AND comment = ''\n" + " AND compaction = " + cqlQuoted(CompactionParams.DEFAULT.asMap()) + "\n" + " AND compression = {'chunk_length_in_kb': '16', 'class': 'org.apache.cassandra.io.compress.LZ4Compressor'}\n" + - " AND memtable = 'default'\n" + + memtableFormat + " AND crc_check_chance = 1.0\n" + " AND default_time_to_live = 0\n" + " AND extensions = {}\n" + " AND gc_grace_seconds = 864000\n" + - " AND incremental_backups = true\n" + " AND max_index_interval = 2048\n" + " AND memtable_flush_period_in_ms = 0\n" + " AND min_index_interval = 128\n" + @@ -1106,19 +1118,31 @@ private static String cqlQuoted(Map map) private static String mvParametersCql() { + // In compatibility mode (pre-5.0), some properties differ + boolean isCompatibilityMode = DatabaseDescriptor.getStorageCompatibilityMode().isBefore(5); + + String memtableFormat = isCompatibilityMode + ? " AND memtable = {}\n" + : " AND memtable = 'default'\n"; + + // allow_auto_snapshot and incremental_backups are not present in pre-5.0 compatibility mode + String autoSnapshotAndIncrementalBackups = isCompatibilityMode + ? "" + : " AND allow_auto_snapshot = true\n" + + " AND incremental_backups = true\n"; + return "additional_write_policy = '99p'\n" + - " AND allow_auto_snapshot = true\n" + + autoSnapshotAndIncrementalBackups + " AND bloom_filter_fp_chance = 0.01\n" + " AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'}\n" + " AND cdc = false\n" + " AND comment = ''\n" + " AND compaction = " + cqlQuoted(CompactionParams.DEFAULT.asMap()) + "\n" + " AND compression = {'chunk_length_in_kb': '16', 'class': 'org.apache.cassandra.io.compress.LZ4Compressor'}\n" + - " AND memtable = 'default'\n" + + memtableFormat + " AND crc_check_chance = 1.0\n" + " AND extensions = {}\n" + " AND gc_grace_seconds = 864000\n" + - " AND incremental_backups = true\n" + " AND max_index_interval = 2048\n" + " AND memtable_flush_period_in_ms = 0\n" + " AND min_index_interval = 128\n" + diff --git a/test/unit/org/apache/cassandra/cql3/statements/DropIdentityStatementTest.java b/test/unit/org/apache/cassandra/cql3/statements/DropIdentityStatementTest.java index 2a96c70c2ea5..506f44bc404b 100644 --- a/test/unit/org/apache/cassandra/cql3/statements/DropIdentityStatementTest.java +++ b/test/unit/org/apache/cassandra/cql3/statements/DropIdentityStatementTest.java @@ -20,6 +20,7 @@ import java.util.Map; +import org.junit.Assume; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Rule; @@ -40,6 +41,7 @@ import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.QueryState; import org.apache.cassandra.transport.Dispatcher; +import org.apache.cassandra.utils.CassandraVersion; import static org.apache.cassandra.auth.AuthKeyspace.IDENTITY_TO_ROLES; import static org.apache.cassandra.cql3.statements.AddIdentityStatementTest.defineSchema; @@ -60,6 +62,9 @@ public class DropIdentityStatementTest @BeforeClass public static void beforeClasss() throws ConfigurationException { + Assume.assumeFalse("Mutual TLS authentication with identity mapping requires Cassandra 5.0+", + DatabaseDescriptor.getStorageCompatibilityMode().isBefore(CassandraVersion.CASSANDRA_5_0.major)); + defineSchema(); } diff --git a/test/unit/org/apache/cassandra/db/SystemKeyspaceMigrator41Test.java b/test/unit/org/apache/cassandra/db/SystemKeyspaceMigrator41Test.java index a804e152ae45..0c00857befa8 100644 --- a/test/unit/org/apache/cassandra/db/SystemKeyspaceMigrator41Test.java +++ b/test/unit/org/apache/cassandra/db/SystemKeyspaceMigrator41Test.java @@ -308,7 +308,9 @@ public void testMigrateCompactionHistory() throws Throwable assertEquals(compactAt, row.getTimestamp("compacted_at")); assertEquals("keyspace", row.getString("keyspace_name")); assertEquals(rowsMerged, row.getMap("rows_merged", Int32Type.instance, LongType.instance)); - assertEquals(ImmutableMap.of(), row.getMap("compaction_properties", UTF8Type.instance, UTF8Type.instance)); + // compaction_properties column only exists in Cassandra 5.0+ + if (!DatabaseDescriptor.getStorageCompatibilityMode().isBefore(CassandraVersion.CASSANDRA_5_0.major)) + assertEquals(ImmutableMap.of(), row.getMap("compaction_properties", UTF8Type.instance, UTF8Type.instance)); } assertEquals(1, rowCount); diff --git a/test/unit/org/apache/cassandra/db/virtual/CIDRFilteringMetricsTableTest.java b/test/unit/org/apache/cassandra/db/virtual/CIDRFilteringMetricsTableTest.java index 889d86213548..3c4985541d68 100644 --- a/test/unit/org/apache/cassandra/db/virtual/CIDRFilteringMetricsTableTest.java +++ b/test/unit/org/apache/cassandra/db/virtual/CIDRFilteringMetricsTableTest.java @@ -28,6 +28,7 @@ import com.google.common.collect.ImmutableList; import org.junit.Assert; +import org.junit.Assume; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; @@ -47,6 +48,7 @@ import org.apache.cassandra.db.Keyspace; import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.metrics.CIDRAuthorizerMetrics; +import org.apache.cassandra.utils.CassandraVersion; import static org.apache.cassandra.auth.AuthKeyspace.CIDR_GROUPS; import static org.apache.cassandra.auth.AuthKeyspace.CIDR_PERMISSIONS; @@ -71,6 +73,9 @@ private static void setupSuperUser() @BeforeClass public static void defineSchema() throws ConfigurationException { + Assume.assumeFalse("CIDR features require Cassandra 5.0+", + DatabaseDescriptor.getStorageCompatibilityMode().isBefore(CassandraVersion.CASSANDRA_5_0.major)); + SchemaLoader.prepareServer(); SchemaLoader.setupAuth(new AuthTestUtils.LocalCassandraRoleManager(), diff --git a/test/unit/org/apache/cassandra/tools/nodetool/CIDRFilteringStatsTest.java b/test/unit/org/apache/cassandra/tools/nodetool/CIDRFilteringStatsTest.java index ec415df09be2..4985d61eae64 100644 --- a/test/unit/org/apache/cassandra/tools/nodetool/CIDRFilteringStatsTest.java +++ b/test/unit/org/apache/cassandra/tools/nodetool/CIDRFilteringStatsTest.java @@ -21,6 +21,7 @@ import java.net.InetSocketAddress; import java.util.Collections; +import org.junit.Assume; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; @@ -28,6 +29,7 @@ import org.apache.cassandra.auth.AuthCacheService; import org.apache.cassandra.auth.AuthTestUtils; import org.apache.cassandra.auth.AuthenticatedUser; +import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.CIDR; import org.apache.cassandra.cql3.CQLTester; import org.apache.cassandra.db.virtual.CIDRFilteringMetricsTable; @@ -35,6 +37,7 @@ import org.apache.cassandra.db.virtual.VirtualKeyspaceRegistry; import org.apache.cassandra.schema.SchemaConstants; import org.apache.cassandra.tools.ToolRunner; +import org.apache.cassandra.utils.CassandraVersion; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertFalse; @@ -45,6 +48,9 @@ public class CIDRFilteringStatsTest extends CQLTester @BeforeClass public static void setup() throws Exception { + Assume.assumeFalse("CIDR features require Cassandra 5.0+", + DatabaseDescriptor.getStorageCompatibilityMode().isBefore(CassandraVersion.CASSANDRA_5_0.major)); + CQLTester.setUpClass(); CQLTester.requireAuthentication(); AuthCacheService.initializeAndRegisterCaches(); From ca2ab52551ffb05904446383ca3f470435723391 Mon Sep 17 00:00:00 2001 From: Daniel Jatnieks Date: Mon, 11 May 2026 14:07:11 -0700 Subject: [PATCH 04/19] Fix more CIDR related tests --- .../apache/cassandra/auth/AuthKeyspace.java | 23 +++++++++++++++---- .../cassandra/schema/SchemaConstants.java | 2 +- .../cql3/SystemKeyspaceTablesNamesTest.java | 4 ++-- .../tools/nodetool/DropCIDRGroupTest.java | 6 +++++ .../tools/nodetool/GetCIDRGroupsOfIPTest.java | 6 +++++ .../InvalidateCIDRPermissionsCacheTest.java | 5 ++++ .../tools/nodetool/ListCIDRGroupTest.java | 6 +++++ .../nodetool/ReloadCIDRGroupsCacheTest.java | 5 ++++ .../tools/nodetool/TableHistogramsTest.java | 2 +- .../tools/nodetool/UpdateCIDRGroupTest.java | 6 +++++ 10 files changed, 57 insertions(+), 8 deletions(-) diff --git a/src/java/org/apache/cassandra/auth/AuthKeyspace.java b/src/java/org/apache/cassandra/auth/AuthKeyspace.java index 33fa1ac6ec3e..fc22fd500671 100644 --- a/src/java/org/apache/cassandra/auth/AuthKeyspace.java +++ b/src/java/org/apache/cassandra/auth/AuthKeyspace.java @@ -62,10 +62,25 @@ private AuthKeyspace() public static final String CIDR_PERMISSIONS = "cidr_permissions"; public static final String CIDR_GROUPS = "cidr_groups"; public static final String IDENTITY_TO_ROLES = "identity_to_role"; - public static final Set TABLE_NAMES = ImmutableSet.of(ROLES, ROLE_MEMBERS, ROLE_PERMISSIONS, - RESOURCE_ROLE_INDEX, NETWORK_PERMISSIONS, - CIDR_PERMISSIONS, CIDR_GROUPS, - IDENTITY_TO_ROLES); + + /** + * Returns the set of table names that should exist in the system_auth keyspace. + * In compatibility mode (pre-5.0), CIDR and identity tables are excluded as they + * are Cassandra 5.0+ features. + * + * @return the set of table names appropriate for the current storage compatibility mode + */ + public static Set tableNames() + { + if (DatabaseDescriptor.getStorageCompatibilityMode().isBefore(CassandraVersion.CASSANDRA_5_0.major)) + return ImmutableSet.of(ROLES, ROLE_MEMBERS, ROLE_PERMISSIONS, + RESOURCE_ROLE_INDEX, NETWORK_PERMISSIONS); + else + return ImmutableSet.of(ROLES, ROLE_MEMBERS, ROLE_PERMISSIONS, + RESOURCE_ROLE_INDEX, NETWORK_PERMISSIONS, + CIDR_PERMISSIONS, CIDR_GROUPS, + IDENTITY_TO_ROLES); + } public static final long SUPERUSER_SETUP_DELAY = SUPERUSER_SETUP_DELAY_MS.getLong(); diff --git a/src/java/org/apache/cassandra/schema/SchemaConstants.java b/src/java/org/apache/cassandra/schema/SchemaConstants.java index f4fd749a0702..57ee02c825f9 100644 --- a/src/java/org/apache/cassandra/schema/SchemaConstants.java +++ b/src/java/org/apache/cassandra/schema/SchemaConstants.java @@ -195,7 +195,7 @@ public static Set getLocalAndReplicatedSystemTableNames() .addAll(SystemKeyspace.TABLE_NAMES) .addAll(SchemaKeyspaceTables.ALL) .addAll(TraceKeyspace.TABLE_NAMES) - .addAll(AuthKeyspace.TABLE_NAMES) + .addAll(AuthKeyspace.tableNames()) .addAll(SystemDistributedKeyspace.TABLE_NAMES) .build(); } diff --git a/test/unit/org/apache/cassandra/cql3/SystemKeyspaceTablesNamesTest.java b/test/unit/org/apache/cassandra/cql3/SystemKeyspaceTablesNamesTest.java index fa7404c87ccd..d35689a0479a 100644 --- a/test/unit/org/apache/cassandra/cql3/SystemKeyspaceTablesNamesTest.java +++ b/test/unit/org/apache/cassandra/cql3/SystemKeyspaceTablesNamesTest.java @@ -76,8 +76,8 @@ public void testSystemTraceKeyspaceTableNames() public void testSystemAuthKeyspaceTableNames() { assertExpectedTablesInKeyspace(SchemaConstants.AUTH_KEYSPACE_NAME, - "AuthKeyspace.TABLE_NAMES", - AuthKeyspace.TABLE_NAMES); + "AuthKeyspace.tableNames()", + AuthKeyspace.tableNames()); } @Test diff --git a/test/unit/org/apache/cassandra/tools/nodetool/DropCIDRGroupTest.java b/test/unit/org/apache/cassandra/tools/nodetool/DropCIDRGroupTest.java index 4ec303068fba..545208a98451 100644 --- a/test/unit/org/apache/cassandra/tools/nodetool/DropCIDRGroupTest.java +++ b/test/unit/org/apache/cassandra/tools/nodetool/DropCIDRGroupTest.java @@ -23,15 +23,18 @@ import java.util.List; import java.util.Map; +import org.junit.Assume; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.apache.cassandra.auth.AuthCacheService; import org.apache.cassandra.auth.AuthTestUtils; +import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.CIDR; import org.apache.cassandra.cql3.CQLTester; import org.apache.cassandra.tools.ToolRunner; +import org.apache.cassandra.utils.CassandraVersion; import static org.assertj.core.api.Assertions.assertThat; @@ -40,6 +43,9 @@ public class DropCIDRGroupTest extends CQLTester @BeforeClass public static void setup() throws Exception { + Assume.assumeFalse("CIDR features require Cassandra 5.0+", + DatabaseDescriptor.getStorageCompatibilityMode().isBefore(CassandraVersion.CASSANDRA_5_0.major)); + CQLTester.setUpClass(); CQLTester.requireAuthentication(); AuthCacheService.initializeAndRegisterCaches(); diff --git a/test/unit/org/apache/cassandra/tools/nodetool/GetCIDRGroupsOfIPTest.java b/test/unit/org/apache/cassandra/tools/nodetool/GetCIDRGroupsOfIPTest.java index e8844053c3a1..45ec589bd7d5 100644 --- a/test/unit/org/apache/cassandra/tools/nodetool/GetCIDRGroupsOfIPTest.java +++ b/test/unit/org/apache/cassandra/tools/nodetool/GetCIDRGroupsOfIPTest.java @@ -23,15 +23,18 @@ import java.util.List; import java.util.Map; +import org.junit.Assume; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.apache.cassandra.auth.AuthCacheService; import org.apache.cassandra.auth.AuthTestUtils; +import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.CIDR; import org.apache.cassandra.cql3.CQLTester; import org.apache.cassandra.tools.ToolRunner; +import org.apache.cassandra.utils.CassandraVersion; import static org.assertj.core.api.Assertions.assertThat; @@ -40,6 +43,9 @@ public class GetCIDRGroupsOfIPTest extends CQLTester @BeforeClass public static void setup() throws Exception { + Assume.assumeFalse("CIDR features require Cassandra 5.0+", + DatabaseDescriptor.getStorageCompatibilityMode().isBefore(CassandraVersion.CASSANDRA_5_0.major)); + CQLTester.setUpClass(); CQLTester.requireAuthentication(); AuthCacheService.initializeAndRegisterCaches(); diff --git a/test/unit/org/apache/cassandra/tools/nodetool/InvalidateCIDRPermissionsCacheTest.java b/test/unit/org/apache/cassandra/tools/nodetool/InvalidateCIDRPermissionsCacheTest.java index ec75c587b0ff..3ff47cd166d1 100644 --- a/test/unit/org/apache/cassandra/tools/nodetool/InvalidateCIDRPermissionsCacheTest.java +++ b/test/unit/org/apache/cassandra/tools/nodetool/InvalidateCIDRPermissionsCacheTest.java @@ -20,6 +20,7 @@ import java.net.InetSocketAddress; +import org.junit.Assume; import org.junit.BeforeClass; import org.junit.Test; @@ -31,6 +32,7 @@ import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.CQLTester; import org.apache.cassandra.tools.ToolRunner; +import org.apache.cassandra.utils.CassandraVersion; import static org.apache.cassandra.auth.AuthTestUtils.ROLE_A; import static org.apache.cassandra.auth.AuthTestUtils.ROLE_B; @@ -44,6 +46,9 @@ public class InvalidateCIDRPermissionsCacheTest extends CQLTester @BeforeClass public static void setup() throws Exception { + Assume.assumeFalse("CIDR features require Cassandra 5.0+", + DatabaseDescriptor.getStorageCompatibilityMode().isBefore(CassandraVersion.CASSANDRA_5_0.major)); + DatabaseDescriptor.setRolesValidity(Integer.MAX_VALUE-1); CQLTester.setUpClass(); CQLTester.requireAuthentication(); diff --git a/test/unit/org/apache/cassandra/tools/nodetool/ListCIDRGroupTest.java b/test/unit/org/apache/cassandra/tools/nodetool/ListCIDRGroupTest.java index a6b377d9dcd5..1fde1e03a61f 100644 --- a/test/unit/org/apache/cassandra/tools/nodetool/ListCIDRGroupTest.java +++ b/test/unit/org/apache/cassandra/tools/nodetool/ListCIDRGroupTest.java @@ -24,14 +24,17 @@ import java.util.List; import java.util.Map; +import org.junit.Assume; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.apache.cassandra.auth.AuthCacheService; +import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.CIDR; import org.apache.cassandra.cql3.CQLTester; import org.apache.cassandra.tools.ToolRunner; +import org.apache.cassandra.utils.CassandraVersion; import static org.apache.cassandra.auth.AuthTestUtils.insertCidrsMappings; import static org.assertj.core.api.Assertions.assertThat; @@ -41,6 +44,9 @@ public class ListCIDRGroupTest extends CQLTester @BeforeClass public static void setup() throws Exception { + Assume.assumeFalse("CIDR features require Cassandra 5.0+", + DatabaseDescriptor.getStorageCompatibilityMode().isBefore(CassandraVersion.CASSANDRA_5_0.major)); + CQLTester.setUpClass(); CQLTester.requireAuthentication(); AuthCacheService.initializeAndRegisterCaches(); diff --git a/test/unit/org/apache/cassandra/tools/nodetool/ReloadCIDRGroupsCacheTest.java b/test/unit/org/apache/cassandra/tools/nodetool/ReloadCIDRGroupsCacheTest.java index 3237d77c32aa..64525f44e4cd 100644 --- a/test/unit/org/apache/cassandra/tools/nodetool/ReloadCIDRGroupsCacheTest.java +++ b/test/unit/org/apache/cassandra/tools/nodetool/ReloadCIDRGroupsCacheTest.java @@ -20,6 +20,7 @@ import java.net.InetSocketAddress; +import org.junit.Assume; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; @@ -31,6 +32,7 @@ import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.CQLTester; import org.apache.cassandra.tools.ToolRunner; +import org.apache.cassandra.utils.CassandraVersion; import static java.lang.String.format; import static org.apache.cassandra.auth.AuthKeyspace.CIDR_GROUPS; @@ -46,6 +48,9 @@ public class ReloadCIDRGroupsCacheTest extends CQLTester @BeforeClass public static void setup() throws Exception { + Assume.assumeFalse("CIDR features require Cassandra 5.0+", + DatabaseDescriptor.getStorageCompatibilityMode().isBefore(CassandraVersion.CASSANDRA_5_0.major)); + CQLTester.setUpClass(); CQLTester.requireAuthentication(); diff --git a/test/unit/org/apache/cassandra/tools/nodetool/TableHistogramsTest.java b/test/unit/org/apache/cassandra/tools/nodetool/TableHistogramsTest.java index 733b876c092c..f82997fa969b 100644 --- a/test/unit/org/apache/cassandra/tools/nodetool/TableHistogramsTest.java +++ b/test/unit/org/apache/cassandra/tools/nodetool/TableHistogramsTest.java @@ -42,7 +42,7 @@ public class TableHistogramsTest extends CQLTester private final int ALL_TABLE_SIZE = SystemKeyspace.TABLE_NAMES.size() + SchemaKeyspace.metadata().tables.size() + TraceKeyspace.TABLE_NAMES.size() + - AuthKeyspace.TABLE_NAMES.size() + + AuthKeyspace.tableNames().size() + SystemDistributedKeyspace.TABLE_NAMES.size(); @BeforeClass diff --git a/test/unit/org/apache/cassandra/tools/nodetool/UpdateCIDRGroupTest.java b/test/unit/org/apache/cassandra/tools/nodetool/UpdateCIDRGroupTest.java index b3fbb9d34d8e..98300c8977c0 100644 --- a/test/unit/org/apache/cassandra/tools/nodetool/UpdateCIDRGroupTest.java +++ b/test/unit/org/apache/cassandra/tools/nodetool/UpdateCIDRGroupTest.java @@ -18,13 +18,16 @@ package org.apache.cassandra.tools.nodetool; +import org.junit.Assume; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.apache.cassandra.auth.AuthCacheService; +import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.CQLTester; import org.apache.cassandra.tools.ToolRunner; +import org.apache.cassandra.utils.CassandraVersion; import static java.lang.String.format; import static org.apache.cassandra.auth.AuthKeyspace.CIDR_GROUPS; @@ -36,6 +39,9 @@ public class UpdateCIDRGroupTest extends CQLTester @BeforeClass public static void setup() throws Exception { + Assume.assumeFalse("CIDR features require Cassandra 5.0+", + DatabaseDescriptor.getStorageCompatibilityMode().isBefore(CassandraVersion.CASSANDRA_5_0.major)); + CQLTester.setUpClass(); CQLTester.requireAuthentication(); AuthCacheService.initializeAndRegisterCaches(); From af9c20e5602c71a3076e4aa641e297aa0660312e Mon Sep 17 00:00:00 2001 From: Daniel Jatnieks Date: Mon, 11 May 2026 14:07:50 -0700 Subject: [PATCH 05/19] Fix testAlterTableWithMemtable in SCM HCD_1 mode --- .../cql3/validation/operations/AlterTest.java | 78 ++++++++++++++++--- 1 file changed, 69 insertions(+), 9 deletions(-) diff --git a/test/unit/org/apache/cassandra/cql3/validation/operations/AlterTest.java b/test/unit/org/apache/cassandra/cql3/validation/operations/AlterTest.java index ed17b0974b4c..aff1eb761e45 100644 --- a/test/unit/org/apache/cassandra/cql3/validation/operations/AlterTest.java +++ b/test/unit/org/apache/cassandra/cql3/validation/operations/AlterTest.java @@ -28,6 +28,8 @@ import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.CQLTester; import org.apache.cassandra.db.ColumnFamilyStore; +import org.apache.cassandra.utils.CassandraVersion; +import org.apache.cassandra.utils.StorageCompatibilityMode; import org.apache.cassandra.db.Keyspace; import org.apache.cassandra.db.memtable.Memtable; import org.apache.cassandra.db.memtable.SkipListMemtable; @@ -638,22 +640,41 @@ public void testDoubleWith() throws Throwable @Test public void testAlterTableWithMemtable() throws Throwable { + StorageCompatibilityMode mode = DatabaseDescriptor.getStorageCompatibilityMode(); + createTable("CREATE TABLE %s (a text, b int, c int, primary key (a, b))"); assertSame(MemtableParams.DEFAULT.factory(), getCurrentColumnFamilyStore().metadata().params.memtable.factory()); - assertSchemaOption("memtable", null); - Class defaultClass = getCurrentColumnFamilyStore().getTracker().getView().getCurrentMemtable().getClass(); + Class defaultMemtableClass = getCurrentColumnFamilyStore().getTracker().getView().getCurrentMemtable().getClass(); - testMemtableConfig("skiplist", SkipListMemtable.FACTORY, SkipListMemtable.class); - testMemtableConfig("test_fullname", TestMemtable.FACTORY, SkipListMemtable.class); - testMemtableConfig("test_shortname", SkipListMemtable.FACTORY, SkipListMemtable.class); + if (mode.isBefore(CassandraVersion.CASSANDRA_5_0.major)) + { + assertMemtableOptionVersion4(null, null); + testMemtableConfigVersion4("skiplist", map("class", "SkipListMemtable"), SkipListMemtable.FACTORY, SkipListMemtable.class); + testMemtableConfigVersion4("test_fullname", map("class", "TestMemtable"), TestMemtable.FACTORY, SkipListMemtable.class); + testMemtableConfigVersion4("test_shortname", map("class", "TestMemtable", "skiplist", "true"), SkipListMemtable.FACTORY, SkipListMemtable.class); + } + else + { + assertMemtableOption(null); + testMemtableConfig("skiplist", SkipListMemtable.FACTORY, SkipListMemtable.class); + testMemtableConfig("test_fullname", TestMemtable.FACTORY, SkipListMemtable.class); + testMemtableConfig("test_shortname", SkipListMemtable.FACTORY, SkipListMemtable.class); + } // verify memtable does not change on other ALTER alterTable("ALTER TABLE %s" + " WITH compression = {'class': 'LZ4Compressor'};"); - assertSchemaOption("memtable", "test_shortname"); - - testMemtableConfig("default", MemtableParams.DEFAULT.factory(), defaultClass); + if (mode.isBefore(CassandraVersion.CASSANDRA_5_0.major)) + { + assertMemtableOptionVersion4("test_shortname", map("class", "TestMemtable", "skiplist", "true")); + testMemtableConfigVersion4("default", null, MemtableParams.DEFAULT.factory(), defaultMemtableClass); + } + else + { + assertMemtableOption("test_shortname"); + testMemtableConfig("default", MemtableParams.DEFAULT.factory(), defaultMemtableClass); + } assertAlterTableThrowsException(ConfigurationException.class, "The 'class_name' option must be specified.", @@ -701,13 +722,52 @@ void assertSchemaOption(String option, Object expected) throws Throwable row(expected)); } + /** + * Assert memtable option where schema stores config key as text. + */ + void assertMemtableOption(String expectedConfigKey) throws Throwable + { + assertSchemaOption("memtable", expectedConfigKey); + } + private void testMemtableConfig(String memtableConfig, Memtable.Factory factoryInstance, Class memtableClass) throws Throwable { alterTable("ALTER TABLE %s" + " WITH memtable = '" + memtableConfig + "';"); assertSame(factoryInstance, getCurrentColumnFamilyStore().metadata().params.memtable.factory()); Assert.assertTrue(memtableClass.isInstance(getCurrentColumnFamilyStore().getTracker().getView().getCurrentMemtable())); - assertSchemaOption("memtable", MemtableParams.DEFAULT.configurationKey().equals(memtableConfig) ? null : memtableConfig); + assertMemtableOption(MemtableParams.DEFAULT.configurationKey().equals(memtableConfig) ? null : memtableConfig); + } + + /** + * Assert memtable option in HCD_1 mode where schema stores class name in frozen>. + * @param expectedConfigKey The config key used in ALTER TABLE (e.g., "skiplist", "test_fullname") + * @param expectedSchemaMap The expected map value in schema (e.g., map("class", "SkipListMemtable")) + */ + void assertMemtableOptionVersion4(String expectedConfigKey, Object expectedSchemaMap) throws Throwable + { + // Assert the schema contains the correct map value + Object expectedSchemaValue; + if (expectedConfigKey == null || "default".equals(expectedConfigKey)) + { + // Default memtable is not written to schema at all + expectedSchemaValue = null; + } + else + { + expectedSchemaValue = expectedSchemaMap; + } + assertSchemaOption("memtable", expectedSchemaValue); + } + + private void testMemtableConfigVersion4(String memtableConfig, Object expectedSchemaMap, Memtable.Factory factoryInstance, Class memtableClass) throws Throwable + { + alterTable("ALTER TABLE %s" + + " WITH memtable = '" + memtableConfig + "';"); + assertSame(factoryInstance, getCurrentColumnFamilyStore().metadata().params.memtable.factory()); + Assert.assertTrue(memtableClass.isInstance(getCurrentColumnFamilyStore().getTracker().getView().getCurrentMemtable())); + assertMemtableOptionVersion4(MemtableParams.DEFAULT.configurationKey().equals(memtableConfig) ? null : memtableConfig, + expectedSchemaMap); } @Test From 3ef2fd171abea5173990c7d6c55b40b82a7a386a Mon Sep 17 00:00:00 2001 From: Daniel Jatnieks Date: Mon, 11 May 2026 15:33:31 -0700 Subject: [PATCH 06/19] InstanceConfig should respect TEST_STORAGE_COMPATIBILITY_MODE system property --- .../org/apache/cassandra/distributed/impl/InstanceConfig.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/distributed/org/apache/cassandra/distributed/impl/InstanceConfig.java b/test/distributed/org/apache/cassandra/distributed/impl/InstanceConfig.java index 10bb9af3dd40..c6912fc152f4 100644 --- a/test/distributed/org/apache/cassandra/distributed/impl/InstanceConfig.java +++ b/test/distributed/org/apache/cassandra/distributed/impl/InstanceConfig.java @@ -174,7 +174,8 @@ private InstanceConfig(int num, .set("default_secondary_index", "sai") .set("default_secondary_index_enabled", "true") - .set("storage_compatibility_mode", "NONE"); + // Respect TEST_STORAGE_COMPATIBILITY_MODE system property (defaults to HCD_1 in tests) + .set("storage_compatibility_mode", CassandraRelevantProperties.TEST_STORAGE_COMPATIBILITY_MODE.getString()); } this.featureFlags = EnumSet.noneOf(Feature.class); this.jmxPort = jmx_port; From ba15755c2e464acd178d464c3b4f774689fb12be Mon Sep 17 00:00:00 2001 From: Daniel Jatnieks Date: Mon, 11 May 2026 15:34:37 -0700 Subject: [PATCH 07/19] Fix more CIDR related tests --- .../test/auth/CIDRAuthorizerConfigTest.java | 11 ++++++++ .../test/jmx/JMXGetterCheckTest.java | 25 +++++++++++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/test/distributed/org/apache/cassandra/distributed/test/auth/CIDRAuthorizerConfigTest.java b/test/distributed/org/apache/cassandra/distributed/test/auth/CIDRAuthorizerConfigTest.java index 084a85542d6c..c3f032d2b4be 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/auth/CIDRAuthorizerConfigTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/auth/CIDRAuthorizerConfigTest.java @@ -21,14 +21,25 @@ import java.io.IOException; import com.google.common.collect.ImmutableMap; +import org.junit.Assume; +import org.junit.BeforeClass; import org.junit.Test; +import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.config.ParameterizedClass; import org.apache.cassandra.distributed.Cluster; import org.apache.cassandra.distributed.test.TestBaseImpl; +import org.apache.cassandra.utils.CassandraVersion; public class CIDRAuthorizerConfigTest extends TestBaseImpl { + @BeforeClass + public static void setup() throws Exception + { + Assume.assumeFalse("CIDR features require Cassandra 5.0+", + DatabaseDescriptor.getStorageCompatibilityMode().isBefore(CassandraVersion.CASSANDRA_5_0.major)); + } + @Test public void testParameterizedClass() throws IOException { diff --git a/test/distributed/org/apache/cassandra/distributed/test/jmx/JMXGetterCheckTest.java b/test/distributed/org/apache/cassandra/distributed/test/jmx/JMXGetterCheckTest.java index 5f21c94ab3a2..5e9bee99666b 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/jmx/JMXGetterCheckTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/jmx/JMXGetterCheckTest.java @@ -22,6 +22,7 @@ import java.util.List; import java.util.Set; import java.util.TreeSet; +import java.util.function.Predicate; import javax.management.JMRuntimeException; import javax.management.MBeanAttributeInfo; import javax.management.MBeanInfo; @@ -33,16 +34,19 @@ import com.google.common.collect.ImmutableSet; import org.junit.Test; +import org.apache.cassandra.config.CassandraRelevantProperties; import org.apache.cassandra.distributed.Cluster; import org.apache.cassandra.distributed.api.Feature; import org.apache.cassandra.distributed.api.IInstanceConfig; import org.apache.cassandra.distributed.api.IInvokableInstance; import org.apache.cassandra.distributed.shared.JMXUtil; import org.apache.cassandra.distributed.test.TestBaseImpl; +import org.apache.cassandra.utils.CassandraVersion; +import org.apache.cassandra.utils.StorageCompatibilityMode; public class JMXGetterCheckTest extends TestBaseImpl { - private static final Set IGNORE_ATTRIBUTES = ImmutableSet.of( + private static final Set BASE_IGNORE_ATTRIBUTES = ImmutableSet.of( "org.apache.cassandra.net:type=MessagingService:BackPressurePerHost", // throws unsupported saying the feature was removed... dropped in CASSANDRA-15375 "org.apache.cassandra.db:type=DynamicEndpointSnitch:Scores" // when running in multiple-port-one-IP mode, this fails @@ -60,6 +64,21 @@ public class JMXGetterCheckTest extends TestBaseImpl "org.apache.cassandra.db:type=StorageService:startNativeTransport" // causes multiple loops to fail ); + private static Set getIgnoreAttributes() + { + // CIDR features require Cassandra 5.0+, skip CIDR-related attributes in compatibility mode + StorageCompatibilityMode mode = CassandraRelevantProperties.TEST_STORAGE_COMPATIBILITY_MODE.getEnum(true, StorageCompatibilityMode.class); + if (mode != null && mode.isBefore(CassandraVersion.CASSANDRA_5_0.major)) + { + return ImmutableSet.builder() + .addAll(BASE_IGNORE_ATTRIBUTES) + .add("org.apache.cassandra.db:type=CIDRFilteringMetricsTable:CountsMetricsFromVtable") + .add("org.apache.cassandra.db:type=CIDRFilteringMetricsTable:LatenciesMetricsFromVtable") + .build(); + } + return BASE_IGNORE_ATTRIBUTES; + } + @Test public void testGetters() throws Exception { @@ -81,6 +100,8 @@ public void testGetters() throws Exception */ public static void testAllValidGetters(Cluster cluster) throws Exception { + Set ignoreAttributes = getIgnoreAttributes(); + for (IInvokableInstance instance: cluster) { if (instance.isShutdown()) @@ -101,7 +122,7 @@ public static void testAllValidGetters(Cluster cluster) throws Exception for (MBeanAttributeInfo a : info.getAttributes()) { String fqn = String.format("%s:%s", name, a.getName()); - if (!a.isReadable() || IGNORE_ATTRIBUTES.contains(fqn)) + if (!a.isReadable() || ignoreAttributes.contains(fqn)) continue; try { From 650e5dd5748898ba4fd743e422877d55e353fec2 Mon Sep 17 00:00:00 2001 From: Daniel Jatnieks Date: Mon, 11 May 2026 15:59:14 -0700 Subject: [PATCH 08/19] Skip DropUDTWithRestartTests needing 5.0 version commit log and SSTables when SCM is based on 4.0 --- .../distributed/test/DropUDTWithRestartTest.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/distributed/org/apache/cassandra/distributed/test/DropUDTWithRestartTest.java b/test/distributed/org/apache/cassandra/distributed/test/DropUDTWithRestartTest.java index 468140688ef5..ed53fe0d3193 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/DropUDTWithRestartTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/DropUDTWithRestartTest.java @@ -47,14 +47,18 @@ import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; import org.junit.Ignore; +import org.junit.Assume; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.datastax.driver.core.ResultSet; import com.datastax.driver.core.Session; +import org.apache.cassandra.config.CassandraRelevantProperties; import org.apache.cassandra.db.Keyspace; import org.apache.cassandra.distributed.Cluster; +import org.apache.cassandra.utils.CassandraVersion; +import org.apache.cassandra.utils.StorageCompatibilityMode; import org.apache.cassandra.distributed.api.ConsistencyLevel; import org.apache.cassandra.distributed.api.ICoordinator; import org.apache.cassandra.distributed.api.IInstance; @@ -301,6 +305,11 @@ public void loadCommitLogAndSSTablesWithDroppedColumnTestCassandra41() throws Ex @Test public void loadCommitLogAndSSTablesWithDroppedColumnTestCassandra5() throws Exception { + // Skip this test if running in compatibility mode < 5.0, as it loads CC5.0 format commit logs and SSTables + StorageCompatibilityMode mode = CassandraRelevantProperties.TEST_STORAGE_COMPATIBILITY_MODE.getEnum(true, StorageCompatibilityMode.class); + Assume.assumeFalse("Test requires Cassandra 5.0+ format data", + mode != null && mode.isBefore(CassandraVersion.CASSANDRA_5_0.major)); + // Cassandra limitations // - user types cannot include other non-frozen udt // - cannot drop non-frozen columns @@ -311,6 +320,11 @@ public void loadCommitLogAndSSTablesWithDroppedColumnTestCassandra5() throws Exc @Test public void loadCommitLogAndSSTablesWithDroppedColumnTestCC50() throws Exception { + // Skip this test if running in compatibility mode < 5.0, as it loads CC5.0 format commit logs and SSTables + StorageCompatibilityMode mode = CassandraRelevantProperties.TEST_STORAGE_COMPATIBILITY_MODE.getEnum(true, StorageCompatibilityMode.class); + Assume.assumeFalse("Test requires Cassandra 5.0+ format data", + mode != null && mode.isBefore(CassandraVersion.CASSANDRA_5_0.major)); + loadCommitLogAndSSTablesWithDroppedColumnTest(CC50_PRODUCT_PATH); } From 67e0302b47e5f79990919f3e973349618f23e127 Mon Sep 17 00:00:00 2001 From: Daniel Jatnieks Date: Mon, 11 May 2026 16:00:10 -0700 Subject: [PATCH 09/19] CIDRAuthorizerConfigTest is a distributed test and needs to use CassandraRelevantProperties instead of DatabaseDescriptor to get the SCM --- .../distributed/test/auth/CIDRAuthorizerConfigTest.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/distributed/org/apache/cassandra/distributed/test/auth/CIDRAuthorizerConfigTest.java b/test/distributed/org/apache/cassandra/distributed/test/auth/CIDRAuthorizerConfigTest.java index c3f032d2b4be..aa0f8e4f226b 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/auth/CIDRAuthorizerConfigTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/auth/CIDRAuthorizerConfigTest.java @@ -25,19 +25,22 @@ import org.junit.BeforeClass; import org.junit.Test; -import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.config.CassandraRelevantProperties; import org.apache.cassandra.config.ParameterizedClass; import org.apache.cassandra.distributed.Cluster; import org.apache.cassandra.distributed.test.TestBaseImpl; import org.apache.cassandra.utils.CassandraVersion; +import org.apache.cassandra.utils.StorageCompatibilityMode; public class CIDRAuthorizerConfigTest extends TestBaseImpl { @BeforeClass public static void setup() throws Exception { + // Skip this test if running in compatibility mode < 5.0, as CIDR authorization is a CC5.0+ feature + StorageCompatibilityMode mode = CassandraRelevantProperties.TEST_STORAGE_COMPATIBILITY_MODE.getEnum(true, StorageCompatibilityMode.class); Assume.assumeFalse("CIDR features require Cassandra 5.0+", - DatabaseDescriptor.getStorageCompatibilityMode().isBefore(CassandraVersion.CASSANDRA_5_0.major)); + mode != null && mode.isBefore(CassandraVersion.CASSANDRA_5_0.major)); } @Test From 7e8f75e6f327d9f0f2eeeb322c870546c1e3f815 Mon Sep 17 00:00:00 2001 From: Daniel Jatnieks Date: Mon, 11 May 2026 16:09:03 -0700 Subject: [PATCH 10/19] SchemaCQLHelperTest skip testing with 5.0 only Sharded memtables when SCM is HCD_1 --- .../cassandra/db/SchemaCQLHelperTest.java | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/test/unit/org/apache/cassandra/db/SchemaCQLHelperTest.java b/test/unit/org/apache/cassandra/db/SchemaCQLHelperTest.java index 070b84f383e3..4dc7b84c5962 100644 --- a/test/unit/org/apache/cassandra/db/SchemaCQLHelperTest.java +++ b/test/unit/org/apache/cassandra/db/SchemaCQLHelperTest.java @@ -41,6 +41,7 @@ import org.apache.cassandra.SchemaLoader; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.CQLTester; +import org.apache.cassandra.utils.CassandraVersion; import org.apache.cassandra.cql3.ColumnIdentifier; import org.apache.cassandra.cql3.FieldIdentifier; import org.apache.cassandra.cql3.statements.schema.IndexTarget; @@ -840,11 +841,14 @@ public void testParseCc40MemtableFormat() ColumnFamilyStore cfs7 = Keyspace.open(keyspace).getColumnFamilyStore(tableName7); Assertions.assertThat(cfs7.metadata().params.memtable.configurationKey()).isEqualTo("trie"); - // Test parsing CC 4.0 format with ShardedSkipListMemtable (short name) - String tableName8 = createTable(keyspace, "CREATE TABLE %s (id int PRIMARY KEY, value text) WITH memtable = {'class': 'ShardedSkipListMemtable'}"); - ColumnFamilyStore cfs8 = Keyspace.open(keyspace).getColumnFamilyStore(tableName8); - Assertions.assertThat(cfs8.metadata().params.memtable.configurationKey()).isEqualTo("skiplist_sharded"); - + // Sharded memtables require Cassandra 5.0+, skip this test in compatibility mode + if (!DatabaseDescriptor.getStorageCompatibilityMode().isBefore(CassandraVersion.CASSANDRA_5_0.major)) + { + // Test parsing CC 4.0 format with ShardedSkipListMemtable (short name) + String tableName8 = createTable(keyspace, "CREATE TABLE %s (id int PRIMARY KEY, value text) WITH memtable = {'class': 'ShardedSkipListMemtable'}"); + ColumnFamilyStore cfs8 = Keyspace.open(keyspace).getColumnFamilyStore(tableName8); + Assertions.assertThat(cfs8.metadata().params.memtable.configurationKey()).isEqualTo("skiplist_sharded"); + } // Test parsing CC 4.0 format with empty map (default) String tableName9 = createTable(keyspace, "CREATE TABLE %s (id int PRIMARY KEY, value text) WITH memtable = {}"); ColumnFamilyStore cfs9 = Keyspace.open(keyspace).getColumnFamilyStore(tableName9); @@ -910,12 +914,16 @@ public void testToMapForCC4OutputFormat() Assertions.assertThat(cql2).contains("memtable = {'class': 'SkipListMemtable'"); Assertions.assertThat(cql2).doesNotContain("org.apache.cassandra.db.memtable.SkipListMemtable"); - String tableName3 = createTable(keyspace, "CREATE TABLE %s (id int PRIMARY KEY, value text) WITH memtable = 'skiplist_sharded'"); - ColumnFamilyStore cfs3 = Keyspace.open(keyspace).getColumnFamilyStore(tableName3); - String cql3 = SchemaCQLHelper.getTableMetadataAsCQL(cfs3.metadata(), cfs3.keyspace.getMetadata()); - // Should output short class name for standard Cassandra memtable - Assertions.assertThat(cql3).contains("memtable = {'class': 'ShardedSkipListMemtable'"); - Assertions.assertThat(cql3).doesNotContain("org.apache.cassandra.db.memtable.ShardedSkipListMemtable"); + // Sharded memtables require Cassandra 5.0+, skip this test in compatibility mode + if (!DatabaseDescriptor.getStorageCompatibilityMode().isBefore(CassandraVersion.CASSANDRA_5_0.major)) + { + String tableName3 = createTable(keyspace, "CREATE TABLE %s (id int PRIMARY KEY, value text) WITH memtable = 'skiplist_sharded'"); + ColumnFamilyStore cfs3 = Keyspace.open(keyspace).getColumnFamilyStore(tableName3); + String cql3 = SchemaCQLHelper.getTableMetadataAsCQL(cfs3.metadata(), cfs3.keyspace.getMetadata()); + // Should output short class name for standard Cassandra memtable + Assertions.assertThat(cql3).contains("memtable = {'class': 'ShardedSkipListMemtable'"); + Assertions.assertThat(cql3).doesNotContain("org.apache.cassandra.db.memtable.ShardedSkipListMemtable"); + } // Test that tables created with fully qualified class names also output short class names String tableName4 = createTable(keyspace, "CREATE TABLE %s (id int PRIMARY KEY, value text) WITH memtable = {'class': 'org.apache.cassandra.db.memtable.TrieMemtable'}"); From f1b117620d77a761500d716c8c90cb5126a545e5 Mon Sep 17 00:00:00 2001 From: Daniel Jatnieks Date: Mon, 11 May 2026 16:14:52 -0700 Subject: [PATCH 11/19] Fix SchemaCQLHelperTest.testCfmOptionsCQL --- .../apache/cassandra/db/SchemaCQLHelperTest.java | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/test/unit/org/apache/cassandra/db/SchemaCQLHelperTest.java b/test/unit/org/apache/cassandra/db/SchemaCQLHelperTest.java index 4dc7b84c5962..8ce64313b858 100644 --- a/test/unit/org/apache/cassandra/db/SchemaCQLHelperTest.java +++ b/test/unit/org/apache/cassandra/db/SchemaCQLHelperTest.java @@ -387,23 +387,33 @@ public void testCfmOptionsCQL() ColumnFamilyStore cfs = Keyspace.open(keyspace).getColumnFamilyStore(table); + boolean isCompatibilityMode = DatabaseDescriptor.getStorageCompatibilityMode().isBefore(5); + String memtableFormat = isCompatibilityMode + ? " AND memtable = {}\n" + : " AND memtable = 'default'\n"; + + // allow_auto_snapshot and incremental_backups are not present in pre-5.0 compatibility mode + String autoSnapshotAndIncrementalBackups = isCompatibilityMode + ? "" + : " AND allow_auto_snapshot = true\n" + + " AND incremental_backups = true\n"; + assertThat(SchemaCQLHelper.getTableMetadataAsCQL(cfs.metadata(), cfs.keyspace.getMetadata()), containsString("AND CLUSTERING ORDER BY (cl1 ASC)\n" + " AND DROPPED COLUMN RECORD reg1 ascii USING TIMESTAMP " + droppedTimestamp +"\n" + " AND additional_write_policy = 'ALWAYS'\n" + - " AND allow_auto_snapshot = true\n" + + autoSnapshotAndIncrementalBackups + " AND bloom_filter_fp_chance = 1.0\n" + " AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'}\n" + " AND cdc = false\n" + " AND comment = 'comment'\n" + " AND compaction = {'class': 'org.apache.cassandra.db.compaction.LeveledCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4', 'sstable_size_in_mb': '1'}\n" + " AND compression = {'chunk_length_in_kb': '64', 'class': 'org.apache.cassandra.io.compress.LZ4Compressor', 'min_compress_ratio': '2.0'}\n" + - " AND memtable = 'default'\n" + + memtableFormat + " AND crc_check_chance = 0.3\n" + " AND default_time_to_live = 4\n" + " AND extensions = {'ext1': 0x76616c31}\n" + " AND gc_grace_seconds = 5\n" + - " AND incremental_backups = true\n" + " AND max_index_interval = 7\n" + " AND memtable_flush_period_in_ms = 8\n" + " AND min_index_interval = 6\n" + From 709b688b29d4a3c0f8a9fde7086d0211a9cec048 Mon Sep 17 00:00:00 2001 From: Daniel Jatnieks Date: Mon, 11 May 2026 16:17:57 -0700 Subject: [PATCH 12/19] Filter the parameter list in MemtableQuickTest and MemtableSizeTestBase based on the storage compatibility mode. --- .../db/memtable/MemtableQuickTest.java | 25 ++++++++++++++----- .../db/memtable/MemtableSizeTestBase.java | 18 ++++++++++--- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/test/unit/org/apache/cassandra/db/memtable/MemtableQuickTest.java b/test/unit/org/apache/cassandra/db/memtable/MemtableQuickTest.java index 279ec0992096..8fef89ce7c08 100644 --- a/test/unit/org/apache/cassandra/db/memtable/MemtableQuickTest.java +++ b/test/unit/org/apache/cassandra/db/memtable/MemtableQuickTest.java @@ -32,12 +32,15 @@ import org.slf4j.LoggerFactory; import org.apache.cassandra.Util; +import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.CQLTester; import org.apache.cassandra.cql3.UntypedResultSet; import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.db.Keyspace; import org.apache.cassandra.dht.Range; import org.apache.cassandra.io.sstable.format.SSTableReader; +import org.apache.cassandra.utils.CassandraVersion; +import org.apache.cassandra.utils.StorageCompatibilityMode; import org.apache.cassandra.utils.concurrent.Refs; @RunWith(Parameterized.class) @@ -60,12 +63,22 @@ public class MemtableQuickTest extends CQLTester @Parameterized.Parameters(name = "{0}") public static List parameters() { - return ImmutableList.of("skiplist", - "skiplist_sharded", - "skiplist_sharded_locking", - "trie", - "trie_stage1", - "persistent_memory"); + // Sharded memtables require Cassandra 5.0+, skip them in compatibility mode + StorageCompatibilityMode mode = DatabaseDescriptor.getStorageCompatibilityMode(); + boolean skipSharded = mode != null && mode.isBefore(CassandraVersion.CASSANDRA_5_0.major); + + ImmutableList.Builder params = ImmutableList.builder(); + params.add("skiplist"); + if (!skipSharded) + { + params.add("skiplist_sharded"); + params.add("skiplist_sharded_locking"); + } + params.add("trie"); + params.add("trie_stage1"); + params.add("persistent_memory"); + + return params.build(); } @BeforeClass diff --git a/test/unit/org/apache/cassandra/db/memtable/MemtableSizeTestBase.java b/test/unit/org/apache/cassandra/db/memtable/MemtableSizeTestBase.java index 0b9d8922092d..071b4c420895 100644 --- a/test/unit/org/apache/cassandra/db/memtable/MemtableSizeTestBase.java +++ b/test/unit/org/apache/cassandra/db/memtable/MemtableSizeTestBase.java @@ -40,7 +40,9 @@ import org.apache.cassandra.db.Keyspace; import org.apache.cassandra.dht.IPartitioner; import org.apache.cassandra.service.StorageService; +import org.apache.cassandra.utils.CassandraVersion; import org.apache.cassandra.utils.FBUtilities; +import org.apache.cassandra.utils.StorageCompatibilityMode; import org.github.jamm.MemoryMeter; import org.github.jamm.MemoryMeter.Guess; @@ -77,10 +79,18 @@ public abstract class MemtableSizeTestBase extends CQLTester @Parameterized.Parameters(name = "{0}") public static List parameters() { - return ImmutableList.of("skiplist", - "skiplist_sharded", - "trie_stage1", - "trie"); + // Sharded memtables require Cassandra 5.0+, skip them in compatibility mode + StorageCompatibilityMode mode = DatabaseDescriptor.getStorageCompatibilityMode(); + boolean skipSharded = mode != null && mode.isBefore(CassandraVersion.CASSANDRA_5_0.major); + + ImmutableList.Builder params = ImmutableList.builder(); + params.add("skiplist"); + if (!skipSharded) + params.add("skiplist_sharded"); + params.add("trie_stage1"); + params.add("trie"); + + return params.build(); } // Must be within 3% of the real usage. We are actually more precise than this, but the threshold is set higher to From f1807b015ededf47a858889ff90a3ca584e4ce6d Mon Sep 17 00:00:00 2001 From: Daniel Jatnieks Date: Mon, 11 May 2026 18:43:53 -0700 Subject: [PATCH 13/19] Fix test framework to respect SCM for CIDR features, remove production guards Fixes the test framework (CQLTester) to conditionally initialize CIDR components based on Storage Compatibility Mode, matching production behavior in StorageService.doAuthSetup(). --- .../auth/CIDRGroupsMappingManager.java | 24 ------------------- .../auth/CIDRPermissionsManager.java | 13 ---------- .../cassandra/auth/CassandraRoleManager.java | 8 ------- .../cassandra/auth/GrantAndRevokeTest.java | 9 +------ .../org/apache/cassandra/cql3/CQLTester.java | 19 ++++++++++++--- 5 files changed, 17 insertions(+), 56 deletions(-) diff --git a/src/java/org/apache/cassandra/auth/CIDRGroupsMappingManager.java b/src/java/org/apache/cassandra/auth/CIDRGroupsMappingManager.java index 93987a0a6d86..6d68d5edee02 100644 --- a/src/java/org/apache/cassandra/auth/CIDRGroupsMappingManager.java +++ b/src/java/org/apache/cassandra/auth/CIDRGroupsMappingManager.java @@ -32,13 +32,10 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.CIDR; import org.apache.cassandra.cql3.PageSize; -import org.apache.cassandra.utils.CassandraVersion; import org.apache.cassandra.cql3.QueryOptions; import org.apache.cassandra.cql3.QueryProcessor; import org.apache.cassandra.cql3.UntypedResultSet; @@ -61,7 +58,6 @@ public class CIDRGroupsMappingManager implements CIDRGroupsMappingManagerMBean { - private static final Logger logger = LoggerFactory.getLogger(CIDRGroupsMappingManager.class); public static final String MBEAN_NAME = "org.apache.cassandra.db:type=CIDRGroupsMappingManager"; private SelectStatement getCidrGroupsStatement; @@ -71,14 +67,6 @@ public class CIDRGroupsMappingManager implements CIDRGroupsMappingManagerMBean public void setup() { - // In compatibility mode, cidr_groups table doesn't exist - if (DatabaseDescriptor.getStorageCompatibilityMode().isBefore(CassandraVersion.CASSANDRA_5_0.major)) - { - logger.debug("Skipping CIDR groups statement preparation in compatibility mode {}", - DatabaseDescriptor.getStorageCompatibilityMode()); - return; - } - if (!MBeanWrapper.instance.isRegistered(MBEAN_NAME)) MBeanWrapper.instance.registerMBean(this, MBEAN_NAME); @@ -118,10 +106,6 @@ protected static String getCidrTuplesSetString(List cidrs) public UntypedResultSet getCidrGroupsTableEntries() { - // In compatibility mode, cidr_groups table doesn't exist - if (DatabaseDescriptor.getStorageCompatibilityMode().isBefore(CassandraVersion.CASSANDRA_5_0.major)) - return UntypedResultSet.create(Collections.emptyList()); - String getAllRowsQuery = String.format("SELECT * FROM %s.%s", SchemaConstants.AUTH_KEYSPACE_NAME, AuthKeyspace.CIDR_GROUPS); @@ -131,10 +115,6 @@ public UntypedResultSet getCidrGroupsTableEntries() public Set getAvailableCidrGroups() { - // In compatibility mode, statement is not prepared - if (getCidrGroupsStatement == null) - return Collections.emptySet(); - Set availableCidrGroups = new HashSet<>(); QueryOptions options = QueryOptions.forInternalCalls(CassandraAuthorizer.authReadConsistencyLevel(), @@ -178,10 +158,6 @@ public Set> retrieveCidrsFromRow(UntypedResultSet.Row r public Set getCidrsOfCidrGroupAsStrings(String cidrGroupName) { - // In compatibility mode, statement is not prepared - if (getCidrsForCidrGroupStatement == null) - return Collections.emptySet(); - QueryOptions options = QueryOptions.forInternalCalls(CassandraAuthorizer.authReadConsistencyLevel(), Lists.newArrayList(ByteBufferUtil.bytes(cidrGroupName))); ResultMessage.Rows rows = select(getCidrsForCidrGroupStatement, options); diff --git a/src/java/org/apache/cassandra/auth/CIDRPermissionsManager.java b/src/java/org/apache/cassandra/auth/CIDRPermissionsManager.java index 160462c45f8c..8476d7b8650c 100644 --- a/src/java/org/apache/cassandra/auth/CIDRPermissionsManager.java +++ b/src/java/org/apache/cassandra/auth/CIDRPermissionsManager.java @@ -35,7 +35,6 @@ import org.apache.cassandra.cql3.QueryProcessor; import org.apache.cassandra.cql3.UntypedResultSet; import org.apache.cassandra.cql3.statements.SelectStatement; -import org.apache.cassandra.utils.CassandraVersion; import org.apache.cassandra.db.ConsistencyLevel; import org.apache.cassandra.db.marshal.UTF8Type; import org.apache.cassandra.exceptions.RequestExecutionException; @@ -62,14 +61,6 @@ public class CIDRPermissionsManager implements CIDRPermissionsManagerMBean, Auth public void setup() { - // In compatibility mode, cidr_permissions table doesn't exist - if (DatabaseDescriptor.getStorageCompatibilityMode().isBefore(CassandraVersion.CASSANDRA_5_0.major)) - { - logger.debug("Skipping CIDR permissions statement preparation in compatibility mode {}", - DatabaseDescriptor.getStorageCompatibilityMode()); - return; - } - if (!MBeanWrapper.instance.isRegistered(MBEAN_NAME)) MBeanWrapper.instance.registerMBean(this, MBEAN_NAME); @@ -96,10 +87,6 @@ UntypedResultSet process(String query, ConsistencyLevel cl) throws RequestExecut private Set getAuthorizedCIDRGroups(String name) { - // In compatibility mode, statement is not prepared - if (getCidrPermissionsOfUserStatement == null) - return Collections.emptySet(); - QueryOptions options = QueryOptions.forInternalCalls(CassandraAuthorizer.authReadConsistencyLevel(), Lists.newArrayList(ByteBufferUtil.bytes(name))); diff --git a/src/java/org/apache/cassandra/auth/CassandraRoleManager.java b/src/java/org/apache/cassandra/auth/CassandraRoleManager.java index 9145084f3a92..9fc0404637a6 100644 --- a/src/java/org/apache/cassandra/auth/CassandraRoleManager.java +++ b/src/java/org/apache/cassandra/auth/CassandraRoleManager.java @@ -42,7 +42,6 @@ import org.apache.cassandra.config.CassandraRelevantProperties; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.schema.SchemaConstants; -import org.apache.cassandra.utils.CassandraVersion; import org.apache.cassandra.cql3.*; import org.apache.cassandra.cql3.statements.SelectStatement; import org.apache.cassandra.db.ConsistencyLevel; @@ -259,13 +258,6 @@ protected final void loadRoleStatement() protected void loadIdentityStatement() { - // Only prepare statement if identity_to_role table exists (5.0+) - if (DatabaseDescriptor.getStorageCompatibilityMode().isBefore(CassandraVersion.CASSANDRA_5_0.major)) - { - loadIdentityStatement = null; - return; - } - loadIdentityStatement = (SelectStatement) prepare("SELECT role from %s.%s where identity=?", SchemaConstants.AUTH_KEYSPACE_NAME, AuthKeyspace.IDENTITY_TO_ROLES); diff --git a/test/unit/org/apache/cassandra/auth/GrantAndRevokeTest.java b/test/unit/org/apache/cassandra/auth/GrantAndRevokeTest.java index 46808804e9c5..db348e244753 100644 --- a/test/unit/org/apache/cassandra/auth/GrantAndRevokeTest.java +++ b/test/unit/org/apache/cassandra/auth/GrantAndRevokeTest.java @@ -70,14 +70,7 @@ public static void setUpClass() public void tearDown() throws Throwable { useSuperUser(); - try - { - executeNet("DROP ROLE " + user); - } - catch (Exception e) - { - // Role may not exist if test was skipped - ignore - } + executeNet("DROP ROLE IF EXISTS " + user); } @Test diff --git a/test/unit/org/apache/cassandra/cql3/CQLTester.java b/test/unit/org/apache/cassandra/cql3/CQLTester.java index 0c9619e367a1..a137352df89c 100644 --- a/test/unit/org/apache/cassandra/cql3/CQLTester.java +++ b/test/unit/org/apache/cassandra/cql3/CQLTester.java @@ -189,6 +189,7 @@ import org.apache.cassandra.transport.SimpleClient; import org.apache.cassandra.transport.messages.ResultMessage; import org.apache.cassandra.utils.ByteBufferUtil; +import org.apache.cassandra.utils.CassandraVersion; import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.JMXServerUtils; import org.apache.cassandra.utils.JVMStabilityInspector; @@ -605,7 +606,11 @@ protected static void requireAuthentication() DatabaseDescriptor.setAuthenticator(new AuthTestUtils.LocalPasswordAuthenticator()); DatabaseDescriptor.setAuthorizer(new AuthTestUtils.LocalCassandraAuthorizer()); DatabaseDescriptor.setNetworkAuthorizer(new AuthTestUtils.LocalCassandraNetworkAuthorizer()); - DatabaseDescriptor.setCIDRAuthorizer(new AuthTestUtils.LocalCassandraCIDRAuthorizer()); + + // Only set CIDR authorizer if storage compatibility mode supports it (5.0+) + // This matches production behavior in StorageService.doAuthSetup() + if (!DatabaseDescriptor.getStorageCompatibilityMode().isBefore(5)) + DatabaseDescriptor.setCIDRAuthorizer(new AuthTestUtils.LocalCassandraCIDRAuthorizer()); // The CassandraRoleManager constructor set the supported and alterable options based on // DatabaseDescriptor authenticator type so it needs to be created only after the authenticator is set. @@ -614,7 +619,10 @@ protected static void requireAuthentication() public void setup() { loadRoleStatement(); - loadIdentityStatement(); + // Only load identity statement if storage compatibility mode supports it (5.0+) + // This matches CassandraRoleManager.setup() production behavior + if (DatabaseDescriptor.getStorageCompatibilityMode().major >= CassandraVersion.CASSANDRA_5_0.major) + loadIdentityStatement(); QueryProcessor.executeInternal(createDefaultRoleQuery()); } }; @@ -625,7 +633,12 @@ public void setup() DatabaseDescriptor.getAuthenticator().setup(); DatabaseDescriptor.getAuthorizer().setup(); DatabaseDescriptor.getNetworkAuthorizer().setup(); - DatabaseDescriptor.getCIDRAuthorizer().setup(); + + // Only setup CIDR authorizer if storage compatibility mode supports it (5.0+) + // This matches production behavior in StorageService.doAuthSetup() + if (!DatabaseDescriptor.getStorageCompatibilityMode().isBefore(5)) + DatabaseDescriptor.getCIDRAuthorizer().setup(); + Schema.instance.registerListener(new AuthSchemaChangeListener()); AuthCacheService.initializeAndRegisterCaches(); From 8a9a811ee55b1cb1998fee0dac51ae0ec4917251 Mon Sep 17 00:00:00 2001 From: Daniel Jatnieks Date: Mon, 11 May 2026 18:56:00 -0700 Subject: [PATCH 14/19] Fix CommitLogDescriptorTest.testVersions for SCM --- .../cassandra/db/commitlog/CommitLogDescriptorTest.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/unit/org/apache/cassandra/db/commitlog/CommitLogDescriptorTest.java b/test/unit/org/apache/cassandra/db/commitlog/CommitLogDescriptorTest.java index eef2823012d3..a9577ae30f30 100644 --- a/test/unit/org/apache/cassandra/db/commitlog/CommitLogDescriptorTest.java +++ b/test/unit/org/apache/cassandra/db/commitlog/CommitLogDescriptorTest.java @@ -85,7 +85,13 @@ public void testVersions() Assert.assertEquals(1340512736956320000L, CommitLogDescriptor.fromFileName("CommitLog-2-1340512736956320000.log").id); - Assert.assertEquals(MessagingService.current_version, new CommitLogDescriptor(1340512736956320000L, null, neverEnabledEncryption).getMessagingVersion()); + // The CommitLogDescriptor constructor uses currentStorageVersion() which + // respects storage compatibility mode, so expect the storage messaging version. + int expectedVersion = StorageCompatibilityMode.current().storageMessagingVersion(); + Assert.assertEquals(expectedVersion, new CommitLogDescriptor(1340512736956320000L, null, neverEnabledEncryption).getMessagingVersion()); + + // CURRENT_VERSION is computed from MessagingService.current_version (doesn't respect SCM) + // but when we read from a filename, it should return the messaging version that corresponds to the version in the filename String newCLName = "CommitLog-" + CommitLogDescriptor.CURRENT_VERSION + "-1340512736956320000.log"; Assert.assertEquals(MessagingService.current_version, CommitLogDescriptor.fromFileName(newCLName).getMessagingVersion()); } From 8b5b8e8452968fe1a044f7302e50b5b49b1683dc Mon Sep 17 00:00:00 2001 From: Daniel Jatnieks Date: Mon, 11 May 2026 18:56:39 -0700 Subject: [PATCH 15/19] Ignore CompactionHistoryTest tests in SCM compatibility mode --- .../cassandra/tools/nodetool/CompactionHistoryTest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/unit/org/apache/cassandra/tools/nodetool/CompactionHistoryTest.java b/test/unit/org/apache/cassandra/tools/nodetool/CompactionHistoryTest.java index 7dbda7768454..925647d19395 100644 --- a/test/unit/org/apache/cassandra/tools/nodetool/CompactionHistoryTest.java +++ b/test/unit/org/apache/cassandra/tools/nodetool/CompactionHistoryTest.java @@ -27,6 +27,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; +import org.junit.Assume; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; @@ -34,12 +35,14 @@ import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; +import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.CQLTester; import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.db.Keyspace; import org.apache.cassandra.db.SystemKeyspace; import org.apache.cassandra.db.compaction.OperationType; import org.apache.cassandra.tools.ToolRunner.ToolResult; +import org.apache.cassandra.utils.CassandraVersion; import org.apache.cassandra.utils.FBUtilities; import static org.apache.cassandra.db.compaction.CompactionHistoryTabularData.COMPACTION_TYPE_PROPERTY; @@ -72,6 +75,8 @@ public static Collection data() @BeforeClass public static void setup() throws Exception { + Assume.assumeFalse("Compaction properties feature requires Cassandra 5.0+", + DatabaseDescriptor.getStorageCompatibilityMode().isBefore(CassandraVersion.CASSANDRA_5_0.major)); requireNetwork(); startJMXServer(); } From 3264a32b5b194fa1844a5ca47cccf963c0617625 Mon Sep 17 00:00:00 2001 From: Daniel Jatnieks Date: Mon, 11 May 2026 19:15:19 -0700 Subject: [PATCH 16/19] Fix CreateTest for SCM and refactor common code with AlterTest to CQLTester. --- .../org/apache/cassandra/cql3/CQLTester.java | 37 ++++++++ .../cql3/validation/operations/AlterTest.java | 33 ------- .../validation/operations/CreateTest.java | 89 ++++++++++++++----- 3 files changed, 102 insertions(+), 57 deletions(-) diff --git a/test/unit/org/apache/cassandra/cql3/CQLTester.java b/test/unit/org/apache/cassandra/cql3/CQLTester.java index a137352df89c..7277f43c86a9 100644 --- a/test/unit/org/apache/cassandra/cql3/CQLTester.java +++ b/test/unit/org/apache/cassandra/cql3/CQLTester.java @@ -175,6 +175,7 @@ import org.apache.cassandra.schema.Schema; import org.apache.cassandra.schema.SchemaConstants; import org.apache.cassandra.schema.SchemaKeyspace; +import org.apache.cassandra.schema.SchemaKeyspaceTables; import org.apache.cassandra.schema.SchemaTestUtil; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.serializers.TypeSerializer; @@ -1643,6 +1644,42 @@ protected static void assertSchemaChange(String query, Assert.assertEquals(expectedArgTypes != null ? Arrays.asList(expectedArgTypes) : null, schemaChange.argTypes); } + /** + * Assert a table schema option has the expected value. + * @param option The schema option name (e.g., "memtable", "compression") + * @param expected The expected value for the option + */ + protected void assertSchemaOption(String option, Object expected) throws Throwable + { + assertRows(execute(String.format("SELECT " + option + " FROM %s.%s WHERE keyspace_name = ? and table_name = ?;", + SchemaConstants.SCHEMA_KEYSPACE_NAME, + SchemaKeyspaceTables.TABLES), + KEYSPACE, + currentTable()), + row(expected)); + } + + /** + * Assert memtable option in HCD_1 mode where schema stores class name in frozen>. + * @param expectedConfigKey The config key used in CREATE/ALTER TABLE (e.g., "skiplist", "test_fullname") + * @param expectedSchemaMap The expected map value in schema (e.g., map("class", "SkipListMemtable")) + */ + protected void assertMemtableOptionVersion4(String expectedConfigKey, Object expectedSchemaMap) throws Throwable + { + // Assert the schema contains the correct map value + Object expectedSchemaValue; + if (expectedConfigKey == null || "default".equals(expectedConfigKey)) + { + // Default memtable is not written to schema at all + expectedSchemaValue = null; + } + else + { + expectedSchemaValue = expectedSchemaMap; + } + assertSchemaOption("memtable", expectedSchemaValue); + } + protected static void assertWarningsContain(Message.Response response, String message) { assertWarningsContain(response.getWarnings(), message); diff --git a/test/unit/org/apache/cassandra/cql3/validation/operations/AlterTest.java b/test/unit/org/apache/cassandra/cql3/validation/operations/AlterTest.java index aff1eb761e45..44734d4b04b4 100644 --- a/test/unit/org/apache/cassandra/cql3/validation/operations/AlterTest.java +++ b/test/unit/org/apache/cassandra/cql3/validation/operations/AlterTest.java @@ -41,8 +41,6 @@ import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.locator.TokenMetadata; import org.apache.cassandra.schema.MemtableParams; -import org.apache.cassandra.schema.SchemaConstants; -import org.apache.cassandra.schema.SchemaKeyspaceTables; import org.apache.cassandra.service.StorageService; import org.apache.cassandra.utils.FBUtilities; @@ -712,16 +710,6 @@ public void testAlterTableWithMemtable() throws Throwable + " WITH memtable = 'unknown';"); } - void assertSchemaOption(String option, Object expected) throws Throwable - { - assertRows(execute(format("SELECT " + option + " FROM %s.%s WHERE keyspace_name = ? and table_name = ?;", - SchemaConstants.SCHEMA_KEYSPACE_NAME, - SchemaKeyspaceTables.TABLES), - KEYSPACE, - currentTable()), - row(expected)); - } - /** * Assert memtable option where schema stores config key as text. */ @@ -739,27 +727,6 @@ private void testMemtableConfig(String memtableConfig, Memtable.Factory factoryI assertMemtableOption(MemtableParams.DEFAULT.configurationKey().equals(memtableConfig) ? null : memtableConfig); } - /** - * Assert memtable option in HCD_1 mode where schema stores class name in frozen>. - * @param expectedConfigKey The config key used in ALTER TABLE (e.g., "skiplist", "test_fullname") - * @param expectedSchemaMap The expected map value in schema (e.g., map("class", "SkipListMemtable")) - */ - void assertMemtableOptionVersion4(String expectedConfigKey, Object expectedSchemaMap) throws Throwable - { - // Assert the schema contains the correct map value - Object expectedSchemaValue; - if (expectedConfigKey == null || "default".equals(expectedConfigKey)) - { - // Default memtable is not written to schema at all - expectedSchemaValue = null; - } - else - { - expectedSchemaValue = expectedSchemaMap; - } - assertSchemaOption("memtable", expectedSchemaValue); - } - private void testMemtableConfigVersion4(String memtableConfig, Object expectedSchemaMap, Memtable.Factory factoryInstance, Class memtableClass) throws Throwable { alterTable("ALTER TABLE %s" diff --git a/test/unit/org/apache/cassandra/cql3/validation/operations/CreateTest.java b/test/unit/org/apache/cassandra/cql3/validation/operations/CreateTest.java index 25ece3026372..878b9a788d06 100644 --- a/test/unit/org/apache/cassandra/cql3/validation/operations/CreateTest.java +++ b/test/unit/org/apache/cassandra/cql3/validation/operations/CreateTest.java @@ -32,6 +32,8 @@ import org.apache.cassandra.config.CassandraRelevantProperties; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.CQLTester; +import org.apache.cassandra.utils.CassandraVersion; +import org.apache.cassandra.utils.StorageCompatibilityMode; import org.apache.cassandra.cql3.Duration; import org.apache.cassandra.db.Mutation; import org.apache.cassandra.db.memtable.Memtable; @@ -52,13 +54,11 @@ import org.apache.cassandra.schema.MemtableParams; import org.apache.cassandra.schema.Schema; import org.apache.cassandra.schema.SchemaConstants; -import org.apache.cassandra.schema.SchemaKeyspaceTables; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.service.StorageService; import org.apache.cassandra.triggers.ITrigger; import org.apache.cassandra.utils.ByteBufferUtil; -import static java.lang.String.format; import static org.apache.cassandra.cql3.Duration.NANOS_PER_HOUR; import static org.apache.cassandra.cql3.Duration.NANOS_PER_MICRO; import static org.apache.cassandra.cql3.Duration.NANOS_PER_MILLI; @@ -636,25 +636,53 @@ public static class InvalidMemtableFactoryField @Test public void testCreateTableWithMemtable() throws Throwable { + StorageCompatibilityMode mode = DatabaseDescriptor.getStorageCompatibilityMode(); + createTable("CREATE TABLE %s (a text, b int, c int, primary key (a, b))"); assertSame(MemtableParams.DEFAULT.factory(), getCurrentColumnFamilyStore().metadata().params.memtable.factory()); Class defaultClass = getCurrentColumnFamilyStore().getTracker().getView().getCurrentMemtable().getClass(); - assertSchemaOption("memtable", null); - - testMemtableConfig("skiplist", SkipListMemtable.FACTORY, SkipListMemtable.class); - testMemtableConfig("trie", MemtableParams.get("trie").factory(), TrieMemtable.class); - testMemtableConfig("skiplist_remapped", SkipListMemtable.FACTORY, SkipListMemtable.class); - testMemtableConfig("test_fullname", TestMemtable.FACTORY, SkipListMemtable.class); - testMemtableConfig("test_shortname", SkipListMemtable.FACTORY, SkipListMemtable.class); - testMemtableConfig("default", MemtableParams.DEFAULT.factory(), defaultClass); - - // Handle CC 4.0 memtable configuration given as a map - testMapMemtableConfig("", null, MemtableParams.DEFAULT.factory(), defaultClass); - testMapMemtableConfig("SkipListMemtable", "skiplist", MemtableParams.get("skiplist").factory(), SkipListMemtable.class); - testMapMemtableConfig("TrieMemtable","trie", MemtableParams.get("trie").factory(), TrieMemtable.class); - testMapMemtableConfig("TrieMemtableStage1", "trie", MemtableParams.get("trie").factory(), TrieMemtable.class); - testMapMemtableConfig("PersistentMemoryMemtable", "persistent_memory", MemtableParams.get("persistent_memory").factory(), PersistentMemoryMemtable.class); + if (mode.isBefore(CassandraVersion.CASSANDRA_5_0.major)) + { + // In CC4/HCD_1 mode, memtable is stored as frozen> in schema + assertMemtableOptionVersion4(null, null); + + testMemtableConfigVersion4("skiplist", map("class", "SkipListMemtable"), SkipListMemtable.FACTORY, SkipListMemtable.class); + // Note: trie config in test/conf/cassandra.yaml has shards: 4 parameter + testMemtableConfigVersion4("trie", map("class", "TrieMemtable", "shards", "4"), MemtableParams.get("trie").factory(), TrieMemtable.class); + testMemtableConfigVersion4("skiplist_remapped", map("class", "SkipListMemtable"), SkipListMemtable.FACTORY, SkipListMemtable.class); + testMemtableConfigVersion4("test_fullname", map("class", "TestMemtable"), TestMemtable.FACTORY, SkipListMemtable.class); + testMemtableConfigVersion4("test_shortname", map("class", "TestMemtable", "skiplist", "true"), SkipListMemtable.FACTORY, SkipListMemtable.class); + testMemtableConfigVersion4("default", null, MemtableParams.DEFAULT.factory(), defaultClass); + + // Handle CC 4.0 memtable configuration given as a map + // Note: empty map defaults to default memtable, which is stored as null in schema + testMapMemtableConfigVersion4("", null, MemtableParams.DEFAULT.factory(), defaultClass); + testMapMemtableConfigVersion4("SkipListMemtable", map("class", "SkipListMemtable"), MemtableParams.get("skiplist").factory(), SkipListMemtable.class); + // Note: trie config in test/conf/cassandra.yaml has shards: 4 parameter + testMapMemtableConfigVersion4("TrieMemtable", map("class", "TrieMemtable", "shards", "4"), MemtableParams.get("trie").factory(), TrieMemtable.class); + testMapMemtableConfigVersion4("TrieMemtableStage1", map("class", "TrieMemtable", "shards", "4"), MemtableParams.get("trie").factory(), TrieMemtable.class); + testMapMemtableConfigVersion4("PersistentMemoryMemtable", map("class", "PersistentMemoryMemtable"), MemtableParams.get("persistent_memory").factory(), PersistentMemoryMemtable.class); + } + else + { + // In CC5 mode, memtable is stored as text in schema + assertSchemaOption("memtable", null); + + testMemtableConfig("skiplist", SkipListMemtable.FACTORY, SkipListMemtable.class); + testMemtableConfig("trie", MemtableParams.get("trie").factory(), TrieMemtable.class); + testMemtableConfig("skiplist_remapped", SkipListMemtable.FACTORY, SkipListMemtable.class); + testMemtableConfig("test_fullname", TestMemtable.FACTORY, SkipListMemtable.class); + testMemtableConfig("test_shortname", SkipListMemtable.FACTORY, SkipListMemtable.class); + testMemtableConfig("default", MemtableParams.DEFAULT.factory(), defaultClass); + + // Handle CC 4.0 memtable configuration given as a map + testMapMemtableConfig("", null, MemtableParams.DEFAULT.factory(), defaultClass); + testMapMemtableConfig("SkipListMemtable", "skiplist", MemtableParams.get("skiplist").factory(), SkipListMemtable.class); + testMapMemtableConfig("TrieMemtable","trie", MemtableParams.get("trie").factory(), TrieMemtable.class); + testMapMemtableConfig("TrieMemtableStage1", "trie", MemtableParams.get("trie").factory(), TrieMemtable.class); + testMapMemtableConfig("PersistentMemoryMemtable", "persistent_memory", MemtableParams.get("persistent_memory").factory(), PersistentMemoryMemtable.class); + } assertThrowsConfigurationException("The 'class_name' option must be specified.", "CREATE TABLE %s (a text, b int, c int, primary key (a, b))" @@ -710,14 +738,27 @@ private void testMapMemtableConfig(String memtableConfig, String expectedMemtabl assertSchemaOption("memtable", expectedMemtableConfig); } - void assertSchemaOption(String option, Object expected) throws Throwable + private void testMemtableConfigVersion4(String memtableConfig, Object expectedSchemaMap, Memtable.Factory factoryInstance, Class memtableClass) throws Throwable { - assertRows(execute(format("SELECT " + option + " FROM %s.%s WHERE keyspace_name = ? and table_name = ?;", - SchemaConstants.SCHEMA_KEYSPACE_NAME, - SchemaKeyspaceTables.TABLES), - KEYSPACE, - currentTable()), - row(expected)); + createTable("CREATE TABLE %s (a text, b int, c int, primary key (a, b))" + + " WITH memtable = '" + memtableConfig + "';"); + assertSame(factoryInstance, getCurrentColumnFamilyStore().metadata().params.memtable.factory()); + Assert.assertTrue(memtableClass.isInstance(getCurrentColumnFamilyStore().getTracker().getView().getCurrentMemtable())); + + assertMemtableOptionVersion4(MemtableParams.DEFAULT.configurationKey().equals(memtableConfig) ? null : memtableConfig, + expectedSchemaMap); + } + + private void testMapMemtableConfigVersion4(String memtableConfig, Object expectedSchemaMap, Memtable.Factory factoryInstance, Class memtableClass) throws Throwable + { + String memtableMap = "".equals(memtableConfig) ? memtableConfig : String.format("'class' : '%s'", memtableConfig); + createTable("CREATE TABLE %s (a text, b int, c int, primary key (a, b))" + + " WITH memtable = {" + memtableMap + "};"); + assertSame(factoryInstance, getCurrentColumnFamilyStore().metadata().params.memtable.factory()); + Assert.assertTrue(memtableClass.isInstance(getCurrentColumnFamilyStore().getTracker().getView().getCurrentMemtable())); + + // For map-based CREATE, always write the expected schema map (not null) + assertSchemaOption("memtable", expectedSchemaMap); } @Test From 07d80a4214c62e1598d6b10edbb18cb47fa97cf6 Mon Sep 17 00:00:00 2001 From: Daniel Jatnieks Date: Mon, 11 May 2026 19:27:08 -0700 Subject: [PATCH 17/19] Fix RandomSchemaTest with update CassandraGenerators to filter sharded memtables in SCM 4 mode --- .../apache/cassandra/utils/CassandraGenerators.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/test/unit/org/apache/cassandra/utils/CassandraGenerators.java b/test/unit/org/apache/cassandra/utils/CassandraGenerators.java index b064ec98fc74..801eb60ae61c 100644 --- a/test/unit/org/apache/cassandra/utils/CassandraGenerators.java +++ b/test/unit/org/apache/cassandra/utils/CassandraGenerators.java @@ -206,7 +206,16 @@ public TableMetadataBuilder withKnownMemtables() { Set known = MemtableParams.knownDefinitions(); // for testing reason, some invalid types are added; filter out - List valid = known.stream().filter(name -> !name.startsWith("test_")).collect(Collectors.toList()); + // Also filter out CC5-only types (sharded memtables) when in HCD_1/CC4 mode + List valid = known.stream() + .filter(name -> !name.startsWith("test_")) + .filter(name -> { + // Filter out sharded memtables when in CC4 compatibility mode + if (DatabaseDescriptor.getStorageCompatibilityMode().isBefore(org.apache.cassandra.utils.CassandraVersion.CASSANDRA_5_0.major)) + return !name.toLowerCase().contains("sharded"); + return true; + }) + .collect(Collectors.toList()); memtableKeyGen = SourceDSL.arbitrary().pick(valid); return this; } From e861b678fa37f026f490b6f971976b67be601f4b Mon Sep 17 00:00:00 2001 From: Daniel Jatnieks Date: Mon, 11 May 2026 19:32:24 -0700 Subject: [PATCH 18/19] Fix checkstyle complaints --- .../cassandra/distributed/test/jmx/JMXGetterCheckTest.java | 1 - test/unit/org/apache/cassandra/cql3/CQLTester.java | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/test/distributed/org/apache/cassandra/distributed/test/jmx/JMXGetterCheckTest.java b/test/distributed/org/apache/cassandra/distributed/test/jmx/JMXGetterCheckTest.java index 5e9bee99666b..d29330eb200e 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/jmx/JMXGetterCheckTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/jmx/JMXGetterCheckTest.java @@ -22,7 +22,6 @@ import java.util.List; import java.util.Set; import java.util.TreeSet; -import java.util.function.Predicate; import javax.management.JMRuntimeException; import javax.management.MBeanAttributeInfo; import javax.management.MBeanInfo; diff --git a/test/unit/org/apache/cassandra/cql3/CQLTester.java b/test/unit/org/apache/cassandra/cql3/CQLTester.java index 7277f43c86a9..c953143212fb 100644 --- a/test/unit/org/apache/cassandra/cql3/CQLTester.java +++ b/test/unit/org/apache/cassandra/cql3/CQLTester.java @@ -211,7 +211,6 @@ import static org.apache.cassandra.cql3.SchemaElement.SchemaElementType.MATERIALIZED_VIEW; import static org.apache.cassandra.cql3.SchemaElement.SchemaElementType.TABLE; import static org.apache.cassandra.cql3.SchemaElement.SchemaElementType.TYPE; -import static org.apache.cassandra.index.sai.SAITester.vector; import static org.apache.cassandra.utils.Clock.Global.nanoTime; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -1495,7 +1494,7 @@ protected void waitForIndexBuilds(String index) /** * Index creation is asynchronous. This method waits until the specified index hasn't any building task running. *

- * This method differs from {@link #waitForIndexQueryable(String, String)} in that it doesn't require the + * This method differs from {@link #waitForIndexQueryable(String, String, long, TimeUnit)} in that it doesn't require the * index to be fully nor successfully built, so it can be used to wait for failing index builds. * * @param keyspace the index keyspace name @@ -1660,7 +1659,7 @@ protected void assertSchemaOption(String option, Object expected) throws Throwab } /** - * Assert memtable option in HCD_1 mode where schema stores class name in frozen>. + * Assert memtable option in HCD_1 mode where schema stores class name in {@code frozen>}. * @param expectedConfigKey The config key used in CREATE/ALTER TABLE (e.g., "skiplist", "test_fullname") * @param expectedSchemaMap The expected map value in schema (e.g., map("class", "SkipListMemtable")) */ From 38e342bedaf218cf8fb1755f0dbff44648acf29c Mon Sep 17 00:00:00 2001 From: Daniel Jatnieks Date: Mon, 11 May 2026 21:59:18 -0700 Subject: [PATCH 19/19] Fix SchemaLoader to not set or setup CIDRAuthorizer in DatabaseDescriptor when SCM is 4.0 --- test/unit/org/apache/cassandra/SchemaLoader.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/test/unit/org/apache/cassandra/SchemaLoader.java b/test/unit/org/apache/cassandra/SchemaLoader.java index 2e28f7fe2da5..5786bf019cc5 100644 --- a/test/unit/org/apache/cassandra/SchemaLoader.java +++ b/test/unit/org/apache/cassandra/SchemaLoader.java @@ -308,14 +308,26 @@ public static void setupAuth(IRoleManager roleManager, IAuthenticator authentica DatabaseDescriptor.setAuthenticator(authenticator); DatabaseDescriptor.setAuthorizer(authorizer); DatabaseDescriptor.setNetworkAuthorizer(networkAuthorizer); - DatabaseDescriptor.setCIDRAuthorizer(cidrAuthorizer); + + // Only set and setup CIDR authorizer if storage compatibility mode supports it (5.0+) + // This matches the pattern in CQLTester and production behavior + if (!DatabaseDescriptor.getStorageCompatibilityMode().isBefore(5)) + { + DatabaseDescriptor.setCIDRAuthorizer(cidrAuthorizer); + } + SchemaTestUtil.announceNewKeyspace(AuthKeyspace.metadata()); DatabaseDescriptor.getRoleManager().setup(); DatabaseDescriptor.getAuthenticator().setup(); DatabaseDescriptor.getInternodeAuthenticator().setupInternode(); DatabaseDescriptor.getAuthorizer().setup(); DatabaseDescriptor.getNetworkAuthorizer().setup(); - DatabaseDescriptor.getCIDRAuthorizer().setup(); + + // Only setup CIDR authorizer if storage compatibility mode supports it (5.0+) + // This matches production behavior in StorageService.doAuthSetup() + if (!DatabaseDescriptor.getStorageCompatibilityMode().isBefore(5)) + DatabaseDescriptor.getCIDRAuthorizer().setup(); + Schema.instance.registerListener(new AuthSchemaChangeListener()); }