mirror of https://github.com/apache/cassandra
Add Timestamp Bound Guardrail (bound user supplied timestamps within a certain range)
Patch by Jordan West; Reviewed by Andrés de la Peña and Brandon Williams for CASSANDRA-18352
This commit is contained in:
parent
9ba27f398b
commit
2ff1ad4788
|
|
@ -1,4 +1,5 @@
|
|||
5.0
|
||||
* Add guardrail to bound timestamps (CASSANDRA-18352)
|
||||
* Add keyspace_name column to system_views.clients (CASSANDRA-18525)
|
||||
* Moved system properties and envs to CassandraRelevantProperties and CassandraRelevantEnv respectively (CASSANDRA-17797)
|
||||
* Add sstablepartitions offline tool to find large partitions in sstables (CASSANDRA-8720)
|
||||
|
|
|
|||
|
|
@ -1767,6 +1767,13 @@ drop_compact_storage_enabled: false
|
|||
# Guardrail to allow/disallow user-provided timestamps. Defaults to true.
|
||||
# user_timestamps_enabled: true
|
||||
#
|
||||
# Guardrail to bound user-provided timestamps within a given range. Default is infinite (denoted by null).
|
||||
# Accepted values are durations of the form 12h, 24h, etc.
|
||||
# maximum_timestamp_warn_threshold:
|
||||
# maximum_timestamp_fail_threshold:
|
||||
# minimum_timestamp_warn_threshold:
|
||||
# minimum_timestamp_fail_threshold:
|
||||
#
|
||||
# Guardrail to allow/disallow GROUP BY functionality.
|
||||
# group_by_enabled: true
|
||||
#
|
||||
|
|
|
|||
|
|
@ -904,6 +904,12 @@ public class Config
|
|||
public volatile DurationSpec.LongNanosecondsBound repair_state_expires = new DurationSpec.LongNanosecondsBound("3d");
|
||||
public volatile int repair_state_size = 100_000;
|
||||
|
||||
/** The configuration of timestamp bounds */
|
||||
public volatile DurationSpec.LongMicrosecondsBound maximum_timestamp_warn_threshold = null;
|
||||
public volatile DurationSpec.LongMicrosecondsBound maximum_timestamp_fail_threshold = null;
|
||||
public volatile DurationSpec.LongMicrosecondsBound minimum_timestamp_warn_threshold = null;
|
||||
public volatile DurationSpec.LongMicrosecondsBound minimum_timestamp_fail_threshold = null;
|
||||
|
||||
/**
|
||||
* The variants of paxos implementation and semantics supported by Cassandra.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -272,6 +272,64 @@ public abstract class DurationSpec
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a duration used for Cassandra configuration. The bound is [0, Long.MAX_VALUE) in microseconds.
|
||||
* If the user sets a different unit - we still validate that converted to microseconds the quantity will not exceed
|
||||
* that upper bound. (CASSANDRA-17571)
|
||||
*/
|
||||
public final static class LongMicrosecondsBound extends DurationSpec
|
||||
{
|
||||
/**
|
||||
* Creates a {@code DurationSpec.LongMicrosecondsBound} of the specified amount.
|
||||
* The bound is [0, Long.MAX_VALUE) in microseconds.
|
||||
*
|
||||
* @param value the duration
|
||||
*/
|
||||
public LongMicrosecondsBound(String value)
|
||||
{
|
||||
super(value, MICROSECONDS, Long.MAX_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code DurationSpec.LongMicrosecondsBound} of the specified amount in the specified unit.
|
||||
* The bound is [0, Long.MAX_VALUE) in milliseconds.
|
||||
*
|
||||
* @param quantity where quantity shouldn't be bigger than Long.MAX_VALUE - 1 in microseconds
|
||||
* @param unit in which the provided quantity is
|
||||
*/
|
||||
public LongMicrosecondsBound(long quantity, TimeUnit unit)
|
||||
{
|
||||
super(quantity, unit, MICROSECONDS, Long.MAX_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code DurationSpec.LongMicrosecondsBound} of the specified amount in microseconds.
|
||||
* The bound is [0, Long.MAX_VALUE) in microseconds.
|
||||
*
|
||||
* @param microseconds where milliseconds shouldn't be bigger than Long.MAX_VALUE-1
|
||||
*/
|
||||
public LongMicrosecondsBound(long microseconds)
|
||||
{
|
||||
this(microseconds, MICROSECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return this duration in number of milliseconds
|
||||
*/
|
||||
public long toMicroseconds()
|
||||
{
|
||||
return unit().toMicros(quantity());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return this duration in number of seconds
|
||||
*/
|
||||
public long toSeconds()
|
||||
{
|
||||
return unit().toSeconds(quantity());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a duration used for Cassandra configuration. The bound is [0, Long.MAX_VALUE) in milliseconds.
|
||||
* If the user sets a different unit - we still validate that converted to milliseconds the quantity will not exceed
|
||||
|
|
|
|||
|
|
@ -84,6 +84,8 @@ public class GuardrailsOptions implements GuardrailsConfig
|
|||
validateDataDiskUsageMaxDiskSize(config.data_disk_usage_max_disk_size);
|
||||
validateMinRFThreshold(config.minimum_replication_factor_warn_threshold, config.minimum_replication_factor_fail_threshold);
|
||||
validateMaxRFThreshold(config.maximum_replication_factor_warn_threshold, config.maximum_replication_factor_fail_threshold);
|
||||
validateTimestampThreshold(config.maximum_timestamp_warn_threshold, config.maximum_timestamp_fail_threshold, "maximum_timestamp");
|
||||
validateTimestampThreshold(config.minimum_timestamp_warn_threshold, config.minimum_timestamp_fail_threshold, "minimum_timestamp");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -760,6 +762,64 @@ public class GuardrailsOptions implements GuardrailsConfig
|
|||
x -> config.zero_ttl_on_twcs_enabled = x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DurationSpec.LongMicrosecondsBound getMaximumTimestampWarnThreshold()
|
||||
{
|
||||
return config.maximum_timestamp_warn_threshold;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DurationSpec.LongMicrosecondsBound getMaximumTimestampFailThreshold()
|
||||
{
|
||||
return config.maximum_timestamp_fail_threshold;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaximumTimestampThreshold(@Nullable DurationSpec.LongMicrosecondsBound warn,
|
||||
@Nullable DurationSpec.LongMicrosecondsBound fail)
|
||||
{
|
||||
validateTimestampThreshold(warn, fail, "maximum_timestamp");
|
||||
|
||||
updatePropertyWithLogging("maximum_timestamp_warn_threshold",
|
||||
warn,
|
||||
() -> config.maximum_timestamp_warn_threshold,
|
||||
x -> config.maximum_timestamp_warn_threshold = x);
|
||||
|
||||
updatePropertyWithLogging("maximum_timestamp_fail_threshold",
|
||||
fail,
|
||||
() -> config.maximum_timestamp_fail_threshold,
|
||||
x -> config.maximum_timestamp_fail_threshold = x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DurationSpec.LongMicrosecondsBound getMinimumTimestampWarnThreshold()
|
||||
{
|
||||
return config.minimum_timestamp_warn_threshold;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DurationSpec.LongMicrosecondsBound getMinimumTimestampFailThreshold()
|
||||
{
|
||||
return config.minimum_timestamp_fail_threshold;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMinimumTimestampThreshold(@Nullable DurationSpec.LongMicrosecondsBound warn,
|
||||
@Nullable DurationSpec.LongMicrosecondsBound fail)
|
||||
{
|
||||
validateTimestampThreshold(warn, fail, "minimum_timestamp");
|
||||
|
||||
updatePropertyWithLogging("minimum_timestamp_warn_threshold",
|
||||
warn,
|
||||
() -> config.minimum_timestamp_warn_threshold,
|
||||
x -> config.minimum_timestamp_warn_threshold = x);
|
||||
|
||||
updatePropertyWithLogging("minimum_timestamp_fail_threshold",
|
||||
fail,
|
||||
() -> config.minimum_timestamp_fail_threshold,
|
||||
x -> config.minimum_timestamp_fail_threshold = x);
|
||||
}
|
||||
|
||||
private static <T> void updatePropertyWithLogging(String propertyName, T newValue, Supplier<T> getter, Consumer<T> setter)
|
||||
{
|
||||
T oldValue = getter.get();
|
||||
|
|
@ -771,6 +831,11 @@ public class GuardrailsOptions implements GuardrailsConfig
|
|||
}
|
||||
|
||||
private static void validatePositiveNumeric(long value, long maxValue, String name)
|
||||
{
|
||||
validatePositiveNumeric(value, maxValue, name, false);
|
||||
}
|
||||
|
||||
private static void validatePositiveNumeric(long value, long maxValue, String name, boolean allowZero)
|
||||
{
|
||||
if (value == -1)
|
||||
return;
|
||||
|
|
@ -779,12 +844,12 @@ public class GuardrailsOptions implements GuardrailsConfig
|
|||
throw new IllegalArgumentException(format("Invalid value %d for %s: maximum allowed value is %d",
|
||||
value, name, maxValue));
|
||||
|
||||
if (value == 0)
|
||||
if (!allowZero && value == 0)
|
||||
throw new IllegalArgumentException(format("Invalid value for %s: 0 is not allowed; " +
|
||||
"if attempting to disable use -1", name));
|
||||
|
||||
// We allow -1 as a general "disabling" flag. But reject anything lower to avoid mistakes.
|
||||
if (value <= 0)
|
||||
if (value < 0)
|
||||
throw new IllegalArgumentException(format("Invalid value %d for %s: negative values are not allowed, " +
|
||||
"outside of -1 which disables the guardrail", value, name));
|
||||
}
|
||||
|
|
@ -808,6 +873,13 @@ public class GuardrailsOptions implements GuardrailsConfig
|
|||
validateWarnLowerThanFail(warn, fail, name);
|
||||
}
|
||||
|
||||
private static void validateMaxLongThreshold(long warn, long fail, String name, boolean allowZero)
|
||||
{
|
||||
validatePositiveNumeric(warn, Long.MAX_VALUE, name + "_warn_threshold", allowZero);
|
||||
validatePositiveNumeric(fail, Long.MAX_VALUE, name + "_fail_threshold", allowZero);
|
||||
validateWarnLowerThanFail(warn, fail, name);
|
||||
}
|
||||
|
||||
private static void validateMinIntThreshold(int warn, int fail, String name)
|
||||
{
|
||||
validatePositiveNumeric(warn, Integer.MAX_VALUE, name + "_warn_threshold");
|
||||
|
|
@ -835,6 +907,18 @@ public class GuardrailsOptions implements GuardrailsConfig
|
|||
fail, DatabaseDescriptor.getDefaultKeyspaceRF()));
|
||||
}
|
||||
|
||||
public static void validateTimestampThreshold(DurationSpec.LongMicrosecondsBound warn,
|
||||
DurationSpec.LongMicrosecondsBound fail,
|
||||
String name)
|
||||
{
|
||||
// this function is used for both upper and lower thresholds because lower threshold is relative
|
||||
// despite using MinThreshold we still want the warn threshold to be less than or equal to
|
||||
// the fail threshold.
|
||||
validateMaxLongThreshold(warn == null ? -1 : warn.toMicroseconds(),
|
||||
fail == null ? -1 : fail.toMicroseconds(),
|
||||
name, true);
|
||||
}
|
||||
|
||||
private static void validateWarnLowerThanFail(long warn, long fail, String name)
|
||||
{
|
||||
if (warn == -1 || fail == -1)
|
||||
|
|
|
|||
|
|
@ -302,6 +302,16 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa
|
|||
}
|
||||
}
|
||||
|
||||
public void validateTimestamp(QueryState queryState, QueryOptions options)
|
||||
{
|
||||
if (!isTimestampSet())
|
||||
return;
|
||||
|
||||
long ts = attrs.getTimestamp(options.getTimestamp(queryState), options);
|
||||
Guardrails.maximumAllowableTimestamp.guard(ts, table(), false, queryState.getClientState());
|
||||
Guardrails.minimumAllowableTimestamp.guard(ts, table(), false, queryState.getClientState());
|
||||
}
|
||||
|
||||
public RegularAndStaticColumns updatedColumns()
|
||||
{
|
||||
return updatedColumns;
|
||||
|
|
@ -506,6 +516,7 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa
|
|||
cl.validateForWrite();
|
||||
|
||||
validateDiskUsage(options, queryState.getClientState());
|
||||
validateTimestamp(queryState, options);
|
||||
|
||||
List<? extends IMutation> mutations =
|
||||
getMutations(queryState.getClientState(),
|
||||
|
|
|
|||
|
|
@ -31,10 +31,12 @@ import org.apache.commons.lang3.StringUtils;
|
|||
import org.apache.cassandra.config.CassandraRelevantProperties;
|
||||
import org.apache.cassandra.config.DataStorageSpec;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.config.DurationSpec;
|
||||
import org.apache.cassandra.config.GuardrailsOptions;
|
||||
import org.apache.cassandra.db.ConsistencyLevel;
|
||||
import org.apache.cassandra.db.compaction.TimeWindowCompactionStrategy;
|
||||
import org.apache.cassandra.locator.InetAddressAndPort;
|
||||
import org.apache.cassandra.service.ClientState;
|
||||
import org.apache.cassandra.service.disk.usage.DiskUsageBroadcaster;
|
||||
import org.apache.cassandra.utils.MBeanWrapper;
|
||||
|
||||
|
|
@ -421,6 +423,24 @@ public final class Guardrails implements GuardrailsMBean
|
|||
format("The keyspace %s has a replication factor of %s, above the %s threshold of %s.",
|
||||
what, value, isWarning ? "warning" : "failure", threshold));
|
||||
|
||||
public static final MaxThreshold maximumAllowableTimestamp =
|
||||
new MaxThreshold("maximum_timestamp",
|
||||
"Timestamps too far in the future can lead to data that can't be easily overwritten",
|
||||
state -> maximumTimestampAsRelativeMicros(CONFIG_PROVIDER.getOrCreate(state).getMaximumTimestampWarnThreshold()),
|
||||
state -> maximumTimestampAsRelativeMicros(CONFIG_PROVIDER.getOrCreate(state).getMaximumTimestampFailThreshold()),
|
||||
(isWarning, what, value, threshold) ->
|
||||
format("The modification to table %s has a timestamp %s after the maximum allowable %s threshold %s",
|
||||
what, value, isWarning ? "warning" : "failure", threshold));
|
||||
|
||||
public static final MinThreshold minimumAllowableTimestamp =
|
||||
new MinThreshold("minimum_timestamp",
|
||||
"Timestamps too far in the past can cause writes can be unexpectedly lost",
|
||||
state -> minimumTimestampAsRelativeMicros(CONFIG_PROVIDER.getOrCreate(state).getMinimumTimestampWarnThreshold()),
|
||||
state -> minimumTimestampAsRelativeMicros(CONFIG_PROVIDER.getOrCreate(state).getMinimumTimestampFailThreshold()),
|
||||
(isWarning, what, value, threshold) ->
|
||||
format("The modification to table %s has a timestamp %s before the minimum allowable %s threshold %s",
|
||||
what, value, isWarning ? "warning" : "failure", threshold));
|
||||
|
||||
private Guardrails()
|
||||
{
|
||||
MBeanWrapper.instance.registerMBean(this, MBEAN_NAME);
|
||||
|
|
@ -1052,6 +1072,42 @@ public final class Guardrails implements GuardrailsMBean
|
|||
DEFAULT_CONFIG.setZeroTTLOnTWCSWarned(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMaximumTimestampWarnThreshold()
|
||||
{
|
||||
return durationToString(DEFAULT_CONFIG.getMaximumTimestampWarnThreshold());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMaximumTimestampFailThreshold()
|
||||
{
|
||||
return durationToString(DEFAULT_CONFIG.getMaximumTimestampFailThreshold());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaximumTimestampThreshold(String warnSeconds, String failSeconds)
|
||||
{
|
||||
DEFAULT_CONFIG.setMaximumTimestampThreshold(durationFromString(warnSeconds), durationFromString(failSeconds));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMinimumTimestampWarnThreshold()
|
||||
{
|
||||
return durationToString(DEFAULT_CONFIG.getMinimumTimestampWarnThreshold());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMinimumTimestampFailThreshold()
|
||||
{
|
||||
return durationToString(DEFAULT_CONFIG.getMinimumTimestampFailThreshold());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMinimumTimestampThreshold(String warnSeconds, String failSeconds)
|
||||
{
|
||||
DEFAULT_CONFIG.setMinimumTimestampThreshold(durationFromString(warnSeconds), durationFromString(failSeconds));
|
||||
}
|
||||
|
||||
private static String toCSV(Set<String> values)
|
||||
{
|
||||
return values == null || values.isEmpty() ? "" : String.join(",", values);
|
||||
|
|
@ -1100,4 +1156,28 @@ public final class Guardrails implements GuardrailsMBean
|
|||
{
|
||||
return StringUtils.isEmpty(size) ? null : new DataStorageSpec.LongBytesBound(size);
|
||||
}
|
||||
|
||||
private static String durationToString(@Nullable DurationSpec duration)
|
||||
{
|
||||
return duration == null ? null : duration.toString();
|
||||
}
|
||||
|
||||
private static DurationSpec.LongMicrosecondsBound durationFromString(@Nullable String duration)
|
||||
{
|
||||
return StringUtils.isEmpty(duration) ? null : new DurationSpec.LongMicrosecondsBound(duration);
|
||||
}
|
||||
|
||||
private static long maximumTimestampAsRelativeMicros(@Nullable DurationSpec.LongMicrosecondsBound duration)
|
||||
{
|
||||
return duration == null
|
||||
? Long.MAX_VALUE
|
||||
: (ClientState.getLastTimestampMicros() + duration.toMicroseconds());
|
||||
}
|
||||
|
||||
private static long minimumTimestampAsRelativeMicros(@Nullable DurationSpec.LongMicrosecondsBound duration)
|
||||
{
|
||||
return duration == null
|
||||
? Long.MIN_VALUE
|
||||
: (ClientState.getLastTimestampMicros() - duration.toMicroseconds());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import java.util.Set;
|
|||
import javax.annotation.Nullable;
|
||||
|
||||
import org.apache.cassandra.config.DataStorageSpec;
|
||||
import org.apache.cassandra.config.DurationSpec;
|
||||
import org.apache.cassandra.db.ConsistencyLevel;
|
||||
|
||||
/**
|
||||
|
|
@ -350,4 +351,50 @@ public interface GuardrailsConfig
|
|||
* @param value {@code true} if 0 default TTL on TWCS tables is allowed, {@code false} otherwise.
|
||||
*/
|
||||
void setZeroTTLOnTWCSEnabled(boolean value);
|
||||
|
||||
/**
|
||||
* @return A timestamp that if a user supplied timestamp is after will trigger a warning
|
||||
*/
|
||||
@Nullable
|
||||
DurationSpec.LongMicrosecondsBound getMaximumTimestampWarnThreshold();
|
||||
|
||||
/**
|
||||
* @return A timestamp that if a user supplied timestamp is after will cause a failure
|
||||
*/
|
||||
@Nullable
|
||||
DurationSpec.LongMicrosecondsBound getMaximumTimestampFailThreshold();
|
||||
|
||||
/**
|
||||
* Sets the warning upper bound for user supplied timestamps
|
||||
*
|
||||
* @param warn The highest acceptable difference between now and the written value timestamp before triggering a
|
||||
* warning. {@code null} means disabled.
|
||||
* @param fail The highest acceptable difference between now and the written value timestamp before triggering a
|
||||
* failure. {@code null} means disabled.
|
||||
*/
|
||||
void setMaximumTimestampThreshold(@Nullable DurationSpec.LongMicrosecondsBound warn,
|
||||
@Nullable DurationSpec.LongMicrosecondsBound fail);
|
||||
|
||||
/**
|
||||
* @return A timestamp that if a user supplied timestamp is before will trigger a warning
|
||||
*/
|
||||
@Nullable
|
||||
DurationSpec.LongMicrosecondsBound getMinimumTimestampWarnThreshold();
|
||||
|
||||
/**
|
||||
* @return A timestamp that if a user supplied timestamp is after will trigger a warning
|
||||
*/
|
||||
@Nullable
|
||||
DurationSpec.LongMicrosecondsBound getMinimumTimestampFailThreshold();
|
||||
|
||||
/**
|
||||
* Sets the warning lower bound for user supplied timestamps
|
||||
*
|
||||
* @param warn The lowest acceptable difference between now and the written value timestamp before triggering a
|
||||
* warning. {@code null} means disabled.
|
||||
* @param fail The lowest acceptable difference between now and the written value timestamp before triggering a
|
||||
* failure. {@code null} means disabled.
|
||||
*/
|
||||
void setMinimumTimestampThreshold(@Nullable DurationSpec.LongMicrosecondsBound warn,
|
||||
@Nullable DurationSpec.LongMicrosecondsBound fail);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -651,4 +651,60 @@ public interface GuardrailsMBean
|
|||
* @param value {@code true} if 0 default TTL on TWCS tables is allowed, {@code false} otherwise.
|
||||
*/
|
||||
void setZeroTTLOnTWCSEnabled(boolean value);
|
||||
|
||||
/**
|
||||
* @return The highest acceptable difference between now and the written value timestamp before triggering a warning.
|
||||
* Expressed as a string formatted as in, for example, {@code 10s} {@code 20m}, {@code 30h} or {@code 40d}.
|
||||
* A {@code null} value means disabled.
|
||||
*/
|
||||
@Nullable
|
||||
String getMaximumTimestampWarnThreshold();
|
||||
|
||||
/**
|
||||
* @return The highest acceptable difference between now and the written value timestamp before triggering a failure.
|
||||
* Expressed as a string formatted as in, for example, {@code 10s} {@code 20m}, {@code 30h} or {@code 40d}.
|
||||
* A {@code null} value means disabled.
|
||||
*/
|
||||
@Nullable
|
||||
String getMaximumTimestampFailThreshold();
|
||||
|
||||
/**
|
||||
* Sets the warning upper bound for user supplied timestamps.
|
||||
*
|
||||
* @param warnDuration The highest acceptable difference between now and the written value timestamp before
|
||||
* triggering a warning. Expressed as a string formatted as in, for example, {@code 10s},
|
||||
* {@code 20m}, {@code 30h} or {@code 40d}. A {@code null} value means disabled.
|
||||
* @param failDuration The highest acceptable difference between now and the written value timestamp before
|
||||
* triggering a failure. Expressed as a string formatted as in, for example, {@code 10s},
|
||||
* {@code 20m}, {@code 30h} or {@code 40d}. A {@code null} value means disabled.
|
||||
*/
|
||||
void setMaximumTimestampThreshold(@Nullable String warnDuration, @Nullable String failDuration);
|
||||
|
||||
/**
|
||||
* @return The lowest acceptable difference between now and the written value timestamp before triggering a warning.
|
||||
* Expressed as a string formatted as in, for example, {@code 10s} {@code 20m}, {@code 30h} or {@code 40d}.
|
||||
* A {@code null} value means disabled.
|
||||
*/
|
||||
@Nullable
|
||||
String getMinimumTimestampWarnThreshold();
|
||||
|
||||
/**
|
||||
* @return The lowest acceptable difference between now and the written value timestamp before triggering a failure.
|
||||
* Expressed as a string formatted as in, for example, {@code 10s} {@code 20m}, {@code 30h} or {@code 40d}.
|
||||
* A {@code null} value means disabled.
|
||||
*/
|
||||
@Nullable
|
||||
String getMinimumTimestampFailThreshold();
|
||||
|
||||
/**
|
||||
* Sets the warning lower bound for user supplied timestamps.
|
||||
*
|
||||
* @param warnDuration The lowest acceptable difference between now and the written value timestamp before
|
||||
* triggering a warning. Expressed as a string formatted as in, for example, {@code 10s},
|
||||
* {@code 20m}, {@code 30h} or {@code 40d}. A {@code null} value means disabled.
|
||||
* @param failDuration The lowest acceptable difference between now and the written value timestamp before
|
||||
* triggering a failure. Expressed as a string formatted as in, for example, {@code 10s},
|
||||
* {@code 20m}, {@code 30h} or {@code 40d}. A {@code null} value means disabled.
|
||||
*/
|
||||
void setMinimumTimestampThreshold(@Nullable String warnDuration, @Nullable String failDuration);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -120,6 +120,7 @@ public class DatabaseDescriptorRefTest
|
|||
"org.apache.cassandra.config.DurationSpec$IntMinutesBound",
|
||||
"org.apache.cassandra.config.DurationSpec$IntSecondsBound",
|
||||
"org.apache.cassandra.config.DurationSpec$LongMillisecondsBound",
|
||||
"org.apache.cassandra.config.DurationSpec$LongMicrosecondsBound",
|
||||
"org.apache.cassandra.config.DurationSpec$LongNanosecondsBound",
|
||||
"org.apache.cassandra.config.DurationSpec$LongSecondsBound",
|
||||
"org.apache.cassandra.config.EncryptionOptions",
|
||||
|
|
|
|||
|
|
@ -213,6 +213,7 @@ public class DurationSpecTest
|
|||
assertEquals(new DurationSpec.IntSecondsBound("10s"), DurationSpec.IntSecondsBound.inSecondsString("10"));
|
||||
assertEquals(new DurationSpec.IntSecondsBound("10s"), DurationSpec.IntSecondsBound.inSecondsString("10s"));
|
||||
|
||||
assertEquals(10L, new DurationSpec.LongMicrosecondsBound("10us").toMicroseconds());
|
||||
assertEquals(10L, new DurationSpec.LongMillisecondsBound("10ms").toMilliseconds());
|
||||
assertEquals(10L, new DurationSpec.LongSecondsBound("10s").toSeconds());
|
||||
}
|
||||
|
|
@ -284,6 +285,11 @@ public class DurationSpecTest
|
|||
assertThatThrownBy(() -> new DurationSpec.IntMinutesBound("-10s")).isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessageContaining("Invalid duration: -10s");
|
||||
|
||||
assertThatThrownBy(() -> new DurationSpec.LongMicrosecondsBound("10ns")).isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessageContaining("Invalid duration: 10ns Accepted units");
|
||||
assertThatThrownBy(() -> new DurationSpec.LongMicrosecondsBound(10, NANOSECONDS)).isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessageContaining("Invalid duration: 10 NANOSECONDS Accepted units");
|
||||
|
||||
assertThatThrownBy(() -> new DurationSpec.LongMillisecondsBound("10ns")).isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessageContaining("Invalid duration: 10ns Accepted units");
|
||||
assertThatThrownBy(() -> new DurationSpec.LongMillisecondsBound(10, NANOSECONDS)).isInstanceOf(IllegalArgumentException.class)
|
||||
|
|
|
|||
|
|
@ -28,12 +28,14 @@ import com.google.common.collect.ImmutableSet;
|
|||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.cassandra.config.DataStorageSpec;
|
||||
import org.apache.cassandra.db.marshal.BytesType;
|
||||
import org.apache.cassandra.db.marshal.ListType;
|
||||
import org.apache.cassandra.db.marshal.MapType;
|
||||
import org.apache.cassandra.db.marshal.SetType;
|
||||
|
||||
import static java.nio.ByteBuffer.allocate;
|
||||
import static org.apache.cassandra.config.DataStorageSpec.DataStorageUnit.BYTES;
|
||||
|
||||
/**
|
||||
* Tests the guardrail for the size of collections, {@link Guardrails#collectionSize}.
|
||||
|
|
@ -53,7 +55,9 @@ public class GuardrailCollectionSizeTest extends ThresholdTester
|
|||
Guardrails.collectionSize,
|
||||
Guardrails::setCollectionSizeThreshold,
|
||||
Guardrails::getCollectionSizeWarnThreshold,
|
||||
Guardrails::getCollectionSizeFailThreshold);
|
||||
Guardrails::getCollectionSizeFailThreshold,
|
||||
bytes -> new DataStorageSpec.LongBytesBound(bytes, BYTES).toString(),
|
||||
size -> new DataStorageSpec.LongBytesBound(size).toBytes());
|
||||
}
|
||||
|
||||
@After
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import com.google.common.collect.ImmutableList;
|
|||
import com.google.common.collect.ImmutableSet;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.cassandra.config.DataStorageSpec;
|
||||
import org.apache.cassandra.db.marshal.BytesType;
|
||||
import org.apache.cassandra.db.marshal.ListType;
|
||||
import org.apache.cassandra.db.marshal.MapType;
|
||||
|
|
@ -35,6 +36,7 @@ import org.apache.cassandra.db.marshal.SetType;
|
|||
|
||||
import static java.lang.String.format;
|
||||
import static java.nio.ByteBuffer.allocate;
|
||||
import static org.apache.cassandra.config.DataStorageSpec.DataStorageUnit.BYTES;
|
||||
|
||||
/**
|
||||
* Tests the guardrail for the size of column values, {@link Guardrails#columnValueSize}.
|
||||
|
|
@ -51,7 +53,9 @@ public class GuardrailColumnValueSizeTest extends ThresholdTester
|
|||
Guardrails.columnValueSize,
|
||||
Guardrails::setColumnValueSizeThreshold,
|
||||
Guardrails::getColumnValueSizeWarnThreshold,
|
||||
Guardrails::getColumnValueSizeFailThreshold);
|
||||
Guardrails::getColumnValueSizeFailThreshold,
|
||||
bytes -> new DataStorageSpec.LongBytesBound(bytes, BYTES).toString(),
|
||||
size -> new DataStorageSpec.LongBytesBound(size).toBytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* 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.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.cassandra.config.DurationSpec;
|
||||
import org.apache.cassandra.service.ClientState;
|
||||
|
||||
public class GuardrailMaximumTimestampTest extends ThresholdTester
|
||||
{
|
||||
public GuardrailMaximumTimestampTest()
|
||||
{
|
||||
super(TimeUnit.DAYS.toSeconds(1) + "s",
|
||||
TimeUnit.DAYS.toSeconds(2) + "s",
|
||||
Guardrails.maximumAllowableTimestamp,
|
||||
Guardrails::setMaximumTimestampThreshold,
|
||||
Guardrails::getMaximumTimestampWarnThreshold,
|
||||
Guardrails::getMaximumTimestampFailThreshold,
|
||||
micros -> new DurationSpec.LongMicrosecondsBound(micros, TimeUnit.MICROSECONDS).toString(),
|
||||
micros -> new DurationSpec.LongMicrosecondsBound(micros).toMicroseconds());
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setupTest()
|
||||
{
|
||||
createTable("CREATE TABLE IF NOT EXISTS %s (k int primary key, v int)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisabledAllowsAnyTimestamp() throws Throwable
|
||||
{
|
||||
guardrails().setMaximumTimestampThreshold(null, null);
|
||||
assertValid("INSERT INTO %s (k, v) VALUES (2, 2) USING TIMESTAMP " + (Long.MAX_VALUE - 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnabledFail() throws Throwable
|
||||
{
|
||||
assertFails("INSERT INTO %s (k, v) VALUES (2, 2) USING TIMESTAMP " + (Long.MAX_VALUE - 1), "maximum_timestamp violated");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnabledInRange() throws Throwable
|
||||
{
|
||||
assertValid("INSERT INTO %s (k, v) VALUES (1, 1) USING TIMESTAMP " + ClientState.getTimestamp());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnabledWarn() throws Throwable
|
||||
{
|
||||
assertWarns("INSERT INTO %s (k, v) VALUES (1, 1) USING TIMESTAMP " + (ClientState.getTimestamp() + (TimeUnit.DAYS.toMicros(1) + 40000)),
|
||||
"maximum_timestamp violated");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* 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.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.cassandra.config.DurationSpec;
|
||||
import org.apache.cassandra.service.ClientState;
|
||||
|
||||
public class GuardrailMinimumTimestampTest extends ThresholdTester
|
||||
{
|
||||
public GuardrailMinimumTimestampTest()
|
||||
{
|
||||
super(TimeUnit.DAYS.toSeconds(1) + "s",
|
||||
TimeUnit.DAYS.toSeconds(2) + "s",
|
||||
Guardrails.minimumAllowableTimestamp,
|
||||
Guardrails::setMinimumTimestampThreshold,
|
||||
Guardrails::getMinimumTimestampWarnThreshold,
|
||||
Guardrails::getMinimumTimestampFailThreshold,
|
||||
micros -> new DurationSpec.LongMicrosecondsBound(micros, TimeUnit.MICROSECONDS).toString(),
|
||||
micros -> new DurationSpec.LongMicrosecondsBound(micros).toMicroseconds());
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setupTest()
|
||||
{
|
||||
createTable("CREATE TABLE IF NOT EXISTS %s (k int primary key, v int)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisabled() throws Throwable
|
||||
{
|
||||
guardrails().setMinimumTimestampThreshold(null, null);
|
||||
assertValid("INSERT INTO %s (k, v) VALUES (1, 1) USING TIMESTAMP 12345");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnabledFailure() throws Throwable
|
||||
{
|
||||
assertFails("INSERT INTO %s (k, v) VALUES (1, 1) USING TIMESTAMP 12345", "minimum_timestamp violated");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnabledInRange() throws Throwable
|
||||
{
|
||||
assertValid("INSERT INTO %s (k, v) VALUES (1, 1) USING TIMESTAMP " + ClientState.getTimestamp());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnabledWarn() throws Throwable
|
||||
{
|
||||
assertWarns("INSERT INTO %s (k, v) VALUES (1, 1) USING TIMESTAMP " + (ClientState.getTimestamp() - (TimeUnit.DAYS.toMicros(1) + 40000)),
|
||||
"minimum_timestamp violated");
|
||||
}
|
||||
}
|
||||
|
|
@ -28,11 +28,9 @@ import java.util.function.ToLongFunction;
|
|||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.cassandra.config.DataStorageSpec;
|
||||
import org.assertj.core.api.Assertions;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import static org.apache.cassandra.config.DataStorageSpec.DataStorageUnit.BYTES;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.fail;
|
||||
|
|
@ -89,14 +87,18 @@ public abstract class ThresholdTester extends GuardrailTester
|
|||
Threshold threshold,
|
||||
TriConsumer<Guardrails, String, String> setter,
|
||||
Function<Guardrails, String> warnGetter,
|
||||
Function<Guardrails, String> failGetter)
|
||||
Function<Guardrails, String> failGetter,
|
||||
Function<Long, String> stringFormatter,
|
||||
ToLongFunction<String> stringParser)
|
||||
{
|
||||
super(threshold);
|
||||
this.warnThreshold = new DataStorageSpec.LongBytesBound(warnThreshold).toBytes();
|
||||
this.failThreshold = new DataStorageSpec.LongBytesBound(failThreshold).toBytes();
|
||||
this.setter = (g, w, a) -> setter.accept(g, w == null ? null : new DataStorageSpec.LongBytesBound(w, BYTES).toString(), a == null ? null : new DataStorageSpec.LongBytesBound(a, BYTES).toString());
|
||||
this.warnGetter = g -> new DataStorageSpec.LongBytesBound(warnGetter.apply(g)).toBytes();
|
||||
this.failGetter = g -> new DataStorageSpec.LongBytesBound(failGetter.apply(g)).toBytes();
|
||||
this.warnThreshold = stringParser.applyAsLong(warnThreshold);
|
||||
this.failThreshold = stringParser.applyAsLong(failThreshold);
|
||||
this.setter = (g, w, f) -> setter.accept(g,
|
||||
w == null ? null : stringFormatter.apply(w),
|
||||
f == null ? null : stringFormatter.apply(f));
|
||||
this.warnGetter = g -> stringParser.applyAsLong(warnGetter.apply(g));
|
||||
this.failGetter = g -> stringParser.applyAsLong(failGetter.apply(g));
|
||||
maxValue = Long.MAX_VALUE - 1;
|
||||
disabledValue = null;
|
||||
}
|
||||
|
|
@ -247,10 +249,9 @@ public abstract class ThresholdTester extends GuardrailTester
|
|||
value, name, disabledValue);
|
||||
|
||||
if (expectedMessage == null && value < 0)
|
||||
expectedMessage = format("Invalid data storage: value must be non-negative");
|
||||
expectedMessage = "value must be non-negative";
|
||||
|
||||
assertEquals(format("Exception message '%s' does not contain '%s'", e.getMessage(), expectedMessage),
|
||||
expectedMessage, e.getMessage());
|
||||
Assertions.assertThat(e).hasMessageContaining(expectedMessage);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue