Merge branch 'cassandra-4.1' into cassandra-5.0

This commit is contained in:
Stefan Miklosovic 2025-07-18 07:35:29 +02:00
commit 5fb3ae4473
No known key found for this signature in database
GPG Key ID: 32F35CB2F546D93E
5 changed files with 869 additions and 0 deletions

View File

@ -25,6 +25,8 @@
* Avoid purging deletions in RowFilter when reconciliation is required (CASSANDRA-20541)
* Fixed multiple single-node SAI query bugs relating to static columns (CASSANDRA-20338)
* Upgrade com.datastax.cassandra:cassandra-driver-core:3.11.5 to org.apache.cassandra:cassandra-driver-core:3.12.1 (CASSANDRA-17231)
Merged from 4.1:
* Add nodetool get/setguardrailsconfig commands (CASSANDRA-19552)
Merged from 4.0:
* Ensure prepared_statement INSERT timestamp precedes eviction DELETE (CASSANDRA-19703)
* Gossip doesn't converge due to race condition when updating EndpointStates multiple fields (CASSANDRA-20659)

View File

@ -82,6 +82,8 @@ import org.apache.cassandra.db.compaction.CompactionManager;
import org.apache.cassandra.db.compaction.CompactionManagerMBean;
import org.apache.cassandra.db.virtual.CIDRFilteringMetricsTable;
import org.apache.cassandra.db.virtual.CIDRFilteringMetricsTableMBean;
import org.apache.cassandra.db.guardrails.Guardrails;
import org.apache.cassandra.db.guardrails.GuardrailsMBean;
import org.apache.cassandra.fql.FullQueryLoggerOptions;
import org.apache.cassandra.fql.FullQueryLoggerOptionsCompositeData;
import org.apache.cassandra.gms.FailureDetector;
@ -170,6 +172,7 @@ public class NodeProbe implements AutoCloseable
protected CIDRGroupsMappingManagerMBean cmbProxy;
protected PermissionsCacheMBean pcProxy;
protected RolesCacheMBean rcProxy;
protected GuardrailsMBean grProxy;
protected Output output;
private boolean failed;
@ -308,6 +311,9 @@ public class NodeProbe implements AutoCloseable
name = new ObjectName(CIDRFilteringMetricsTable.MBEAN_NAME);
cfmProxy = JMX.newMBeanProxy(mbeanServerConn, name, CIDRFilteringMetricsTableMBean.class);
name = new ObjectName(Guardrails.MBEAN_NAME);
grProxy = JMX.newMBeanProxy(mbeanServerConn, name, GuardrailsMBean.class);
}
catch (MalformedObjectNameException e)
{
@ -2432,6 +2438,11 @@ public class NodeProbe implements AutoCloseable
table.printTo(out);
}
public GuardrailsMBean getGuardrailsMBean()
{
return grProxy;
}
}
class ColumnFamilyStoreMBeanIterator implements Iterator<Map.Entry<String, ColumnFamilyStoreMBean>>

View File

@ -52,6 +52,8 @@ import com.google.common.base.Throwables;
import org.apache.cassandra.locator.EndpointSnitchInfoMBean;
import org.apache.cassandra.tools.nodetool.*;
import org.apache.cassandra.tools.nodetool.GuardrailsConfigCommand.GetGuardrailsConfig;
import org.apache.cassandra.tools.nodetool.GuardrailsConfigCommand.SetGuardrailsConfig;
import org.apache.cassandra.utils.FBUtilities;
import com.google.common.collect.Maps;
@ -145,6 +147,7 @@ public class NodeTool
GetDefaultKeyspaceRF.class,
GetEndpoints.class,
GetFullQueryLog.class,
GetGuardrailsConfig.class,
GetInterDCStreamThroughput.class,
GetLoggingLevels.class,
GetMaxHintWindow.class,
@ -206,6 +209,7 @@ public class NodeTool
SetConcurrentCompactors.class,
SetConcurrentViewBuilders.class,
SetDefaultKeyspaceRF.class,
SetGuardrailsConfig.class,
SetHintedHandoffThrottleInKB.class,
SetInterDCStreamThroughput.class,
SetLoggingLevel.class,

View File

@ -0,0 +1,459 @@
/*
* 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.tools.nodetool;
import java.io.PrintStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.google.common.annotations.VisibleForTesting;
import io.airlift.airline.Arguments;
import io.airlift.airline.Command;
import io.airlift.airline.Option;
import org.apache.cassandra.db.guardrails.GuardrailsMBean;
import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool;
import org.apache.cassandra.tools.nodetool.formatter.TableBuilder;
import static java.lang.String.format;
import static java.util.Arrays.stream;
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toList;
public abstract class GuardrailsConfigCommand extends NodeTool.NodeToolCmd
{
@Command(name = "getguardrailsconfig", description = "Print runtime configuration of guardrails.")
public static class GetGuardrailsConfig extends GuardrailsConfigCommand
{
@Option(name = { "--category", "-c" },
description = "Category of guardrails to filter, can be one of 'values', 'thresholds', 'flags', 'others'.",
allowedValues = { "values", "thresholds", "flags", "others" })
private String guardrailCategory;
@Arguments(description = "Specific names or guardrails to get configuration of.")
private List<String> args = new ArrayList<>();
@Override
public void execute(NodeProbe probe)
{
GuardrailCategory categoryEnum = GuardrailCategory.parseCategory(guardrailCategory, probe.output().out);
if (!args.isEmpty() && categoryEnum != null)
throw new IllegalStateException("Do not specify additional arguments when --category/-c is set.");
List<Method> allGetters = stream(probe.getGuardrailsMBean().getClass().getDeclaredMethods())
.filter(method -> method.getName().startsWith("get")
&& !method.getName().endsWith("CSV"))
.filter(method -> args.isEmpty() || args.contains(toSnakeCase(method.getName().substring(3))))
.sorted(comparing(Method::getName))
.collect(toList());
display(probe, allGetters, categoryEnum);
}
@Override
public void addRow(List<InternalRow> bucket, GuardrailsMBean mBean, Method method, String guardrailName) throws Throwable
{
Class<?> returnType = method.getReturnType();
Object value = method.invoke(mBean);
if (returnType.equals(int.class) || returnType.equals(Integer.class)
|| returnType.equals(long.class) || returnType.equals(Long.class)
|| returnType.equals(boolean.class) || returnType.equals(Boolean.class)
|| returnType.equals(Set.class))
{
constructRow(bucket, guardrailName, value.toString());
}
else if (returnType.equals(String.class))
{
if (value == null || value.toString().isEmpty())
constructRow(bucket, guardrailName, "null");
else
constructRow(bucket, guardrailName, value.toString());
}
else
{
throw new RuntimeException("unhandled return type: " + returnType.getTypeName());
}
}
}
@Command(name = "setguardrailsconfig", description = "Modify runtime configuration of guardrails.")
public static class SetGuardrailsConfig extends GuardrailsConfigCommand
{
private static final Pattern SETTER_PATTERN = Pattern.compile("^set");
@Option(name = { "--list", "-l" },
description = "List all available guardrails setters")
private boolean list;
@Option(name = { "--category", "-c" },
description = "Category of guardrails to filter, can be one of 'values', 'thresholds', 'flags', 'others'.",
allowedValues = { "values", "thresholds", "flags", "others" })
private String guardrailCategory;
@Arguments(usage = "[<setter> <value1> ...]",
description = "For flags, possible values are 'true' or 'false'. " +
"For thresholds, two values are expected, first for warning, second for failure. " +
"For values, one value is expected, multiple values separated by comma.")
private final List<String> args = new ArrayList<>();
@Override
public void execute(NodeProbe probe)
{
if (!list && guardrailCategory != null)
throw new IllegalStateException("--category/-c can be specified only together with --list/-l");
GuardrailCategory categoryEnum = GuardrailCategory.parseCategory(guardrailCategory, probe.output().out);
if (args.isEmpty() && !list)
throw new IllegalStateException("No arguments.");
if (list)
display(probe, getAllSetters(probe), categoryEnum);
else
executeSetter(probe);
}
@Override
public void addRow(List<InternalRow> bucket, GuardrailsMBean mBean, Method method, String guardrailName) throws Throwable
{
if (method.getParameterTypes().length == 1)
constructRow(bucket, sanitizeSetterName(method), method.getParameterTypes()[0].getName());
else
constructRow(bucket, sanitizeSetterName(method), stream(method.getParameterTypes()).map(Class::getName).collect(toList()).toString());
}
private List<Method> getAllSetters(NodeProbe probe)
{
return stream(probe.getGuardrailsMBean().getClass().getDeclaredMethods())
.filter(method -> method.getName().startsWith("set") && !method.getName().endsWith("CSV"))
.filter(method -> args.isEmpty() || args.contains(toSnakeCase(method.getName().substring(3))))
.sorted(comparing(Method::getName))
.collect(toList());
}
private String sanitizeSetterName(Method setter)
{
return toSnakeCase(SETTER_PATTERN.matcher(setter.getName()).replaceAll(""));
}
private void executeSetter(NodeProbe nodeProbe)
{
String snakeCaseName = args.get(0);
String setterName = toCamelCase(args.get(0).startsWith("set_") ? args.get(0) : "set_" + args.get(0));
Method setter = getAllSetters(nodeProbe).stream()
.findFirst()
.orElseThrow(() -> new IllegalStateException(format("Setter method %s not found. " +
"Run nodetool setguardrailsconfig --list " +
"to see available setters", setterName)));
validateArguments(setter, snakeCaseName, args);
List<String> methodArgs = args.subList(1, args.size());
try
{
setter.invoke(nodeProbe.getGuardrailsMBean(), prepareArguments(methodArgs, setter));
}
catch (Exception ex)
{
String reason;
if (ex.getCause() != null)
reason = ex.getCause().getMessage();
else
reason = ex.getMessage();
throw new IllegalStateException(format("Error occured when setting the config for setter %s with arguments %s: %s",
snakeCaseName, methodArgs, reason));
}
}
private void validateArguments(Method setter, String setterName, List<String> args)
{
if (args.size() != setter.getParameterCount() + 1)
{
throw new IllegalStateException(format("%s is expecting %d argument values. Getting %d instead.",
setterName,
setter.getParameterCount(),
args.size() - 1));
}
}
private Object[] prepareArguments(List<String> args, Method method)
{
Class<?>[] parameterTypes = method.getParameterTypes();
Object[] arguments = new Object[args.size()];
for (int i = 0; i < args.size(); i++)
arguments[i] = castType(parameterTypes[i], args.get(i));
return arguments;
}
private Object castType(Class<?> targetType, String value) throws IllegalArgumentException
{
if (targetType == String.class)
return value.equals("null") ? "" : value;
else if (targetType == int.class || targetType == Integer.class)
return getNumber(value, Integer::parseInt, -1);
else if (targetType == long.class || targetType == Long.class)
return getNumber(value, Long::parseLong, -1);
else if (targetType == boolean.class || targetType == Boolean.class)
{
return getNumber(value, (v) -> {
if (!v.equals("true") && !v.equals("false"))
throw new IllegalStateException("Use 'true' or 'false' values for booleans");
return Boolean.parseBoolean(v);
}, false);
}
else if (targetType == Set.class)
{
if (value == null || value.equals("null"))
return Set.of();
else
return Set.of(value.split(","));
}
else
{
throw new IllegalArgumentException(format("unsupported type: %s", targetType));
}
}
private <T> T getNumber(String value, Function<String, T> transformer, T defaultValue)
{
if (value == null || value.equals("null"))
return defaultValue;
try
{
return transformer.apply(value);
}
catch (NumberFormatException ex)
{
throw new IllegalStateException(format("Unable to parse value %s", value), ex);
}
}
}
private static final Pattern CAMEL_PATTERN = Pattern.compile("([a-z])([A-Z])");
/**
* Special map for methods which do not adhere to camel-case convention precisely.
* These will be translated manually.
*/
private static final Map<String, String> toSnakeCaseTranslationMap = Map.of("ZeroTTLOnTWCSEnabled", "zero_ttl_on_twcs_enabled",
"ZeroTTLOnTWCSWarned", "zero_ttl_on_twcs_warned",
"FieldsPerUDTFailThreshold", "fields_per_udt_fail_threshold",
"FieldsPerUDTWarnThreshold", "fields_per_udt_warn_threshold",
"FieldsPerUDTThreshold", "fields_per_udt_threshold");
private static final Map<String, String> toCamelCaseTranslationMap = Map.of("set_zero_ttl_on_twcs_enabled", "setZeroTTLOnTWCSEnabled",
"set_zero_ttl_on_twcs_warned", "setZeroTTLOnTWCSWarned",
"set_fields_per_udt_threshold", "setFieldsPerUDTThreshold");
@VisibleForTesting
public enum GuardrailCategory
{
values,
thresholds,
flags,
others;
public static GuardrailCategory parseCategory(String category, PrintStream out)
{
if (category == null)
return null;
try
{
return GuardrailCategory.valueOf(category.toLowerCase());
}
catch (IllegalArgumentException ex)
{
String enabledValues = Arrays.stream(GuardrailCategory.values())
.map(GuardrailCategory::name)
.collect(Collectors.joining(","));
out.printf("%nError: Illegal value for -c/--category used: '"
+ category + "'. Supported values are " + enabledValues + ".%n");
System.exit(1);
return null;
}
}
}
/**
* Set of guardrails which are flags, even though their suffix would suggest they are part of "values" which have warned, ignored, and disallowed sub-categories
*/
private static final Set<String> specialFlags = Set.of("intersect_filtering_query_warned", "zero_ttl_on_twcs_warned");
void display(NodeProbe probe, List<Method> methods, GuardrailCategory userCategory)
{
try
{
List<InternalRow> flags = new ArrayList<>();
List<InternalRow> thresholds = new ArrayList<>();
List<InternalRow> values = new ArrayList<>();
List<InternalRow> others = new ArrayList<>();
for (Method method : methods)
{
String guardrailName = toSnakeCase(method.getName().substring(3));
List<InternalRow> bucket;
if (guardrailName.endsWith("_enabled"))
bucket = flags;
else if (guardrailName.endsWith("_threshold"))
bucket = thresholds;
else if (guardrailName.endsWith("_disallowed") ||
guardrailName.endsWith("_ignored"))
bucket = values;
else if (guardrailName.endsWith("_warned"))
{
if (specialFlags.contains(guardrailName))
bucket = flags;
else
bucket = values;
}
else
bucket = others;
addRow(bucket, probe.getGuardrailsMBean(), method, guardrailName);
}
TableBuilder tb = new TableBuilder();
Map<GuardrailCategory, List<InternalRow>> holder = new LinkedHashMap<>();
holder.put(GuardrailCategory.flags, flags);
holder.put(GuardrailCategory.thresholds, thresholds);
holder.put(GuardrailCategory.values, values);
holder.put(GuardrailCategory.others, others);
if (userCategory != null)
{
populateTable(tb, holder.get(userCategory));
}
else
{
if (holder.values().stream().flatMap(list -> Stream.of(list.toArray(InternalRow[]::new))).count() == 1)
{
for (Map.Entry<GuardrailCategory, List<InternalRow>> entry : holder.entrySet())
populateOne(tb, entry.getValue());
}
else
{
for (Map.Entry<GuardrailCategory, List<InternalRow>> entry : holder.entrySet())
populateTable(tb, entry.getValue());
}
}
tb.printTo(probe.output().out);
}
catch (Throwable e)
{
throw new RuntimeException("Error occured when getting the guardrails config", e);
}
}
private void populateTable(TableBuilder tableBuilder, List<InternalRow> bucket)
{
for (InternalRow row : bucket)
tableBuilder.add(row.name, row.value);
}
private void populateOne(TableBuilder tableBuilder, List<InternalRow> bucket)
{
if (bucket.size() == 1)
tableBuilder.add(bucket.get(0).value);
}
void constructRow(List<InternalRow> bucket, String guardrailName, String value)
{
bucket.add(new InternalRow(guardrailName, value));
}
abstract void addRow(List<InternalRow> bucket, GuardrailsMBean mBean, Method method, String guardrailName) throws Throwable;
public static class InternalRow
{
final String name;
final String value;
public InternalRow(String name, String value)
{
this.name = name;
this.value = value;
}
}
private static String toSnakeCase(String camelCase)
{
if (camelCase == null || camelCase.isEmpty())
return camelCase;
else
{
String maybeSnakeCase = toSnakeCaseTranslationMap.get(camelCase);
if (maybeSnakeCase != null)
return maybeSnakeCase;
return CAMEL_PATTERN.matcher(camelCase).replaceAll("$1_$2").toLowerCase();
}
}
private static String toCamelCase(String snakeCase)
{
if (snakeCase == null || snakeCase.isEmpty())
return snakeCase;
String maybeCamelCase = toCamelCaseTranslationMap.get(snakeCase);
if (maybeCamelCase != null)
return maybeCamelCase;
StringBuilder result = new StringBuilder();
boolean toUpper = false;
for (int i = 0; i < snakeCase.length(); i++)
{
char c = snakeCase.charAt(i);
if (c == '_')
{
toUpper = true;
}
else
{
result.append(toUpper ? Character.toUpperCase(c) : c);
toUpper = false;
}
}
return result.toString();
}
}

View File

@ -0,0 +1,393 @@
/*
* 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.tools.nodetool;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.BeforeClass;
import org.junit.Test;
import org.apache.cassandra.cql3.CQLTester;
import org.apache.cassandra.tools.ToolRunner.ToolResult;
import org.apache.cassandra.tools.nodetool.GuardrailsConfigCommand.GuardrailCategory;
import static org.apache.cassandra.tools.ToolRunner.invokeNodetool;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class GuardrailsConfigCommandsTest extends CQLTester
{
@BeforeClass
public static void setup() throws Exception
{
requireNetwork();
startJMXServer();
}
@Test
public void testGuardrailsConfigCommands()
{
ToolResult getResult = invokeNodetool("getguardrailsconfig");
getResult.asserts().success();
assertEquals(removeMultipleSpaces(ALL_GUARDRAILS_GETTER_OUTPUT), getOutput(getResult));
ToolResult getFlagsResult = invokeNodetool("getguardrailsconfig", "-c", "flags");
getFlagsResult.asserts().success();
assertEquals(removeMultipleSpaces(ALL_FLAGS_GETTER_OUTPUT), getOutput(getFlagsResult));
ToolResult getValuesResult = invokeNodetool("getguardrailsconfig", "-c", "values");
getValuesResult.asserts().success();
assertEquals(removeMultipleSpaces(ALL_VALUES_GETTER_OUTPUT), getOutput(getValuesResult));
ToolResult getThresholdsResult = invokeNodetool("getguardrailsconfig", "-c", "thresholds");
getValuesResult.asserts().success();
assertEquals(removeMultipleSpaces(ALL_THRESHOLDS_GETTER_OUTPUT), getOutput(getThresholdsResult));
ToolResult wrongCategory = invokeNodetool("getguardrailsconfig", "-c", "nonsense");
wrongCategory.asserts().failure();
assertTrue(getOutput(wrongCategory).contains("Error: Illegal value for -c/--category used: 'nonsense'. Supported values are values,thresholds,flags,others."));
// individual guardrail
ToolResult individualResult = invokeNodetool("getguardrailsconfig", "group_by_enabled");
getResult.asserts().success();
assertEquals("true\n", getOutput(individualResult));
// more than one result
ToolResult multipleResult = invokeNodetool("getguardrailsconfig", "group_by_enabled", "keyspaces_fail_threshold");
getResult.asserts().success();
assertEquals("group_by_enabled true\nkeyspaces_fail_threshold -1\n", getOutput(multipleResult));
// category with individual
ToolResult categoryWithIndividualResult = invokeNodetool("getguardrailsconfig", "-c", "values", "group_by_enabled");
categoryWithIndividualResult.asserts().failure();
assertTrue(categoryWithIndividualResult.getStdout().contains("Do not specify additional arguments when --category/-c is set."));
// get config of all guardrails enumerated on command line
String[] allLines = ALL_GUARDRAILS_GETTER_OUTPUT.split("\n");
List<String> argsForAllGuardrails = new ArrayList<>();
argsForAllGuardrails.add("getguardrailsconfig");
for (String line : allLines)
argsForAllGuardrails.add(line.split(" ")[0]);
ToolResult allGuardrails = invokeNodetool(argsForAllGuardrails);
allGuardrails.asserts().success();
assertEquals(ALL_GUARDRAILS_GETTER_OUTPUT, removeMultipleSpaces(allGuardrails.getStdout()));
// set
ToolResult setResultNoArgs = invokeNodetool("setguardrailsconfig");
setResultNoArgs.asserts().failure();
assertTrue(getOutput(setResultNoArgs).contains("No arguments."));
ToolResult setResultList = invokeNodetool("setguardrailsconfig", "--list");
setResultList.asserts().success();
setResultList = invokeNodetool("setguardrailsconfig", "-l");
setResultList.asserts().success();
assertEquals(removeMultipleSpaces(ALL_GUARDRAILS_SETTER_OUTPUT), getOutput(setResultList));
ToolResult categoryList = invokeNodetool("setguardrailsconfig", "--list", "--category", "values");
categoryList.asserts().success();
for (GuardrailCategory category : GuardrailCategory.values())
{
categoryList = invokeNodetool("setguardrailsconfig", "-l", "-c", category.name());
categoryList.asserts().success();
String expectedOutput = null;
switch (category)
{
case flags:
expectedOutput = ALL_FLAGS_SETTER_OUTPUT;
break;
case values:
expectedOutput = ALL_VALUES_SETTER_OUTPUT;
break;
case thresholds:
expectedOutput = ALL_THRESHOLDS_SETTER_OUTPUT;
break;
case others:
expectedOutput = ALL_OTHER_SETTER_OUTPUT;
break;
default:
fail("Untested category " + category);
}
assertEquals(removeMultipleSpaces(expectedOutput), getOutput(categoryList));
}
// test -c without -l does not make sense
ToolResult emptyCategoryListing = invokeNodetool("setguardrailsconfig", "-l", "-c");
emptyCategoryListing.asserts().failure();
assertTrue(getOutput(emptyCategoryListing).contains("Required values for option 'guardrailCategory' not provided"));
// test -c alone does not make sense
ToolResult onlyCategory = invokeNodetool("setguardrailsconfig", "-c", "values");
onlyCategory.asserts().failure();
assertTrue(getOutput(onlyCategory).contains("--category/-c can be specified only together with --list/-l"));
// it would be quite cumbersome to test all guardrails are settable so we will
// set one from each category to prove the point
// flag
setFlag("allow_filtering_enabled", false);
assertFalse(getFlag("allow_filtering_enabled"));
setFlag("allow_filtering_enabled", true);
assertTrue(getFlag("allow_filtering_enabled"));
// value
setValues("table_properties_warned", "comment", "cdc");
assertArrayEquals(new String[]{ "comment", "cdc" }, getValues("table_properties_warned"));
setValues("table_properties_warned", "null");
assertArrayEquals(new String[0], getValues("table_properties_warned"));
// threshold
setThresholds("keyspaces_threshold", "10", "20");
assertEquals("20", getThreshold("keyspaces_fail_threshold"));
assertEquals("10", getThreshold("keyspaces_warn_threshold"));
setThresholds("keyspaces_threshold", "-1", "-1");
assertEquals("-1", getThreshold("keyspaces_fail_threshold"));
assertEquals("-1", getThreshold("keyspaces_warn_threshold"));
// test incorrect number of parameters or invalid values
ToolResult invalidNumberOfArgsForThreshold = invokeNodetool("setguardrailsconfig", "keyspaces_threshold", "10", "20", "30");
invalidNumberOfArgsForThreshold.asserts().failure();
assertTrue(invalidNumberOfArgsForThreshold.getStdout().contains("keyspaces_threshold is expecting 2 argument values. Getting 3 instead."));
// not separated by comma
ToolResult invalidNumberOfArgsForValues = invokeNodetool("setguardrailsconfig", "table_properties_warned", "comment", "cdc");
invalidNumberOfArgsForValues.asserts().failure();
assertTrue(invalidNumberOfArgsForValues.getStdout().contains("table_properties_warned is expecting 1 argument values. Getting 2 instead."));
// invalid boolean
ToolResult invalidBooleanForFlags = invokeNodetool("setguardrailsconfig", "allow_filtering_enabled", "nonsense");
invalidBooleanForFlags.asserts().failure();
assertTrue(invalidBooleanForFlags.getStdout().contains("Use 'true' or 'false' values for booleans"));
// test propagation of errors from guardrail when values are wrong
ToolResult nonsenseSetterArgs = invokeNodetool("setguardrailsconfig", "keyspaces_threshold", "-10", "-20");
nonsenseSetterArgs.asserts().failure();
assertTrue(nonsenseSetterArgs.getStdout().contains("Error occured when setting the config for setter keyspaces_threshold with arguments [-10, -20]: " +
"Invalid value -10 for keyspaces_warn_threshold: negative values are not allowed, outside of -1 which disables the guardrail"));
}
private static final String ALL_FLAGS_GETTER_OUTPUT =
"allow_filtering_enabled true \n" +
"alter_table_enabled true \n" +
"compact_tables_enabled true \n" +
"drop_keyspace_enabled true \n" +
"drop_truncate_table_enabled true \n" +
"group_by_enabled true \n" +
"intersect_filtering_query_enabled true \n" +
"intersect_filtering_query_warned true \n" +
"non_partition_restricted_query_enabled true \n" +
"read_before_write_list_operations_enabled true \n" +
"secondary_indexes_enabled true \n" +
"simple_strategy_enabled true \n" +
"uncompressed_tables_enabled true \n" +
"user_timestamps_enabled true \n" +
"vector_type_enabled true \n" +
"zero_ttl_on_twcs_enabled true \n" +
"zero_ttl_on_twcs_warned true \n";
private static final String ALL_THRESHOLDS_GETTER_OUTPUT =
"collection_size_fail_threshold null \n" +
"collection_size_warn_threshold null \n" +
"column_value_size_fail_threshold null \n" +
"column_value_size_warn_threshold null \n" +
"columns_per_table_fail_threshold -1 \n" +
"columns_per_table_warn_threshold -1 \n" +
"data_disk_usage_percentage_fail_threshold -1 \n" +
"data_disk_usage_percentage_warn_threshold -1 \n" +
"fields_per_udt_fail_threshold -1 \n" +
"fields_per_udt_warn_threshold -1 \n" +
"in_select_cartesian_product_fail_threshold -1 \n" +
"in_select_cartesian_product_warn_threshold -1 \n" +
"items_per_collection_fail_threshold -1 \n" +
"items_per_collection_warn_threshold -1 \n" +
"keyspaces_fail_threshold -1 \n" +
"keyspaces_warn_threshold -1 \n" +
"materialized_views_per_table_fail_threshold -1 \n" +
"materialized_views_per_table_warn_threshold -1 \n" +
"maximum_replication_factor_fail_threshold -1 \n" +
"maximum_replication_factor_warn_threshold -1 \n" +
"maximum_timestamp_fail_threshold null \n" +
"maximum_timestamp_warn_threshold null \n" +
"minimum_replication_factor_fail_threshold -1 \n" +
"minimum_replication_factor_warn_threshold -1 \n" +
"minimum_timestamp_fail_threshold null \n" +
"minimum_timestamp_warn_threshold null \n" +
"page_size_fail_threshold -1 \n" +
"page_size_warn_threshold -1 \n" +
"partition_keys_in_select_fail_threshold -1 \n" +
"partition_keys_in_select_warn_threshold -1 \n" +
"partition_size_fail_threshold null \n" +
"partition_size_warn_threshold null \n" +
"partition_tombstones_fail_threshold -1 \n" +
"partition_tombstones_warn_threshold -1 \n" +
"sai_frozen_term_size_fail_threshold 8KiB \n" +
"sai_frozen_term_size_warn_threshold 1KiB \n" +
"sai_sstable_indexes_per_query_fail_threshold -1 \n" +
"sai_sstable_indexes_per_query_warn_threshold 32 \n" +
"sai_string_term_size_fail_threshold 8KiB \n" +
"sai_string_term_size_warn_threshold 1KiB \n" +
"sai_vector_term_size_fail_threshold 32KiB\n" +
"sai_vector_term_size_warn_threshold 16KiB\n" +
"secondary_indexes_per_table_fail_threshold -1 \n" +
"secondary_indexes_per_table_warn_threshold -1 \n" +
"tables_fail_threshold -1 \n" +
"tables_warn_threshold -1 \n" +
"vector_dimensions_fail_threshold -1 \n" +
"vector_dimensions_warn_threshold -1 \n";
private static final String ALL_VALUES_GETTER_OUTPUT =
"read_consistency_levels_disallowed [] \n" +
"read_consistency_levels_warned [] \n" +
"table_properties_disallowed [] \n" +
"table_properties_ignored [] \n" +
"table_properties_warned [] \n" +
"write_consistency_levels_disallowed [] \n" +
"write_consistency_levels_warned [] \n";
private static final String ALL_OTHER_GETTER_OUTPUT =
"data_disk_usage_max_disk_size null \n";
private static final String ALL_FLAGS_SETTER_OUTPUT =
"allow_filtering_enabled boolean \n" +
"alter_table_enabled boolean \n" +
"compact_tables_enabled boolean \n" +
"drop_keyspace_enabled boolean \n" +
"drop_truncate_table_enabled boolean \n" +
"group_by_enabled boolean \n" +
"intersect_filtering_query_enabled boolean \n" +
"intersect_filtering_query_warned boolean \n" +
"non_partition_restricted_query_enabled boolean \n" +
"read_before_write_list_operations_enabled boolean \n" +
"secondary_indexes_enabled boolean \n" +
"simple_strategy_enabled boolean \n" +
"uncompressed_tables_enabled boolean \n" +
"user_timestamps_enabled boolean \n" +
"vector_type_enabled boolean \n" +
"zero_ttl_on_twcs_enabled boolean \n" +
"zero_ttl_on_twcs_warned boolean \n";
private static final String ALL_THRESHOLDS_SETTER_OUTPUT =
"collection_size_threshold [java.lang.String, java.lang.String]\n" +
"column_value_size_threshold [java.lang.String, java.lang.String]\n" +
"columns_per_table_threshold [int, int] \n" +
"data_disk_usage_percentage_threshold [int, int] \n" +
"fields_per_udt_threshold [int, int] \n" +
"in_select_cartesian_product_threshold [int, int] \n" +
"items_per_collection_threshold [int, int] \n" +
"keyspaces_threshold [int, int] \n" +
"materialized_views_per_table_threshold [int, int] \n" +
"maximum_replication_factor_threshold [int, int] \n" +
"maximum_timestamp_threshold [java.lang.String, java.lang.String]\n" +
"minimum_replication_factor_threshold [int, int] \n" +
"minimum_timestamp_threshold [java.lang.String, java.lang.String]\n" +
"page_size_threshold [int, int] \n" +
"partition_keys_in_select_threshold [int, int] \n" +
"partition_size_threshold [java.lang.String, java.lang.String]\n" +
"partition_tombstones_threshold [long, long] \n" +
"sai_frozen_term_size_threshold [java.lang.String, java.lang.String]\n" +
"sai_sstable_indexes_per_query_threshold [int, int] \n" +
"sai_string_term_size_threshold [java.lang.String, java.lang.String]\n" +
"sai_vector_term_size_threshold [java.lang.String, java.lang.String]\n" +
"secondary_indexes_per_table_threshold [int, int] \n" +
"tables_threshold [int, int] \n" +
"vector_dimensions_threshold [int, int] \n";
private static final String ALL_VALUES_SETTER_OUTPUT =
"read_consistency_levels_disallowed java.util.Set \n" +
"read_consistency_levels_warned java.util.Set \n" +
"table_properties_disallowed java.util.Set \n" +
"table_properties_ignored java.util.Set \n" +
"table_properties_warned java.util.Set \n" +
"write_consistency_levels_disallowed java.util.Set \n" +
"write_consistency_levels_warned java.util.Set \n";
private static final String ALL_OTHER_SETTER_OUTPUT =
"data_disk_usage_max_disk_size java.lang.String \n";
private static final String ALL_GUARDRAILS_GETTER_OUTPUT = removeMultipleSpaces(ALL_FLAGS_GETTER_OUTPUT +
ALL_THRESHOLDS_GETTER_OUTPUT +
ALL_VALUES_GETTER_OUTPUT +
ALL_OTHER_GETTER_OUTPUT);
private static final String ALL_GUARDRAILS_SETTER_OUTPUT = removeMultipleSpaces(ALL_FLAGS_SETTER_OUTPUT +
ALL_THRESHOLDS_SETTER_OUTPUT +
ALL_VALUES_SETTER_OUTPUT +
ALL_OTHER_SETTER_OUTPUT);
private static String removeMultipleSpaces(String input)
{
return input.replaceAll(" +", " ").replaceAll(" \n", "\n");
}
private String getOutput(ToolResult toolResult)
{
return removeMultipleSpaces(toolResult.getStdout());
}
private ToolResult setFlag(String name, Boolean flag)
{
return invokeNodetool("setguardrailsconfig", name, flag.toString());
}
private ToolResult setThresholds(String name, String warn, String fail)
{
return invokeNodetool("setguardrailsconfig", name, warn, fail);
}
private ToolResult setValues(String name, String... values)
{
return invokeNodetool("setguardrailsconfig", name, String.join(",", Arrays.asList(values)));
}
private boolean getFlag(String name)
{
return Boolean.parseBoolean(invokeNodetool("getguardrailsconfig", name).getStdout().replaceAll("\n", ""));
}
private String getThreshold(String name)
{
return invokeNodetool("getguardrailsconfig", name).getStdout().replaceAll("\n", "");
}
private String[] getValues(String name)
{
String[] split = invokeNodetool("getguardrailsconfig", name).getStdout()
.replace("\n", "")
.replace("[", "")
.replace("]", "")
.replace(" ", "")
.split(",");
if (split.length == 1 && split[0].isEmpty())
return new String[0];
else
return split;
}
}