diff --git a/CHANGES.txt b/CHANGES.txt index 9ded13f525..54fa1e8ec6 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 4.1 + * Add guardrail for creation of secondary indexes (CASSANDRA-17498) * Add guardrail to disallow creation of uncompressed tables (CASSANDRA-17504) * Add guardrail to disallow creation of new COMPACT STORAGE tables (CASSANDRA-17522) * repair vtables should expose a completed field due to lack of filtering options in CQL (CASSANDRA-17520) diff --git a/conf/cassandra.yaml b/conf/cassandra.yaml index df1eeb259b..1a876ca934 100644 --- a/conf/cassandra.yaml +++ b/conf/cassandra.yaml @@ -1619,6 +1619,8 @@ drop_compact_storage_enabled: false # The two thresholds default to -1 to disable. # secondary_indexes_per_table_warn_threshold: -1 # secondary_indexes_per_table_fail_threshold: -1 +# Guardrail to enable or disable the creation of secondary indexes +# secondary_indexes_enabled: true # Guardrail to warn or fail when creating more materialized views per table than threshold. # The two thresholds default to -1 to disable. # materialized_views_per_table_warn_threshold: -1 diff --git a/src/java/org/apache/cassandra/config/Config.java b/src/java/org/apache/cassandra/config/Config.java index 00db7ea859..e0dc5767c6 100644 --- a/src/java/org/apache/cassandra/config/Config.java +++ b/src/java/org/apache/cassandra/config/Config.java @@ -800,6 +800,7 @@ public class Config public volatile Set write_consistency_levels_warned = Collections.emptySet(); public volatile Set write_consistency_levels_disallowed = Collections.emptySet(); public volatile boolean user_timestamps_enabled = true; + public volatile boolean secondary_indexes_enabled = true; public volatile boolean uncompressed_tables_enabled = true; public volatile boolean compact_tables_enabled = true; public volatile boolean read_before_write_list_operations_enabled = true; diff --git a/src/java/org/apache/cassandra/config/GuardrailsOptions.java b/src/java/org/apache/cassandra/config/GuardrailsOptions.java index 405cca7ee3..d08f8c555a 100644 --- a/src/java/org/apache/cassandra/config/GuardrailsOptions.java +++ b/src/java/org/apache/cassandra/config/GuardrailsOptions.java @@ -332,6 +332,20 @@ public class GuardrailsOptions implements GuardrailsConfig x -> config.user_timestamps_enabled = x); } + @Override + public boolean getSecondaryIndexesEnabled() + { + return config.secondary_indexes_enabled; + } + + public void setSecondaryIndexesEnabled(boolean enabled) + { + updatePropertyWithLogging("secondary_indexes_enabled", + enabled, + () -> config.secondary_indexes_enabled, + x -> config.secondary_indexes_enabled = x); + } + @Override public boolean getUncompressedTablesEnabled() { diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateIndexStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateIndexStatement.java index 686a9cd5d1..7a3a41e618 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/CreateIndexStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateIndexStatement.java @@ -84,6 +84,8 @@ public final class CreateIndexStatement extends AlterSchemaStatement { attrs.validate(); + Guardrails.createSecondaryIndexesEnabled.ensureEnabled("Creating secondary indexes", state); + if (attrs.isCustom && attrs.customClass.equals(SASIIndex.class.getName()) && !DatabaseDescriptor.getSASIIndexesEnabled()) throw new InvalidRequestException("SASI indexes are disabled. Enable in cassandra.yaml to use."); diff --git a/src/java/org/apache/cassandra/db/guardrails/Guardrails.java b/src/java/org/apache/cassandra/db/guardrails/Guardrails.java index 6e08660b2a..824160491f 100644 --- a/src/java/org/apache/cassandra/db/guardrails/Guardrails.java +++ b/src/java/org/apache/cassandra/db/guardrails/Guardrails.java @@ -98,6 +98,14 @@ public final class Guardrails implements GuardrailsMBean : format("Tables cannot have more than %s secondary indexes, aborting the creation of secondary index %s", threshold, what)); + /** + * Guardrail disabling user's ability to create secondary indexes + */ + public static final DisableFlag createSecondaryIndexesEnabled = + new DisableFlag("secondary_indexes", + state -> !CONFIG_PROVIDER.getOrCreate(state).getSecondaryIndexesEnabled(), + "User creation of secondary indexes"); + /** * Guardrail on the number of materialized views per table. */ @@ -355,6 +363,18 @@ public final class Guardrails implements GuardrailsMBean DEFAULT_CONFIG.setSecondaryIndexesPerTableThreshold(warn, fail); } + @Override + public boolean getSecondaryIndexesEnabled() + { + return DEFAULT_CONFIG.getSecondaryIndexesEnabled(); + } + + @Override + public void setSecondaryIndexesEnabled(boolean enabled) + { + DEFAULT_CONFIG.setSecondaryIndexesEnabled(enabled); + } + @Override public int getMaterializedViewsPerTableWarnThreshold() { diff --git a/src/java/org/apache/cassandra/db/guardrails/GuardrailsConfig.java b/src/java/org/apache/cassandra/db/guardrails/GuardrailsConfig.java index 3b3473a288..f95989fcf7 100644 --- a/src/java/org/apache/cassandra/db/guardrails/GuardrailsConfig.java +++ b/src/java/org/apache/cassandra/db/guardrails/GuardrailsConfig.java @@ -93,6 +93,11 @@ public interface GuardrailsConfig */ int getSecondaryIndexesPerTableFailThreshold(); + /** + * @return Whether creation of secondary indexes is allowed. + */ + boolean getSecondaryIndexesEnabled(); + /** * @return The threshold to warn when creating more materialized views per table than threshold. */ diff --git a/src/java/org/apache/cassandra/db/guardrails/GuardrailsMBean.java b/src/java/org/apache/cassandra/db/guardrails/GuardrailsMBean.java index 77f0d2dfc4..8d151a1032 100644 --- a/src/java/org/apache/cassandra/db/guardrails/GuardrailsMBean.java +++ b/src/java/org/apache/cassandra/db/guardrails/GuardrailsMBean.java @@ -119,6 +119,17 @@ public interface GuardrailsMBean */ void setSecondaryIndexesPerTableThreshold(int warn, int fail); + /** + * @return Whether secondary index creation is active or not on the node + */ + boolean getSecondaryIndexesEnabled(); + + /** + * Enables or disables the ability to create secondary indexes + * @param enabled + */ + void setSecondaryIndexesEnabled(boolean enabled); + /** * @return The threshold to warn when creating more materialized views per table than threshold. * -1 means disabled. diff --git a/test/unit/org/apache/cassandra/db/guardrails/GuardrailSecondaryIndexTester.java b/test/unit/org/apache/cassandra/db/guardrails/GuardrailSecondaryIndexTester.java new file mode 100644 index 0000000000..53ba1b5eb6 --- /dev/null +++ b/test/unit/org/apache/cassandra/db/guardrails/GuardrailSecondaryIndexTester.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.cassandra.db.guardrails; + +import org.junit.Before; +import org.junit.Test; + +import static java.lang.String.format; + +/** + * Tests the guardrail for disabling user creation of secondary indexes, {@link Guardrails#setSecondaryIndexesEnabled(boolean)}. + */ +public class GuardrailSecondaryIndexTester extends GuardrailTester +{ + public GuardrailSecondaryIndexTester() + { + super(Guardrails.createSecondaryIndexesEnabled); + } + + @Before + public void setupTest() + { + createTable("CREATE TABLE %s (k int PRIMARY KEY, v1 int, v2 int, v3 int, v4 int)"); + } + + private void setGuardrail(boolean enabled) + { + guardrails().setSecondaryIndexesEnabled(enabled); + } + + @Test + public void testCreateIndex() throws Throwable + { + setGuardrail(true); + assertValid(String.format("CREATE INDEX %s ON %s.%s(%s)", "v1_idx", keyspace(), currentTable(), "v1")); + assertValid(String.format("CREATE INDEX %s ON %s.%s(%s)", "v2_idx", keyspace(), currentTable(), "v2")); + + setGuardrail(false); + assertFails(String.format("CREATE INDEX %s ON %s.%s(%s)", "v3_idx", keyspace(), currentTable(), "v3"), "Creating secondary indexes"); + assertFails(String.format("CREATE INDEX %s ON %s.%s(%s)", "v4_idx", keyspace(), currentTable(), "v4"), "Creating secondary indexes"); + assertFails(String.format("CREATE INDEX %s ON %s.%s(%s)", "v2_idx", keyspace(), currentTable(), "v2"), "Creating secondary indexes"); + + setGuardrail(true); + assertValid(String.format("CREATE INDEX %s ON %s.%s(%s)", "v3_idx", keyspace(), currentTable(), "v3")); + assertValid(String.format("CREATE INDEX %s ON %s.%s(%s)", "v4_idx", keyspace(), currentTable(), "v4")); + + // Confirm can drop in either state + setGuardrail(false); + dropIndex(format("DROP INDEX %s.%s", keyspace(), "v1_idx")); + + setGuardrail(true); + dropIndex(format("DROP INDEX %s.%s", keyspace(), "v2_idx")); + } + + @Test + public void testCustomIndex() throws Throwable + { + // 2i guardrail will also affect custom indexes + setGuardrail(false); + assertFails(format("CREATE CUSTOM INDEX ON %%s (%s) USING 'org.apache.cassandra.index.sasi.SASIIndex'", "v4"), + format("Creating secondary indexes", currentTable()) + ); + + // Confirm custom creation will work on flip + setGuardrail(true); + assertValid(format("CREATE CUSTOM INDEX ON %%s (%s) USING 'org.apache.cassandra.index.sasi.SASIIndex'", "v4")); + } +}