mirror of https://github.com/apache/cassandra
Merge branch 'cassandra-4.1' into cassandra-5.0
This commit is contained in:
commit
5fb3ae4473
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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>>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue