mirror of https://github.com/apache/cassandra
Add guardrails for read/write consistency levels
patch by Andrés de la Peña; reviewed by Caleb Rackliffe for CASSANDRA-17188 Co-authored-by: Andrés de la Peña <a.penya.garcia@gmail.com> Co-authored-by: Aleksandr Sorokoumov <aleksandr.sorokoumov@gmail.com>
This commit is contained in:
parent
2b2c6decfa
commit
a41040ccdc
|
|
@ -1,4 +1,5 @@
|
|||
4.1
|
||||
* Add guardrails for read/write consistency levels (CASSANDRA-17188)
|
||||
* Add guardrail for SELECT IN terms and their cartesian product (CASSANDRA-17187)
|
||||
* remove unused imports in cqlsh.py and cqlshlib (CASSANDRA-17413)
|
||||
* deprecate property windows_timer_interval (CASSANDRA-17404)
|
||||
|
|
|
|||
|
|
@ -1604,7 +1604,8 @@ drop_compact_storage_enabled: false
|
|||
# The two thresholds default to -1 to disable.
|
||||
# materialized_views_per_table_warn_threshold: -1
|
||||
# materialized_views_per_table_fail_threshold: -1
|
||||
# Guardrail to ignore or reject properties when creating tables. By default all properties are allowed.
|
||||
# Guardrail to warn about, ignore or reject properties when creating tables. By default all properties are allowed.
|
||||
# table_properties_warned: []
|
||||
# table_properties_ignored: []
|
||||
# table_properties_disallowed: []
|
||||
# Guardrail to allow/disallow user-provided timestamps. Defaults to true.
|
||||
|
|
@ -1625,6 +1626,12 @@ drop_compact_storage_enabled: false
|
|||
# The two thresholds default to -1 to disable.
|
||||
# in_select_cartesian_product_warn_threshold: -1
|
||||
# in_select_cartesian_product_fail_threshold: -1
|
||||
# Guardrail to warn about or reject read consistency levels. By default, all consistency levels are allowed.
|
||||
# read_consistency_levels_warned: []
|
||||
# read_consistency_levels_disallowed: []
|
||||
# Guardrail to warn about or reject write consistency levels. By default, all consistency levels are allowed.
|
||||
# write_consistency_levels_warned: []
|
||||
# write_consistency_levels_disallowed: []
|
||||
|
||||
# Startup Checks are executed as part of Cassandra startup process, not all of them
|
||||
# are configurable (so you can disable them) but these which are enumerated bellow.
|
||||
|
|
|
|||
|
|
@ -788,8 +788,13 @@ public class Config
|
|||
public volatile int partition_keys_in_select_fail_threshold = DISABLED_GUARDRAIL;
|
||||
public volatile int in_select_cartesian_product_warn_threshold = DISABLED_GUARDRAIL;
|
||||
public volatile int in_select_cartesian_product_fail_threshold = DISABLED_GUARDRAIL;
|
||||
public volatile Set<String> table_properties_warned = Collections.emptySet();
|
||||
public volatile Set<String> table_properties_ignored = Collections.emptySet();
|
||||
public volatile Set<String> table_properties_disallowed = Collections.emptySet();
|
||||
public volatile Set<ConsistencyLevel> read_consistency_levels_warned = Collections.emptySet();
|
||||
public volatile Set<ConsistencyLevel> read_consistency_levels_disallowed = Collections.emptySet();
|
||||
public volatile Set<ConsistencyLevel> write_consistency_levels_warned = Collections.emptySet();
|
||||
public volatile Set<ConsistencyLevel> write_consistency_levels_disallowed = Collections.emptySet();
|
||||
public volatile boolean user_timestamps_enabled = true;
|
||||
public volatile boolean read_before_write_list_operations_enabled = true;
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
package org.apache.cassandra.config;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
|
@ -27,6 +28,7 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.apache.cassandra.cql3.statements.schema.TableAttributes;
|
||||
import org.apache.cassandra.db.ConsistencyLevel;
|
||||
import org.apache.cassandra.db.guardrails.Guardrails;
|
||||
import org.apache.cassandra.db.guardrails.GuardrailsConfig;
|
||||
|
||||
|
|
@ -64,12 +66,17 @@ public class GuardrailsOptions implements GuardrailsConfig
|
|||
validateIntThreshold(config.columns_per_table_warn_threshold, config.columns_per_table_fail_threshold, "columns_per_table");
|
||||
validateIntThreshold(config.secondary_indexes_per_table_warn_threshold, config.secondary_indexes_per_table_fail_threshold, "secondary_indexes_per_table");
|
||||
validateIntThreshold(config.materialized_views_per_table_warn_threshold, config.materialized_views_per_table_fail_threshold, "materialized_views_per_table");
|
||||
config.table_properties_warned = validateTableProperties(config.table_properties_warned, "table_properties_warned");
|
||||
config.table_properties_ignored = validateTableProperties(config.table_properties_ignored, "table_properties_ignored");
|
||||
config.table_properties_disallowed = validateTableProperties(config.table_properties_disallowed, "table_properties_disallowed");
|
||||
validateIntThreshold(config.page_size_warn_threshold, config.page_size_fail_threshold, "page_size");
|
||||
validateIntThreshold(config.partition_keys_in_select_warn_threshold,
|
||||
config.partition_keys_in_select_fail_threshold, "partition_keys_in_select");
|
||||
validateIntThreshold(config.in_select_cartesian_product_warn_threshold, config.in_select_cartesian_product_fail_threshold, "in_select_cartesian_product");
|
||||
config.read_consistency_levels_warned = validateConsistencyLevels(config.read_consistency_levels_warned, "read_consistency_levels_warned");
|
||||
config.read_consistency_levels_disallowed = validateConsistencyLevels(config.read_consistency_levels_disallowed, "read_consistency_levels_disallowed");
|
||||
config.write_consistency_levels_warned = validateConsistencyLevels(config.write_consistency_levels_warned, "write_consistency_levels_warned");
|
||||
config.write_consistency_levels_disallowed = validateConsistencyLevels(config.write_consistency_levels_disallowed, "write_consistency_levels_disallowed");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -266,6 +273,20 @@ public class GuardrailsOptions implements GuardrailsConfig
|
|||
x -> config.page_size_fail_threshold = x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getTablePropertiesWarned()
|
||||
{
|
||||
return config.table_properties_warned;
|
||||
}
|
||||
|
||||
public void setTablePropertiesWarned(Set<String> properties)
|
||||
{
|
||||
updatePropertyWithLogging("table_properties_warned",
|
||||
validateTableProperties(properties, "table_properties_warned"),
|
||||
() -> config.table_properties_warned,
|
||||
x -> config.table_properties_warned = x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getTablePropertiesIgnored()
|
||||
{
|
||||
|
|
@ -347,6 +368,61 @@ public class GuardrailsOptions implements GuardrailsConfig
|
|||
x -> config.in_select_cartesian_product_fail_threshold = x);
|
||||
}
|
||||
|
||||
public Set<ConsistencyLevel> getReadConsistencyLevelsWarned()
|
||||
{
|
||||
return config.read_consistency_levels_warned;
|
||||
}
|
||||
|
||||
public void setReadConsistencyLevelsWarned(Set<ConsistencyLevel> consistencyLevels)
|
||||
{
|
||||
updatePropertyWithLogging("read_consistency_levels_warned",
|
||||
validateConsistencyLevels(consistencyLevels, "read_consistency_levels_warned"),
|
||||
() -> config.read_consistency_levels_warned,
|
||||
x -> config.read_consistency_levels_warned = x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ConsistencyLevel> getReadConsistencyLevelsDisallowed()
|
||||
{
|
||||
return config.read_consistency_levels_disallowed;
|
||||
}
|
||||
|
||||
public void setReadConsistencyLevelsDisallowed(Set<ConsistencyLevel> consistencyLevels)
|
||||
{
|
||||
updatePropertyWithLogging("read_consistency_levels_disallowed",
|
||||
validateConsistencyLevels(consistencyLevels, "read_consistency_levels_disallowed"),
|
||||
() -> config.read_consistency_levels_disallowed,
|
||||
x -> config.read_consistency_levels_disallowed = x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ConsistencyLevel> getWriteConsistencyLevelsWarned()
|
||||
{
|
||||
return config.write_consistency_levels_warned;
|
||||
}
|
||||
|
||||
public void setWriteConsistencyLevelsWarned(Set<ConsistencyLevel> consistencyLevels)
|
||||
{
|
||||
updatePropertyWithLogging("write_consistency_levels_warned",
|
||||
validateConsistencyLevels(consistencyLevels, "write_consistency_levels_warned"),
|
||||
() -> config.write_consistency_levels_warned,
|
||||
x -> config.write_consistency_levels_warned = x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ConsistencyLevel> getWriteConsistencyLevelsDisallowed()
|
||||
{
|
||||
return config.write_consistency_levels_disallowed;
|
||||
}
|
||||
|
||||
public void setWriteConsistencyLevelsDisallowed(Set<ConsistencyLevel> consistencyLevels)
|
||||
{
|
||||
updatePropertyWithLogging("write_consistency_levels_disallowed",
|
||||
validateConsistencyLevels(consistencyLevels, "write_consistency_levels_disallowed"),
|
||||
() -> config.write_consistency_levels_disallowed,
|
||||
x -> config.write_consistency_levels_disallowed = x);
|
||||
}
|
||||
|
||||
private static <T> void updatePropertyWithLogging(String propertyName, T newValue, Supplier<T> getter, Consumer<T> setter)
|
||||
{
|
||||
T oldValue = getter.get();
|
||||
|
|
@ -415,4 +491,12 @@ public class GuardrailsOptions implements GuardrailsConfig
|
|||
|
||||
return lowerCaseProperties;
|
||||
}
|
||||
|
||||
private static Set<ConsistencyLevel> validateConsistencyLevels(Set<ConsistencyLevel> consistencyLevels, String name)
|
||||
{
|
||||
if (consistencyLevels == null)
|
||||
throw new IllegalArgumentException(format("Invalid value for %s: null is not allowed", name));
|
||||
|
||||
return consistencyLevels.isEmpty() ? Collections.emptySet() : Sets.immutableEnumSet(consistencyLevels);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import org.slf4j.helpers.MessageFormatter;
|
|||
|
||||
import org.apache.cassandra.audit.AuditLogContext;
|
||||
import org.apache.cassandra.audit.AuditLogEntryType;
|
||||
import org.apache.cassandra.db.guardrails.Guardrails;
|
||||
import org.apache.cassandra.schema.TableId;
|
||||
import org.apache.cassandra.schema.TableMetadata;
|
||||
import org.apache.cassandra.schema.ColumnMetadata;
|
||||
|
|
@ -410,6 +411,9 @@ public class BatchStatement implements CQLStatement
|
|||
if (options.getSerialConsistency() == null)
|
||||
throw new InvalidRequestException("Invalid empty serial consistency level");
|
||||
|
||||
Guardrails.writeConsistencyLevels.guard(EnumSet.of(options.getConsistency(), options.getSerialConsistency()),
|
||||
queryState.getClientState());
|
||||
|
||||
if (hasConditions)
|
||||
return executeWithConditions(options, queryState, queryStartNanoTime);
|
||||
|
||||
|
|
|
|||
|
|
@ -460,6 +460,9 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa
|
|||
if (options.getConsistency() == null)
|
||||
throw new InvalidRequestException("Invalid empty consistency level");
|
||||
|
||||
Guardrails.writeConsistencyLevels.guard(EnumSet.of(options.getConsistency(), options.getSerialConsistency()),
|
||||
queryState.getClientState());
|
||||
|
||||
return hasConditions()
|
||||
? executeWithCondition(queryState, options, queryStartNanoTime)
|
||||
: executeWithoutCondition(queryState, options, queryStartNanoTime);
|
||||
|
|
|
|||
|
|
@ -240,6 +240,7 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement
|
|||
checkNotNull(cl, "Invalid empty consistency level");
|
||||
|
||||
cl.validateForRead();
|
||||
Guardrails.readConsistencyLevels.guard(EnumSet.of(cl), state.getClientState());
|
||||
|
||||
int nowInSec = options.getNowInSeconds(state);
|
||||
int userLimit = getLimit(options);
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@
|
|||
package org.apache.cassandra.db;
|
||||
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import com.carrotsearch.hppc.ObjectIntHashMap;
|
||||
import org.apache.cassandra.locator.Endpoints;
|
||||
import org.apache.cassandra.locator.InOurDc;
|
||||
|
|
@ -81,6 +83,11 @@ public enum ConsistencyLevel
|
|||
return codeIdx[code];
|
||||
}
|
||||
|
||||
public static ConsistencyLevel fromString(String str)
|
||||
{
|
||||
return valueOf(str.toUpperCase(Locale.US));
|
||||
}
|
||||
|
||||
public static int quorumFor(AbstractReplicationStrategy replicationStrategy)
|
||||
{
|
||||
return (replicationStrategy.getReplicationFactor().allReplicas / 2) + 1;
|
||||
|
|
|
|||
|
|
@ -18,13 +18,18 @@
|
|||
|
||||
package org.apache.cassandra.db.guardrails;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.config.GuardrailsOptions;
|
||||
import org.apache.cassandra.db.ConsistencyLevel;
|
||||
import org.apache.cassandra.service.ClientState;
|
||||
import org.apache.cassandra.utils.MBeanWrapper;
|
||||
|
||||
|
|
@ -101,10 +106,11 @@ public final class Guardrails implements GuardrailsMBean
|
|||
threshold, what));
|
||||
|
||||
/**
|
||||
* Guardrail ignoring/disallowing the usage of certain table properties.
|
||||
* Guardrail warning about, ignoring or rejecting the usage of certain table properties.
|
||||
*/
|
||||
public static final Values<String> tableProperties =
|
||||
new Values<>(state -> CONFIG_PROVIDER.getOrCreate(state).getTablePropertiesIgnored(),
|
||||
new Values<>(state -> CONFIG_PROVIDER.getOrCreate(state).getTablePropertiesWarned(),
|
||||
state -> CONFIG_PROVIDER.getOrCreate(state).getTablePropertiesIgnored(),
|
||||
state -> CONFIG_PROVIDER.getOrCreate(state).getTablePropertiesDisallowed(),
|
||||
"Table Properties");
|
||||
|
||||
|
|
@ -161,6 +167,23 @@ public final class Guardrails implements GuardrailsMBean
|
|||
: format("Aborting query because the cartesian product of the IN restrictions on %s " +
|
||||
"produces %d values, this exceeds fail threshold of %s.",
|
||||
what, value, threshold));
|
||||
/**
|
||||
* Guardrail on read consistency levels.
|
||||
*/
|
||||
public static final Values<ConsistencyLevel> readConsistencyLevels =
|
||||
new Values<>(state -> CONFIG_PROVIDER.getOrCreate(state).getReadConsistencyLevelsWarned(),
|
||||
state -> Collections.emptySet(),
|
||||
state -> CONFIG_PROVIDER.getOrCreate(state).getReadConsistencyLevelsDisallowed(),
|
||||
"read consistency levels");
|
||||
|
||||
/**
|
||||
* Guardrail on write consistency levels.
|
||||
*/
|
||||
public static final Values<ConsistencyLevel> writeConsistencyLevels =
|
||||
new Values<>(state -> CONFIG_PROVIDER.getOrCreate(state).getWriteConsistencyLevelsWarned(),
|
||||
state -> Collections.emptySet(),
|
||||
state -> CONFIG_PROVIDER.getOrCreate(state).getWriteConsistencyLevelsDisallowed(),
|
||||
"write consistency levels");
|
||||
|
||||
private Guardrails()
|
||||
{
|
||||
|
|
@ -280,6 +303,35 @@ public final class Guardrails implements GuardrailsMBean
|
|||
DEFAULT_CONFIG.setMaterializedViewsPerTableThreshold(warn, fail);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getTablePropertiesWarned()
|
||||
{
|
||||
return DEFAULT_CONFIG.getTablePropertiesWarned();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTablePropertiesWarnedCSV()
|
||||
{
|
||||
return toCSV(DEFAULT_CONFIG.getTablePropertiesWarned());
|
||||
}
|
||||
|
||||
public void setTablePropertiesWarned(String... properties)
|
||||
{
|
||||
setTablePropertiesWarned(ImmutableSet.copyOf(properties));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTablePropertiesWarned(Set<String> properties)
|
||||
{
|
||||
DEFAULT_CONFIG.setTablePropertiesWarned(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTablePropertiesWarnedCSV(String properties)
|
||||
{
|
||||
setTablePropertiesWarned(fromCSV(properties));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getTablePropertiesDisallowed()
|
||||
{
|
||||
|
|
@ -368,6 +420,7 @@ public final class Guardrails implements GuardrailsMBean
|
|||
DEFAULT_CONFIG.setPageSizeThreshold(warn, fail);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getReadBeforeWriteListOperationsEnabled()
|
||||
{
|
||||
return DEFAULT_CONFIG.getReadBeforeWriteListOperationsEnabled();
|
||||
|
|
@ -415,13 +468,118 @@ public final class Guardrails implements GuardrailsMBean
|
|||
DEFAULT_CONFIG.setInSelectCartesianProductThreshold(warn, fail);
|
||||
}
|
||||
|
||||
public Set<ConsistencyLevel> getReadConsistencyLevelsWarned()
|
||||
{
|
||||
return DEFAULT_CONFIG.getReadConsistencyLevelsWarned();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReadConsistencyLevelsWarnedCSV()
|
||||
{
|
||||
return toCSV(DEFAULT_CONFIG.getReadConsistencyLevelsWarned(), ConsistencyLevel::toString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReadConsistencyLevelsWarned(Set<ConsistencyLevel> consistencyLevels)
|
||||
{
|
||||
DEFAULT_CONFIG.setReadConsistencyLevelsWarned(consistencyLevels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReadConsistencyLevelsWarnedCSV(String consistencyLevels)
|
||||
{
|
||||
DEFAULT_CONFIG.setReadConsistencyLevelsWarned(fromCSV(consistencyLevels, ConsistencyLevel::fromString));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ConsistencyLevel> getReadConsistencyLevelsDisallowed()
|
||||
{
|
||||
return DEFAULT_CONFIG.getReadConsistencyLevelsDisallowed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReadConsistencyLevelsDisallowedCSV()
|
||||
{
|
||||
return toCSV(DEFAULT_CONFIG.getReadConsistencyLevelsDisallowed(), ConsistencyLevel::toString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReadConsistencyLevelsDisallowed(Set<ConsistencyLevel> consistencyLevels)
|
||||
{
|
||||
DEFAULT_CONFIG.setReadConsistencyLevelsDisallowed(consistencyLevels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReadConsistencyLevelsDisallowedCSV(String consistencyLevels)
|
||||
{
|
||||
DEFAULT_CONFIG.setReadConsistencyLevelsDisallowed(fromCSV(consistencyLevels, ConsistencyLevel::fromString));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ConsistencyLevel> getWriteConsistencyLevelsWarned()
|
||||
{
|
||||
return DEFAULT_CONFIG.getWriteConsistencyLevelsWarned();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWriteConsistencyLevelsWarnedCSV()
|
||||
{
|
||||
return toCSV(DEFAULT_CONFIG.getWriteConsistencyLevelsWarned(), ConsistencyLevel::toString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWriteConsistencyLevelsWarned(Set<ConsistencyLevel> consistencyLevels)
|
||||
{
|
||||
DEFAULT_CONFIG.setWriteConsistencyLevelsWarned(consistencyLevels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWriteConsistencyLevelsWarnedCSV(String consistencyLevels)
|
||||
{
|
||||
DEFAULT_CONFIG.setWriteConsistencyLevelsWarned(fromCSV(consistencyLevels, ConsistencyLevel::fromString));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ConsistencyLevel> getWriteConsistencyLevelsDisallowed()
|
||||
{
|
||||
return DEFAULT_CONFIG.getWriteConsistencyLevelsDisallowed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWriteConsistencyLevelsDisallowedCSV()
|
||||
{
|
||||
return toCSV(DEFAULT_CONFIG.getWriteConsistencyLevelsDisallowed(), ConsistencyLevel::toString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWriteConsistencyLevelsDisallowed(Set<ConsistencyLevel> consistencyLevels)
|
||||
{
|
||||
DEFAULT_CONFIG.setWriteConsistencyLevelsDisallowed(consistencyLevels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWriteConsistencyLevelsDisallowedCSV(String consistencyLevels)
|
||||
{
|
||||
DEFAULT_CONFIG.setWriteConsistencyLevelsDisallowed(fromCSV(consistencyLevels, ConsistencyLevel::fromString));
|
||||
}
|
||||
|
||||
private static String toCSV(Set<String> values)
|
||||
{
|
||||
return values == null ? "" : String.join(",", values);
|
||||
return values == null || values.isEmpty() ? "" : String.join(",", values);
|
||||
}
|
||||
|
||||
private static <T> String toCSV(Set<T> values, Function<T, String> formatter)
|
||||
{
|
||||
return values == null || values.isEmpty() ? "" : values.stream().map(formatter).collect(Collectors.joining(","));
|
||||
}
|
||||
|
||||
private static Set<String> fromCSV(String csv)
|
||||
{
|
||||
return csv == null ? null : ImmutableSet.copyOf(csv.split(","));
|
||||
return StringUtils.isEmpty(csv) ? Collections.emptySet() : ImmutableSet.copyOf(csv.split(","));
|
||||
}
|
||||
|
||||
private static <T> Set<T> fromCSV(String csv, Function<String, T> parser)
|
||||
{
|
||||
return StringUtils.isEmpty(csv) ? Collections.emptySet() : fromCSV(csv).stream().map(parser).collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ package org.apache.cassandra.db.guardrails;
|
|||
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.cassandra.db.ConsistencyLevel;
|
||||
|
||||
/**
|
||||
* Configuration settings for guardrails.
|
||||
*
|
||||
|
|
@ -111,6 +113,11 @@ public interface GuardrailsConfig
|
|||
*/
|
||||
int getMaterializedViewsPerTableFailThreshold();
|
||||
|
||||
/**
|
||||
* @return The table properties that are warned about when creating or altering a table.
|
||||
*/
|
||||
Set<String> getTablePropertiesWarned();
|
||||
|
||||
/**
|
||||
* @return The table properties that are ignored when creating or altering a table.
|
||||
*/
|
||||
|
|
@ -156,4 +163,24 @@ public interface GuardrailsConfig
|
|||
* -1 means disabled.
|
||||
*/
|
||||
public int getInSelectCartesianProductFailThreshold();
|
||||
|
||||
/**
|
||||
* @return The consistency levels that are warned about when reading.
|
||||
*/
|
||||
Set<ConsistencyLevel> getReadConsistencyLevelsWarned();
|
||||
|
||||
/**
|
||||
* @return The consistency levels that are disallowed when reading.
|
||||
*/
|
||||
Set<ConsistencyLevel> getReadConsistencyLevelsDisallowed();
|
||||
|
||||
/**
|
||||
* @return The consistency levels that are warned about when writing.
|
||||
*/
|
||||
Set<ConsistencyLevel> getWriteConsistencyLevelsWarned();
|
||||
|
||||
/**
|
||||
* @return The consistency levels that are disallowed when writing.
|
||||
*/
|
||||
Set<ConsistencyLevel> getWriteConsistencyLevelsDisallowed();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ package org.apache.cassandra.db.guardrails;
|
|||
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.cassandra.db.ConsistencyLevel;
|
||||
|
||||
/**
|
||||
* JMX entrypoint for updating the default guardrails configuration parsed from {@code cassandra.yaml}.
|
||||
* <p>
|
||||
|
|
@ -135,6 +137,26 @@ public interface GuardrailsMBean
|
|||
*/
|
||||
void setMaterializedViewsPerTableThreshold(int warn, int fail);
|
||||
|
||||
/**
|
||||
* @return properties that are warned about when creating or altering a table.
|
||||
*/
|
||||
Set<String> getTablePropertiesWarned();
|
||||
|
||||
/**
|
||||
* @return Comma-separated list of properties that are warned about when creating or altering a table.
|
||||
*/
|
||||
String getTablePropertiesWarnedCSV();
|
||||
|
||||
/**
|
||||
* @param properties properties that are warned about when creating or altering a table.
|
||||
*/
|
||||
void setTablePropertiesWarned(Set<String> properties);
|
||||
|
||||
/**
|
||||
* @param properties Comma-separated list of properties that are warned about when creating or altering a table.
|
||||
*/
|
||||
void setTablePropertiesWarnedCSV(String properties);
|
||||
|
||||
/**
|
||||
* @return properties that are not allowed when creating or altering a table.
|
||||
*/
|
||||
|
|
@ -260,4 +282,84 @@ public interface GuardrailsMBean
|
|||
* -1 means disabled.
|
||||
*/
|
||||
public void setInSelectCartesianProductThreshold(int warn, int fail);
|
||||
|
||||
/**
|
||||
* @return consistency levels that are warned about when reading.
|
||||
*/
|
||||
Set<ConsistencyLevel> getReadConsistencyLevelsWarned();
|
||||
|
||||
/**
|
||||
* @return Comma-separated list of consistency levels that are warned about when reading.
|
||||
*/
|
||||
String getReadConsistencyLevelsWarnedCSV();
|
||||
|
||||
/**
|
||||
* @param consistencyLevels consistency levels that are warned about when reading.
|
||||
*/
|
||||
void setReadConsistencyLevelsWarned(Set<ConsistencyLevel> consistencyLevels);
|
||||
|
||||
/**
|
||||
* @param consistencyLevels Comma-separated list of consistency levels that are warned about when reading.
|
||||
*/
|
||||
void setReadConsistencyLevelsWarnedCSV(String consistencyLevels);
|
||||
|
||||
/**
|
||||
* @return consistency levels that are not allowed when reading.
|
||||
*/
|
||||
Set<ConsistencyLevel> getReadConsistencyLevelsDisallowed();
|
||||
|
||||
/**
|
||||
* @return Comma-separated list of consistency levels that are not allowed when reading.
|
||||
*/
|
||||
String getReadConsistencyLevelsDisallowedCSV();
|
||||
|
||||
/**
|
||||
* @param consistencyLevels consistency levels that are not allowed when reading.
|
||||
*/
|
||||
void setReadConsistencyLevelsDisallowed(Set<ConsistencyLevel> consistencyLevels);
|
||||
|
||||
/**
|
||||
* @param consistencyLevels Comma-separated list of consistency levels that are not allowed when reading.
|
||||
*/
|
||||
void setReadConsistencyLevelsDisallowedCSV(String consistencyLevels);
|
||||
|
||||
/**
|
||||
* @return consistency levels that are warned about when writing.
|
||||
*/
|
||||
Set<ConsistencyLevel> getWriteConsistencyLevelsWarned();
|
||||
|
||||
/**
|
||||
* @return Comma-separated list of consistency levels that are warned about when writing.
|
||||
*/
|
||||
String getWriteConsistencyLevelsWarnedCSV();
|
||||
|
||||
/**
|
||||
* @param consistencyLevels consistency levels that are warned about when writing.
|
||||
*/
|
||||
void setWriteConsistencyLevelsWarned(Set<ConsistencyLevel> consistencyLevels);
|
||||
|
||||
/**
|
||||
* @param consistencyLevels Comma-separated list of consistency levels that are warned about when writing.
|
||||
*/
|
||||
void setWriteConsistencyLevelsWarnedCSV(String consistencyLevels);
|
||||
|
||||
/**
|
||||
* @return consistency levels that are not allowed when writing.
|
||||
*/
|
||||
Set<ConsistencyLevel> getWriteConsistencyLevelsDisallowed();
|
||||
|
||||
/**
|
||||
* @return Comma-separated list of consistency levels that are not allowed when writing.
|
||||
*/
|
||||
String getWriteConsistencyLevelsDisallowedCSV();
|
||||
|
||||
/**
|
||||
* @param consistencyLevels consistency levels that are not allowed when writing.
|
||||
*/
|
||||
void setWriteConsistencyLevelsDisallowed(Set<ConsistencyLevel> consistencyLevels);
|
||||
|
||||
/**
|
||||
* @param consistencyLevels Comma-separated list of consistency levels that are not allowed when writing.
|
||||
*/
|
||||
void setWriteConsistencyLevelsDisallowedCSV(String consistencyLevels);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,12 +31,14 @@ import org.apache.cassandra.service.ClientState;
|
|||
import static java.lang.String.format;
|
||||
|
||||
/**
|
||||
* A guardrail that warns about but ignores some specific values, and rejects the use of some other values.
|
||||
* A guardrail that warns about some specific values, warns about but ignores some other values, and/or rejects the use
|
||||
* of some other values.
|
||||
*
|
||||
* @param <T> The type of the values of which certain are disallowed.
|
||||
* @param <T> The type of the values of which certain are warned, ignored and/or disallowed.
|
||||
*/
|
||||
public class Values<T> extends Guardrail
|
||||
{
|
||||
private final Function<ClientState, Set<T>> warnedValues;
|
||||
private final Function<ClientState, Set<T>> ignoredValues;
|
||||
private final Function<ClientState, Set<T>> disallowedValues;
|
||||
private final String what;
|
||||
|
|
@ -44,22 +46,45 @@ public class Values<T> extends Guardrail
|
|||
/**
|
||||
* Creates a new values guardrail.
|
||||
*
|
||||
* @param ignoredValues a {@link ClientState}-based of the values that are ignored.
|
||||
* @param disallowedValues a {@link ClientState}-based of the values that are disallowed.
|
||||
* @param warnedValues a {@link ClientState}-based provider of the values for which a warning is triggered.
|
||||
* @param ignoredValues a {@link ClientState}-based provider of the values that are ignored.
|
||||
* @param disallowedValues a {@link ClientState}-based provider of the values that are disallowed.
|
||||
* @param what The feature that is guarded by this guardrail (for reporting in error messages).
|
||||
*/
|
||||
public Values(Function<ClientState, Set<T>> ignoredValues,
|
||||
public Values(Function<ClientState, Set<T>> warnedValues,
|
||||
Function<ClientState, Set<T>> ignoredValues,
|
||||
Function<ClientState, Set<T>> disallowedValues,
|
||||
String what)
|
||||
{
|
||||
this.warnedValues = warnedValues;
|
||||
this.ignoredValues = ignoredValues;
|
||||
this.disallowedValues = disallowedValues;
|
||||
this.what = what;
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers a warning for each of the provided values that are disallowed by this guardrail and triggers an action
|
||||
* to ignore them. If the values are disallowed it will abort the operation.
|
||||
* Triggers a warning for each of the provided values that is discouraged by this guardrail. If any of the values
|
||||
* is disallowed it will abort the operation.
|
||||
* <p>
|
||||
* This assumes that there aren't any values to be ignored, thus it doesn't require an ignore action. If this is
|
||||
* not the case and the provided value is set up to be ignored this will throw an assertion error.
|
||||
*
|
||||
* @param values The values to check.
|
||||
* @param state The client state, used to skip the check if the query is internal or is done by a superuser.
|
||||
* A {@code null} value means that the check should be done regardless of the query.
|
||||
*/
|
||||
public void guard(Set<T> values, @Nullable ClientState state)
|
||||
{
|
||||
guard(values, x -> {
|
||||
throw new AssertionError(format("There isn't an ignore action for %s, but value %s is setup to be ignored",
|
||||
what, x));
|
||||
}, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers a warning for each of the provided values that is discouraged by this guardrail. Also triggers a warning
|
||||
* for each of the provided values that is ignored by this guardrail and triggers the provided action to ignore it.
|
||||
* If any of the values is disallowed it will abort the operation.
|
||||
*
|
||||
* @param values The values to check.
|
||||
* @param ignoreAction An action called on the subset of {@code values} that should be ignored. This action
|
||||
|
|
@ -86,5 +111,11 @@ public class Values<T> extends Guardrail
|
|||
toIgnore.stream().sorted().collect(Collectors.toList()), what, ignored));
|
||||
toIgnore.forEach(ignoreAction);
|
||||
}
|
||||
|
||||
Set<T> warned = warnedValues.apply(state);
|
||||
Set<T> toWarn = Sets.intersection(values, warned);
|
||||
if (!toWarn.isEmpty())
|
||||
warn(format("Provided values %s are not recommended for %s (warned values are: %s)",
|
||||
toWarn.stream().sorted().collect(Collectors.toList()), what, warned));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,10 +97,12 @@ public class DatabaseDescriptorRefTest
|
|||
"org.apache.cassandra.config.EncryptionOptions$ServerEncryptionOptions$OutgoingEncryptedPortSource",
|
||||
"org.apache.cassandra.db.guardrails.GuardrailsConfig",
|
||||
"org.apache.cassandra.db.guardrails.GuardrailsConfigMBean",
|
||||
"org.apache.cassandra.db.guardrails.GuardrailsConfig$ConsistencyLevels",
|
||||
"org.apache.cassandra.db.guardrails.GuardrailsConfig$IntThreshold",
|
||||
"org.apache.cassandra.db.guardrails.GuardrailsConfig$TableProperties",
|
||||
"org.apache.cassandra.config.GuardrailsOptions",
|
||||
"org.apache.cassandra.config.GuardrailsOptions$Config",
|
||||
"org.apache.cassandra.config.GuardrailsOptions$ConsistencyLevels",
|
||||
"org.apache.cassandra.config.GuardrailsOptions$IntThreshold",
|
||||
"org.apache.cassandra.config.GuardrailsOptions$TableProperties",
|
||||
"org.apache.cassandra.config.GuardrailsOptions$Threshold",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* 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 java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.cassandra.db.ConsistencyLevel;
|
||||
|
||||
/**
|
||||
* Utilty class for testing the guardrails for read/write consistency levels.
|
||||
*/
|
||||
public abstract class GuardrailConsistencyLevelsTester extends GuardrailTester
|
||||
{
|
||||
private final String warnedPropertyName;
|
||||
private final String disallowePropertyName;
|
||||
private final Function<Guardrails, Set<ConsistencyLevel>> warnedGetter;
|
||||
private final Function<Guardrails, Set<ConsistencyLevel>> disallowedGetter;
|
||||
private final Function<Guardrails, String> warnedCSVGetter;
|
||||
private final Function<Guardrails, String> disallowedCSVGetter;
|
||||
private final BiConsumer<Guardrails, Set<ConsistencyLevel>> warnedSetter;
|
||||
private final BiConsumer<Guardrails, Set<ConsistencyLevel>> disallowedSetter;
|
||||
private final BiConsumer<Guardrails, String> warnedCSVSetter;
|
||||
private final BiConsumer<Guardrails, String> disallowedCSVSetter;
|
||||
|
||||
public GuardrailConsistencyLevelsTester(String warnedPropertyName,
|
||||
String disallowePropertyName,
|
||||
Function<Guardrails, Set<ConsistencyLevel>> warnedGetter,
|
||||
Function<Guardrails, Set<ConsistencyLevel>> disallowedGetter,
|
||||
Function<Guardrails, String> warnedCSVGetter,
|
||||
Function<Guardrails, String> disallowedCSVGetter,
|
||||
BiConsumer<Guardrails, Set<ConsistencyLevel>> warnedSetter,
|
||||
BiConsumer<Guardrails, Set<ConsistencyLevel>> disallowedSetter,
|
||||
BiConsumer<Guardrails, String> warnedCSVSetter,
|
||||
BiConsumer<Guardrails, String> disallowedCSVSetter)
|
||||
{
|
||||
this.warnedPropertyName = warnedPropertyName;
|
||||
this.disallowePropertyName = disallowePropertyName;
|
||||
this.warnedGetter = warnedGetter;
|
||||
this.disallowedGetter = disallowedGetter;
|
||||
this.warnedCSVGetter = g -> sortCSV(warnedCSVGetter.apply(g));
|
||||
this.disallowedCSVGetter = g -> sortCSV(disallowedCSVGetter.apply(g));
|
||||
this.warnedSetter = warnedSetter;
|
||||
this.disallowedSetter = disallowedSetter;
|
||||
this.warnedCSVSetter = warnedCSVSetter;
|
||||
this.disallowedCSVSetter = disallowedCSVSetter;
|
||||
}
|
||||
|
||||
@Before
|
||||
public void before()
|
||||
{
|
||||
warnConsistencyLevels();
|
||||
disableConsistencyLevels();
|
||||
}
|
||||
|
||||
protected void warnConsistencyLevels(ConsistencyLevel... consistencyLevels)
|
||||
{
|
||||
warnedSetter.accept(guardrails(), ImmutableSet.copyOf(consistencyLevels));
|
||||
}
|
||||
|
||||
protected void disableConsistencyLevels(ConsistencyLevel... consistencyLevels)
|
||||
{
|
||||
disallowedSetter.accept(guardrails(), ImmutableSet.copyOf(consistencyLevels));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConfigValidation()
|
||||
{
|
||||
String message = "Invalid value for %s: null is not allowed";
|
||||
assertInvalidProperty(warnedSetter, null, message, warnedPropertyName);
|
||||
assertInvalidProperty(disallowedSetter, null, message, disallowePropertyName);
|
||||
|
||||
assertValidProperty(Collections.emptySet());
|
||||
assertValidProperty(EnumSet.allOf(ConsistencyLevel.class));
|
||||
|
||||
assertValidPropertyCSV("");
|
||||
assertValidPropertyCSV(EnumSet.allOf(ConsistencyLevel.class)
|
||||
.stream()
|
||||
.map(ConsistencyLevel::toString)
|
||||
.collect(Collectors.joining(",")));
|
||||
|
||||
assertInvalidPropertyCSV("invalid", "INVALID");
|
||||
assertInvalidPropertyCSV("ONE,invalid1,invalid2", "INVALID1");
|
||||
assertInvalidPropertyCSV("invalid1,invalid2,ONE", "INVALID1");
|
||||
assertInvalidPropertyCSV("invalid1,ONE,invalid2", "INVALID1");
|
||||
}
|
||||
|
||||
private void assertValidProperty(Set<ConsistencyLevel> properties)
|
||||
{
|
||||
assertValidProperty(warnedSetter, warnedGetter, properties);
|
||||
assertValidProperty(disallowedSetter, disallowedGetter, properties);
|
||||
}
|
||||
|
||||
private void assertValidPropertyCSV(String csv)
|
||||
{
|
||||
csv = sortCSV(csv);
|
||||
assertValidProperty(warnedCSVSetter, warnedCSVGetter, csv);
|
||||
assertValidProperty(disallowedCSVSetter, disallowedCSVGetter, csv);
|
||||
}
|
||||
|
||||
private void assertInvalidPropertyCSV(String properties, String rejected)
|
||||
{
|
||||
String message = "No enum constant org.apache.cassandra.db.ConsistencyLevel.%s";
|
||||
assertInvalidProperty(warnedCSVSetter, properties, message, rejected);
|
||||
assertInvalidProperty(disallowedCSVSetter, properties, message, rejected);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* 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.Test;
|
||||
|
||||
import org.apache.cassandra.db.ConsistencyLevel;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import static org.apache.cassandra.db.ConsistencyLevel.ALL;
|
||||
import static org.apache.cassandra.db.ConsistencyLevel.EACH_QUORUM;
|
||||
import static org.apache.cassandra.db.ConsistencyLevel.LOCAL_ONE;
|
||||
import static org.apache.cassandra.db.ConsistencyLevel.LOCAL_QUORUM;
|
||||
import static org.apache.cassandra.db.ConsistencyLevel.ONE;
|
||||
import static org.apache.cassandra.db.ConsistencyLevel.QUORUM;
|
||||
|
||||
/**
|
||||
* Tests the guardrail for read consistency levels, {@link Guardrails#readConsistencyLevels}.
|
||||
*/
|
||||
public class GuardrailReadConsistencyLevelsTest extends GuardrailConsistencyLevelsTester
|
||||
{
|
||||
public GuardrailReadConsistencyLevelsTest()
|
||||
{
|
||||
super("read_consistency_levels_warned",
|
||||
"read_consistency_levels_disallowed",
|
||||
Guardrails::getReadConsistencyLevelsWarned,
|
||||
Guardrails::getReadConsistencyLevelsDisallowed,
|
||||
Guardrails::getReadConsistencyLevelsWarnedCSV,
|
||||
Guardrails::getReadConsistencyLevelsDisallowedCSV,
|
||||
Guardrails::setReadConsistencyLevelsWarned,
|
||||
Guardrails::setReadConsistencyLevelsDisallowed,
|
||||
Guardrails::setReadConsistencyLevelsWarnedCSV,
|
||||
Guardrails::setReadConsistencyLevelsDisallowedCSV);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelect() throws Throwable
|
||||
{
|
||||
createTable("CREATE TABLE IF NOT EXISTS %s (k INT, c INT, v INT, PRIMARY KEY(k, c))");
|
||||
|
||||
execute("INSERT INTO %s (k, c, v) VALUES (0, 0, 0)");
|
||||
execute("INSERT INTO %s (k, c, v) VALUES (0, 1, 1)");
|
||||
execute("INSERT INTO %s (k, c, v) VALUES (1, 0, 2)");
|
||||
execute("INSERT INTO %s (k, c, v) VALUES (1, 1, 3)");
|
||||
|
||||
testQuery("SELECT * FROM %s");
|
||||
testQuery("SELECT * FROM %s WHERE k = 0");
|
||||
testQuery("SELECT * FROM %s WHERE k = 0 AND c = 0");
|
||||
}
|
||||
|
||||
private void testQuery(String query) throws Throwable
|
||||
{
|
||||
testQuery(query, ONE);
|
||||
testQuery(query, ALL);
|
||||
testQuery(query, QUORUM);
|
||||
testQuery(query, EACH_QUORUM);
|
||||
testQuery(query, LOCAL_ONE);
|
||||
testQuery(query, LOCAL_QUORUM);
|
||||
}
|
||||
|
||||
private void testQuery(String query, ConsistencyLevel cl) throws Throwable
|
||||
{
|
||||
warnConsistencyLevels();
|
||||
disableConsistencyLevels();
|
||||
assertValid(query, cl);
|
||||
|
||||
warnConsistencyLevels(cl);
|
||||
assertWarns(query, cl);
|
||||
|
||||
disableConsistencyLevels(cl);
|
||||
assertFails(query, cl);
|
||||
}
|
||||
|
||||
private void assertValid(String query, ConsistencyLevel cl) throws Throwable
|
||||
{
|
||||
assertValid(() -> execute(userClientState, query, cl));
|
||||
}
|
||||
|
||||
private void assertWarns(String query, ConsistencyLevel cl) throws Throwable
|
||||
{
|
||||
assertWarns(() -> execute(userClientState, query, cl),
|
||||
format("Provided values [%s] are not recommended for read consistency levels (warned values are: %s)",
|
||||
cl, guardrails().getReadConsistencyLevelsWarned()));
|
||||
|
||||
assertExcludedUsers(query, cl);
|
||||
}
|
||||
|
||||
private void assertFails(String query, ConsistencyLevel cl) throws Throwable
|
||||
{
|
||||
assertFails(() -> execute(userClientState, query, cl),
|
||||
format("Provided values [%s] are not allowed for read consistency levels (disallowed values are: %s)",
|
||||
cl, guardrails().getReadConsistencyLevelsDisallowed()));
|
||||
|
||||
assertExcludedUsers(query, cl);
|
||||
}
|
||||
|
||||
private void assertExcludedUsers(String query, ConsistencyLevel cl) throws Throwable
|
||||
{
|
||||
assertValid(() -> execute(superClientState, query, cl));
|
||||
assertValid(() -> execute(systemClientState, query, cl));
|
||||
}
|
||||
}
|
||||
|
|
@ -45,51 +45,81 @@ public class GuardrailTablePropertiesTest extends GuardrailTester
|
|||
"WHERE pk IS NOT null and ck IS NOT null PRIMARY KEY(ck, pk) %s";
|
||||
private static final String ALTER_VIEW = "ALTER MATERIALIZED VIEW %s.%s WITH %s";
|
||||
|
||||
private static final String WARNED_PROPERTY_NAME = "table_properties_warned";
|
||||
private static final String IGNORED_PROPERTY_NAME = "table_properties_ignored";
|
||||
private static final String DISALLOWED_PROPERTY_NAME = "table_properties_disallowed";
|
||||
|
||||
@Before
|
||||
public void before()
|
||||
{
|
||||
// only allow "gc_grace_seconds" and "comments"
|
||||
Set<String> allowed = new HashSet<>(Arrays.asList("gc_grace_seconds", "comment"));
|
||||
// only allow "gc_grace_seconds", "comments" and "default_time_to_live"
|
||||
Set<String> allowed = new HashSet<>(Arrays.asList("gc_grace_seconds", "comment", "default_time_to_live"));
|
||||
guardrails().setTablePropertiesDisallowed(TableAttributes.validKeywords()
|
||||
.stream()
|
||||
.filter(p -> !allowed.contains(p))
|
||||
.map(String::toUpperCase)
|
||||
.collect(Collectors.toSet()));
|
||||
// but actually warn about "comment"
|
||||
guardrails().setTablePropertiesIgnored(Collections.singleton("comment"));
|
||||
// but actually ignore "comment" and warn about "default_time_to_live"
|
||||
guardrails().setTablePropertiesIgnored("comment");
|
||||
guardrails().setTablePropertiesWarned("default_time_to_live");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConfigValidation()
|
||||
{
|
||||
String message = "Invalid value for %s: null is not allowed";
|
||||
assertInvalidProperty(Guardrails::setTablePropertiesWarned, (Set<String>) null, message, WARNED_PROPERTY_NAME);
|
||||
assertInvalidProperty(Guardrails::setTablePropertiesIgnored, (Set<String>) null, message, IGNORED_PROPERTY_NAME);
|
||||
assertInvalidProperty(Guardrails::setTablePropertiesDisallowed, (Set<String>) null, message, DISALLOWED_PROPERTY_NAME);
|
||||
|
||||
assertValidProperty(Collections.emptySet());
|
||||
assertValidProperty(TableAttributes.allKeywords());
|
||||
|
||||
assertValidPropertyCSV("");
|
||||
assertValidPropertyCSV(String.join(",", TableAttributes.allKeywords()));
|
||||
|
||||
assertInvalidProperty(Collections.singleton("invalid"), Collections.singleton("invalid"));
|
||||
assertInvalidProperty(ImmutableSet.of("comment", "invalid1", "invalid2"), ImmutableSet.of("invalid1", "invalid2"));
|
||||
assertInvalidProperty(ImmutableSet.of("invalid1", "invalid2", "comment"), ImmutableSet.of("invalid1", "invalid2"));
|
||||
assertInvalidProperty(ImmutableSet.of("invalid1", "comment", "invalid2"), ImmutableSet.of("invalid1", "invalid2"));
|
||||
|
||||
assertInvalidPropertyCSV("invalid", "[invalid]");
|
||||
assertInvalidPropertyCSV("comment,invalid1,invalid2", "[invalid1, invalid2]");
|
||||
assertInvalidPropertyCSV("invalid1,invalid2,comment", "[invalid1, invalid2]");
|
||||
assertInvalidPropertyCSV("invalid1,comment,invalid2", "[invalid1, invalid2]");
|
||||
}
|
||||
|
||||
private void assertValidProperty(Set<String> properties)
|
||||
{
|
||||
assertValidProperty(Guardrails::setTablePropertiesIgnored, properties);
|
||||
assertValidProperty(Guardrails::setTablePropertiesDisallowed, properties);
|
||||
assertValidProperty(Guardrails::setTablePropertiesWarned, Guardrails::getTablePropertiesWarned, properties);
|
||||
assertValidProperty(Guardrails::setTablePropertiesIgnored, Guardrails::getTablePropertiesIgnored, properties);
|
||||
assertValidProperty(Guardrails::setTablePropertiesDisallowed, Guardrails::getTablePropertiesDisallowed, properties);
|
||||
}
|
||||
|
||||
private void assertValidPropertyCSV(String csv)
|
||||
{
|
||||
csv = sortCSV(csv);
|
||||
assertValidProperty(Guardrails::setTablePropertiesWarnedCSV, g -> sortCSV(g.getTablePropertiesWarnedCSV()), csv);
|
||||
assertValidProperty(Guardrails::setTablePropertiesIgnoredCSV, g -> sortCSV(g.getTablePropertiesIgnoredCSV()), csv);
|
||||
assertValidProperty(Guardrails::setTablePropertiesDisallowedCSV, g -> sortCSV(g.getTablePropertiesDisallowedCSV()), csv);
|
||||
}
|
||||
|
||||
private void assertInvalidProperty(Set<String> properties, Set<String> rejected)
|
||||
{
|
||||
String message = "Invalid value for %s: '%s' do not parse as valid table properties";
|
||||
assertInvalidProperty(Guardrails::setTablePropertiesWarned, properties, message, WARNED_PROPERTY_NAME, rejected);
|
||||
assertInvalidProperty(Guardrails::setTablePropertiesIgnored, properties, message, IGNORED_PROPERTY_NAME, rejected);
|
||||
assertInvalidProperty(Guardrails::setTablePropertiesDisallowed, properties, message, DISALLOWED_PROPERTY_NAME, rejected);
|
||||
}
|
||||
|
||||
private void assertInvalidPropertyCSV(String properties, String rejected)
|
||||
{
|
||||
String message = "Invalid value for %s: '%s' do not parse as valid table properties";
|
||||
assertInvalidProperty(Guardrails::setTablePropertiesWarnedCSV, properties, message, WARNED_PROPERTY_NAME, rejected);
|
||||
assertInvalidProperty(Guardrails::setTablePropertiesIgnoredCSV, properties, message, IGNORED_PROPERTY_NAME, rejected);
|
||||
assertInvalidProperty(Guardrails::setTablePropertiesDisallowedCSV, properties, message, DISALLOWED_PROPERTY_NAME, rejected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTableProperties() throws Throwable
|
||||
{
|
||||
|
|
@ -112,6 +142,13 @@ public class GuardrailTablePropertiesTest extends GuardrailTester
|
|||
keyspace(),
|
||||
tableName.get()).one().getString("comment"));
|
||||
|
||||
// default_time_to_live is "warned". So it should warn, and getting the default ttl on the created table should
|
||||
// not be empty, since we don't ignore it.
|
||||
assertWarns(() -> tableName.set(createTableWithProperties("with default_time_to_live = 1000")), "[default_time_to_live]");
|
||||
assertEquals(1000, executeNet("SELECT default_time_to_live FROM system_schema.tables WHERE keyspace_name=? AND table_name=?",
|
||||
keyspace(),
|
||||
tableName.get()).one().getInt("default_time_to_live"));
|
||||
|
||||
// alter column is allowed
|
||||
assertValid(this::createTableWithProperties);
|
||||
assertValid("ALTER TABLE %s ADD v1 int");
|
||||
|
|
@ -130,8 +167,6 @@ public class GuardrailTablePropertiesTest extends GuardrailTester
|
|||
|
||||
// alter mv properties except "gc_grace_seconds" is not allowed
|
||||
assertValid(() -> alterViewWithProperties("gc_grace_seconds = 1000"));
|
||||
assertFails(() -> alterViewWithProperties("compaction = { 'class': 'SizeTieredCompactionStrategy' } AND default_time_to_live = 1"),
|
||||
"[compaction, default_time_to_live]");
|
||||
assertFails(() -> alterViewWithProperties("compaction = { 'class': 'SizeTieredCompactionStrategy' } AND crc_check_chance = 1"),
|
||||
"[compaction, crc_check_chance]");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,11 +22,15 @@ import java.net.InetSocketAddress;
|
|||
import java.nio.ByteBuffer;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.TreeSet;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
|
||||
|
|
@ -37,13 +41,16 @@ import org.apache.cassandra.cql3.CQLStatement;
|
|||
import org.apache.cassandra.cql3.CQLTester;
|
||||
import org.apache.cassandra.cql3.QueryOptions;
|
||||
import org.apache.cassandra.cql3.QueryProcessor;
|
||||
import org.apache.cassandra.db.ConsistencyLevel;
|
||||
import org.apache.cassandra.db.view.View;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.index.sasi.SASIIndex;
|
||||
import org.apache.cassandra.service.ClientState;
|
||||
import org.apache.cassandra.service.ClientWarn;
|
||||
import org.apache.cassandra.service.QueryState;
|
||||
import org.apache.cassandra.transport.ProtocolVersion;
|
||||
import org.apache.cassandra.transport.messages.ResultMessage;
|
||||
import org.apache.cassandra.utils.Clock;
|
||||
import org.assertj.core.api.Assertions;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
|
@ -105,6 +112,12 @@ public abstract class GuardrailTester extends CQLTester
|
|||
setter.accept(guardrails(), value);
|
||||
}
|
||||
|
||||
protected <T> void assertValidProperty(BiConsumer<Guardrails, T> setter, Function<Guardrails, T> getter, T value)
|
||||
{
|
||||
setter.accept(guardrails(), value);
|
||||
assertEquals(value, getter.apply(guardrails()));
|
||||
}
|
||||
|
||||
protected <T> void assertInvalidProperty(BiConsumer<Guardrails, T> setter,
|
||||
T value,
|
||||
String message,
|
||||
|
|
@ -222,6 +235,23 @@ public abstract class GuardrailTester extends CQLTester
|
|||
assertFails(() -> execute(userClientState, query), messages);
|
||||
}
|
||||
|
||||
protected void assertThrows(CheckedFunction function, Class<? extends Throwable> exception, String message)
|
||||
{
|
||||
try
|
||||
{
|
||||
function.apply();
|
||||
fail("Expected to fail, but it did not");
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
if (!exception.isAssignableFrom(e.getClass()))
|
||||
Assert.fail(format("Expected to fail with %s but got %s", exception.getName(), e.getClass().getName()));
|
||||
|
||||
assertTrue(format("Error message '%s' does not contain expected message '%s'", e.getMessage(), message),
|
||||
e.getMessage().contains(message));
|
||||
}
|
||||
}
|
||||
|
||||
private void assertWarnings(String... messages)
|
||||
{
|
||||
List<String> warnings = getWarnings();
|
||||
|
|
@ -282,6 +312,32 @@ public abstract class GuardrailTester extends CQLTester
|
|||
}
|
||||
|
||||
protected ResultMessage execute(ClientState state, String query, List<ByteBuffer> values)
|
||||
{
|
||||
QueryOptions options = QueryOptions.forInternalCalls(values);
|
||||
|
||||
return execute(state, query, options);
|
||||
}
|
||||
|
||||
protected ResultMessage execute(ClientState state, String query, ConsistencyLevel cl)
|
||||
{
|
||||
return execute(state, query, cl, null);
|
||||
}
|
||||
|
||||
protected ResultMessage execute(ClientState state, String query, ConsistencyLevel cl, ConsistencyLevel serialCl)
|
||||
{
|
||||
QueryOptions options = QueryOptions.create(cl,
|
||||
Collections.emptyList(),
|
||||
false,
|
||||
10,
|
||||
null,
|
||||
serialCl,
|
||||
ProtocolVersion.CURRENT,
|
||||
KEYSPACE);
|
||||
|
||||
return execute(state, query, options);
|
||||
}
|
||||
|
||||
protected ResultMessage execute(ClientState state, String query, QueryOptions options)
|
||||
{
|
||||
QueryState queryState = new QueryState(state);
|
||||
|
||||
|
|
@ -289,8 +345,11 @@ public abstract class GuardrailTester extends CQLTester
|
|||
CQLStatement statement = QueryProcessor.parseStatement(formattedQuery, queryState.getClientState());
|
||||
statement.validate(state);
|
||||
|
||||
QueryOptions options = QueryOptions.forInternalCalls(values);
|
||||
return statement.execute(queryState, options, Clock.Global.nanoTime());
|
||||
}
|
||||
|
||||
return statement.executeLocally(queryState, options);
|
||||
protected static String sortCSV(String csv)
|
||||
{
|
||||
return String.join(",", (new TreeSet<>(ImmutableSet.copyOf((csv.split(","))))));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,208 @@
|
|||
/*
|
||||
* 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 org.apache.cassandra.db.ConsistencyLevel;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import static org.apache.cassandra.db.ConsistencyLevel.ALL;
|
||||
import static org.apache.cassandra.db.ConsistencyLevel.ANY;
|
||||
import static org.apache.cassandra.db.ConsistencyLevel.LOCAL_ONE;
|
||||
import static org.apache.cassandra.db.ConsistencyLevel.LOCAL_QUORUM;
|
||||
import static org.apache.cassandra.db.ConsistencyLevel.LOCAL_SERIAL;
|
||||
import static org.apache.cassandra.db.ConsistencyLevel.ONE;
|
||||
import static org.apache.cassandra.db.ConsistencyLevel.QUORUM;
|
||||
import static org.apache.cassandra.db.ConsistencyLevel.SERIAL;
|
||||
|
||||
/**
|
||||
* Tests the guardrail for write consistency levels, {@link Guardrails#writeConsistencyLevels}.
|
||||
*/
|
||||
public class GuardrailWriteConsistencyLevelsTest extends GuardrailConsistencyLevelsTester
|
||||
{
|
||||
public GuardrailWriteConsistencyLevelsTest()
|
||||
{
|
||||
super("write_consistency_levels_warned",
|
||||
"write_consistency_levels_disallowed",
|
||||
Guardrails::getWriteConsistencyLevelsWarned,
|
||||
Guardrails::getWriteConsistencyLevelsDisallowed,
|
||||
Guardrails::getWriteConsistencyLevelsWarnedCSV,
|
||||
Guardrails::getWriteConsistencyLevelsDisallowedCSV,
|
||||
Guardrails::setWriteConsistencyLevelsWarned,
|
||||
Guardrails::setWriteConsistencyLevelsDisallowed,
|
||||
Guardrails::setWriteConsistencyLevelsWarnedCSV,
|
||||
Guardrails::setWriteConsistencyLevelsDisallowedCSV);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void before()
|
||||
{
|
||||
super.before();
|
||||
createTable("CREATE TABLE IF NOT EXISTS %s (k INT, c INT, v TEXT, PRIMARY KEY(k, c))");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsert() throws Throwable
|
||||
{
|
||||
testQuery("INSERT INTO %s (k, c, v) VALUES (1, 2, 'val')");
|
||||
testLWTQuery("INSERT INTO %s (k, c, v) VALUES (1, 2, 'val') IF NOT EXISTS");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdate() throws Throwable
|
||||
{
|
||||
testQuery("UPDATE %s SET v = 'val2' WHERE k = 1 AND c = 2");
|
||||
testLWTQuery("UPDATE %s SET v = 'val2' WHERE k = 1 AND c = 2 IF EXISTS");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDelete() throws Throwable
|
||||
{
|
||||
testQuery("DELETE FROM %s WHERE k=1");
|
||||
testLWTQuery("DELETE FROM %s WHERE k=1 AND c=2 IF EXISTS");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBatch() throws Throwable
|
||||
{
|
||||
testQuery("BEGIN BATCH INSERT INTO %s (k, c, v) VALUES (1, 2, 'val') APPLY BATCH");
|
||||
testQuery("BEGIN BATCH UPDATE %s SET v = 'val2' WHERE k = 1 AND c = 2 APPLY BATCH");
|
||||
testQuery("BEGIN BATCH DELETE FROM %s WHERE k=1 APPLY BATCH");
|
||||
|
||||
testLWTQuery("BEGIN BATCH INSERT INTO %s (k, c, v) VALUES (1, 2, 'val') IF NOT EXISTS APPLY BATCH");
|
||||
testLWTQuery("BEGIN BATCH UPDATE %s SET v = 'val2' WHERE k = 1 AND c = 2 IF EXISTS APPLY BATCH");
|
||||
testLWTQuery("BEGIN BATCH DELETE FROM %s WHERE k=1 AND c=2 IF EXISTS APPLY BATCH");
|
||||
}
|
||||
|
||||
private void testQuery(String query) throws Throwable
|
||||
{
|
||||
testQuery(query, ONE);
|
||||
testQuery(query, ALL);
|
||||
testQuery(query, ANY);
|
||||
testQuery(query, QUORUM);
|
||||
testQuery(query, LOCAL_ONE);
|
||||
testQuery(query, LOCAL_QUORUM);
|
||||
}
|
||||
|
||||
private void testQuery(String query, ConsistencyLevel cl) throws Throwable
|
||||
{
|
||||
warnConsistencyLevels();
|
||||
disableConsistencyLevels();
|
||||
assertValid(query, cl, null);
|
||||
|
||||
warnConsistencyLevels(cl);
|
||||
assertWarns(query, cl, null, cl);
|
||||
|
||||
warnConsistencyLevels();
|
||||
disableConsistencyLevels(cl);
|
||||
assertFails(query, cl, null, cl);
|
||||
}
|
||||
|
||||
private void testLWTQuery(String query) throws Throwable
|
||||
{
|
||||
testLWTQuery(query, ONE);
|
||||
testLWTQuery(query, ALL);
|
||||
testLWTQuery(query, QUORUM);
|
||||
testLWTQuery(query, LOCAL_ONE);
|
||||
testLWTQuery(query, LOCAL_QUORUM);
|
||||
}
|
||||
|
||||
private void testLWTQuery(String query, ConsistencyLevel cl) throws Throwable
|
||||
{
|
||||
disableConsistencyLevels();
|
||||
|
||||
warnConsistencyLevels();
|
||||
assertValid(query, cl, SERIAL);
|
||||
assertValid(query, cl, LOCAL_SERIAL);
|
||||
assertValid(query, cl, null);
|
||||
|
||||
warnConsistencyLevels(cl);
|
||||
assertWarns(query, cl, SERIAL, cl);
|
||||
assertWarns(query, cl, LOCAL_SERIAL, cl);
|
||||
assertWarns(query, cl, null, cl);
|
||||
|
||||
warnConsistencyLevels(SERIAL);
|
||||
assertWarns(query, cl, SERIAL, SERIAL);
|
||||
assertValid(query, cl, LOCAL_SERIAL);
|
||||
assertWarns(query, cl, null, SERIAL);
|
||||
|
||||
warnConsistencyLevels(LOCAL_SERIAL);
|
||||
assertValid(query, cl, SERIAL);
|
||||
assertWarns(query, cl, LOCAL_SERIAL, LOCAL_SERIAL);
|
||||
assertValid(query, cl, null);
|
||||
|
||||
warnConsistencyLevels(SERIAL, LOCAL_SERIAL);
|
||||
assertWarns(query, cl, SERIAL, SERIAL);
|
||||
assertWarns(query, cl, LOCAL_SERIAL, LOCAL_SERIAL);
|
||||
assertWarns(query, cl, null, SERIAL);
|
||||
|
||||
warnConsistencyLevels();
|
||||
|
||||
disableConsistencyLevels(cl);
|
||||
assertFails(query, cl, SERIAL, cl);
|
||||
assertFails(query, cl, LOCAL_SERIAL, cl);
|
||||
assertFails(query, cl, null, cl);
|
||||
|
||||
disableConsistencyLevels(SERIAL);
|
||||
assertFails(query, cl, SERIAL, SERIAL);
|
||||
assertValid(query, cl, LOCAL_SERIAL);
|
||||
assertFails(query, cl, null, SERIAL);
|
||||
|
||||
disableConsistencyLevels(LOCAL_SERIAL);
|
||||
assertValid(query, cl, SERIAL);
|
||||
assertFails(query, cl, LOCAL_SERIAL, LOCAL_SERIAL);
|
||||
assertValid(query, cl, null);
|
||||
|
||||
disableConsistencyLevels(SERIAL, LOCAL_SERIAL);
|
||||
assertFails(query, cl, SERIAL, SERIAL);
|
||||
assertFails(query, cl, LOCAL_SERIAL, LOCAL_SERIAL);
|
||||
assertFails(query, cl, null, SERIAL);
|
||||
}
|
||||
|
||||
private void assertValid(String query, ConsistencyLevel cl, ConsistencyLevel serialCl) throws Throwable
|
||||
{
|
||||
assertValid(() -> execute(userClientState, query, cl, serialCl));
|
||||
}
|
||||
|
||||
private void assertWarns(String query, ConsistencyLevel cl, ConsistencyLevel serialCl, ConsistencyLevel warnedCl) throws Throwable
|
||||
{
|
||||
assertWarns(() -> execute(userClientState, query, cl, serialCl),
|
||||
format("Provided values [%s] are not recommended for write consistency levels (warned values are: %s)",
|
||||
warnedCl, guardrails().getWriteConsistencyLevelsWarned()));
|
||||
|
||||
assertExcludedUsers(query, cl, serialCl);
|
||||
}
|
||||
|
||||
private void assertFails(String query, ConsistencyLevel cl, ConsistencyLevel serialCl, ConsistencyLevel rejectedCl) throws Throwable
|
||||
{
|
||||
assertFails(() -> execute(userClientState, query, cl, serialCl),
|
||||
format("Provided values [%s] are not allowed for write consistency levels (disallowed values are: %s)",
|
||||
rejectedCl, guardrails().getWriteConsistencyLevelsDisallowed()));
|
||||
|
||||
assertExcludedUsers(query, cl, serialCl);
|
||||
}
|
||||
|
||||
private void assertExcludedUsers(String query, ConsistencyLevel cl, ConsistencyLevel serialCl) throws Throwable
|
||||
{
|
||||
assertValid(() -> execute(superClientState, query, cl, serialCl));
|
||||
assertValid(() -> execute(systemClientState, query, cl, serialCl));
|
||||
}
|
||||
}
|
||||
|
|
@ -18,10 +18,12 @@
|
|||
|
||||
package org.apache.cassandra.db.guardrails;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
|
|
@ -155,67 +157,30 @@ public class GuardrailsTest extends GuardrailTester
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testDisallowedValues() throws Throwable
|
||||
public void testValuesWarned() throws Throwable
|
||||
{
|
||||
// Using a sorted set below to ensure the order in the error message checked below are not random
|
||||
Values<Integer> disallowed = new Values<>(state -> Collections.emptySet(),
|
||||
state -> insertionOrderedSet(4, 6, 20),
|
||||
"integer");
|
||||
// Using a sorted set below to ensure the order in the warning message checked below is not random
|
||||
Values<Integer> warned = new Values<>(state -> insertionOrderedSet(4, 6, 20),
|
||||
state -> Collections.emptySet(),
|
||||
state -> Collections.emptySet(),
|
||||
"integer");
|
||||
|
||||
Consumer<Integer> action = i -> Assert.fail("The ignore action shouldn't have been triggered");
|
||||
assertValid(() -> disallowed.guard(set(3), action, userClientState));
|
||||
assertFails(() -> disallowed.guard(set(4), action, userClientState),
|
||||
"Provided values [4] are not allowed for integer (disallowed values are: [4, 6, 20])");
|
||||
assertValid(() -> disallowed.guard(set(10), action, userClientState));
|
||||
assertFails(() -> disallowed.guard(set(20), action, userClientState),
|
||||
"Provided values [20] are not allowed for integer (disallowed values are: [4, 6, 20])");
|
||||
assertValid(() -> disallowed.guard(set(200), action, userClientState));
|
||||
assertValid(() -> disallowed.guard(set(1, 2, 3), action, userClientState));
|
||||
|
||||
assertFails(() -> disallowed.guard(set(4, 6), action, null),
|
||||
"Provided values [4, 6] are not allowed for integer (disallowed values are: [4, 6, 20])");
|
||||
assertFails(() -> disallowed.guard(set(4, 5, 6, 7), action, null),
|
||||
"Provided values [4, 6] are not allowed for integer (disallowed values are: [4, 6, 20])");
|
||||
assertValid(() -> warned.guard(set(3), action, userClientState));
|
||||
assertWarns(() -> warned.guard(set(4), action, userClientState),
|
||||
"Provided values [4] are not recommended for integer (warned values are: [4, 6, 20])");
|
||||
assertWarns(() -> warned.guard(set(4, 6), action, null),
|
||||
"Provided values [4, 6] are not recommended for integer (warned values are: [4, 6, 20])");
|
||||
assertWarns(() -> warned.guard(set(4, 5, 6, 7), action, null),
|
||||
"Provided values [4, 6] are not recommended for integer (warned values are: [4, 6, 20])");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisallowedValuesUsers() throws Throwable
|
||||
{
|
||||
Values<Integer> disallowed = new Values<>(state -> Collections.emptySet(),
|
||||
state -> Collections.singleton(2),
|
||||
"integer");
|
||||
|
||||
Consumer<Integer> action = i -> Assert.fail("The ignore action shouldn't have been triggered");
|
||||
assertValid(() -> disallowed.guard(set(1), action, null));
|
||||
assertValid(() -> disallowed.guard(set(1), action, userClientState));
|
||||
assertValid(() -> disallowed.guard(set(1), action, systemClientState));
|
||||
assertValid(() -> disallowed.guard(set(1), action, superClientState));
|
||||
|
||||
String message = "Provided values [2] are not allowed for integer (disallowed values are: [2])";
|
||||
assertFails(() -> disallowed.guard(set(2), action, null), message);
|
||||
assertFails(() -> disallowed.guard(set(2), action, userClientState), message);
|
||||
assertValid(() -> disallowed.guard(set(2), action, systemClientState));
|
||||
assertValid(() -> disallowed.guard(set(2), action, superClientState));
|
||||
|
||||
Set<Integer> allowedValues = set(1);
|
||||
assertValid(() -> disallowed.guard(allowedValues, action, null));
|
||||
assertValid(() -> disallowed.guard(allowedValues, action, userClientState));
|
||||
assertValid(() -> disallowed.guard(allowedValues, action, systemClientState));
|
||||
assertValid(() -> disallowed.guard(allowedValues, action, superClientState));
|
||||
|
||||
Set<Integer> disallowedValues = set(2);
|
||||
message = "Provided values [2] are not allowed for integer (disallowed values are: [2])";
|
||||
assertFails(() -> disallowed.guard(disallowedValues, action, null), message);
|
||||
assertFails(() -> disallowed.guard(disallowedValues, action, userClientState), message);
|
||||
assertValid(() -> disallowed.guard(disallowedValues, action, systemClientState));
|
||||
assertValid(() -> disallowed.guard(disallowedValues, action, superClientState));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIgnoredValues() throws Throwable
|
||||
public void testValuesIgnored() throws Throwable
|
||||
{
|
||||
// Using a sorted set below to ensure the order in the error message checked below are not random
|
||||
Values<Integer> ignored = new Values<>(state -> insertionOrderedSet(4, 6, 20),
|
||||
Values<Integer> ignored = new Values<>(state -> Collections.emptySet(),
|
||||
state -> insertionOrderedSet(4, 6, 20),
|
||||
state -> Collections.emptySet(),
|
||||
"integer");
|
||||
|
||||
|
|
@ -237,6 +202,71 @@ public class GuardrailsTest extends GuardrailTester
|
|||
"Ignoring provided values [4, 6] as they are not supported for integer (ignored values are: [4, 6, 20])");
|
||||
assertEquals(set(4, 6), triggeredOn);
|
||||
triggeredOn.clear();
|
||||
|
||||
assertThrows(() -> ignored.guard(set(4), userClientState),
|
||||
AssertionError.class,
|
||||
"There isn't an ignore action for integer, but value 4 is setup to be ignored");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValuesDisallowed() throws Throwable
|
||||
{
|
||||
// Using a sorted set below to ensure the order in the error message checked below are not random
|
||||
Values<Integer> disallowed = new Values<>(state -> Collections.emptySet(),
|
||||
state -> Collections.emptySet(),
|
||||
state -> insertionOrderedSet(4, 6, 20),
|
||||
"integer");
|
||||
|
||||
Consumer<Integer> action = i -> Assert.fail("The ignore action shouldn't have been triggered");
|
||||
assertValid(() -> disallowed.guard(set(3), action, userClientState));
|
||||
assertFails(() -> disallowed.guard(set(4), action, userClientState),
|
||||
"Provided values [4] are not allowed for integer (disallowed values are: [4, 6, 20])");
|
||||
assertValid(() -> disallowed.guard(set(10), action, userClientState));
|
||||
assertFails(() -> disallowed.guard(set(20), action, userClientState),
|
||||
"Provided values [20] are not allowed for integer (disallowed values are: [4, 6, 20])");
|
||||
assertValid(() -> disallowed.guard(set(200), action, userClientState));
|
||||
assertValid(() -> disallowed.guard(set(1, 2, 3), action, userClientState));
|
||||
|
||||
assertFails(() -> disallowed.guard(set(4, 6), action, null),
|
||||
"Provided values [4, 6] are not allowed for integer (disallowed values are: [4, 6, 20])");
|
||||
assertFails(() -> disallowed.guard(set(4, 5, 6, 7), action, null),
|
||||
"Provided values [4, 6] are not allowed for integer (disallowed values are: [4, 6, 20])");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValuesUsers() throws Throwable
|
||||
{
|
||||
Values<Integer> disallowed = new Values<>(state -> Collections.singleton(2),
|
||||
state -> Collections.singleton(3),
|
||||
state -> Collections.singleton(4),
|
||||
"integer");
|
||||
|
||||
Consumer<Integer> action = i -> Assert.fail("The ignore action shouldn't have been triggered");
|
||||
|
||||
assertValid(() -> disallowed.guard(set(1), action, null));
|
||||
assertValid(() -> disallowed.guard(set(1), action, userClientState));
|
||||
assertValid(() -> disallowed.guard(set(1), action, systemClientState));
|
||||
assertValid(() -> disallowed.guard(set(1), action, superClientState));
|
||||
|
||||
String message = "Provided values [2] are not recommended for integer (warned values are: [2])";
|
||||
assertWarns(() -> disallowed.guard(set(2), action, null), message);
|
||||
assertWarns(() -> disallowed.guard(set(2), action, userClientState), message);
|
||||
assertValid(() -> disallowed.guard(set(2), action, systemClientState));
|
||||
assertValid(() -> disallowed.guard(set(2), action, superClientState));
|
||||
|
||||
message = "Ignoring provided values [3] as they are not supported for integer (ignored values are: [3])";
|
||||
List<Integer> triggeredOn = new ArrayList<>();
|
||||
assertWarns(() -> disallowed.guard(set(3), triggeredOn::add, null), message);
|
||||
assertWarns(() -> disallowed.guard(set(3), triggeredOn::add, userClientState), message);
|
||||
assertValid(() -> disallowed.guard(set(3), triggeredOn::add, systemClientState));
|
||||
assertValid(() -> disallowed.guard(set(3), triggeredOn::add, superClientState));
|
||||
Assert.assertEquals(list(3, 3), triggeredOn);
|
||||
|
||||
message = "Provided values [4] are not allowed for integer (disallowed values are: [4])";
|
||||
assertFails(() -> disallowed.guard(set(4), action, null), message);
|
||||
assertFails(() -> disallowed.guard(set(4), action, userClientState), message);
|
||||
assertValid(() -> disallowed.guard(set(4), action, systemClientState));
|
||||
assertValid(() -> disallowed.guard(set(4), action, superClientState));
|
||||
}
|
||||
|
||||
private static Set<Integer> set(Integer value)
|
||||
|
|
|
|||
Loading…
Reference in New Issue