Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ Merged from 5.0:
* Fix resource cleanup after SAI query timeouts (CASSANDRA-19177)
* Suppress CVE-2023-6481 (CASSANDRA-19184)
Merged from 4.1:
* Nodetool to get and modify Guardrails configuration
* Fix hints delivery for a node going down repeatedly (CASSANDRA-19495)
* Do not go to disk for reading hints file sizes (CASSANDRA-19477)
* Fix system_views.settings to handle array types (CASSANDRA-19475)
Expand Down
11 changes: 11 additions & 0 deletions src/java/org/apache/cassandra/tools/NodeProbe.java
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@
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;
Expand Down Expand Up @@ -171,6 +173,7 @@ public class NodeProbe implements AutoCloseable
protected CIDRGroupsMappingManagerMBean cmbProxy;
protected PermissionsCacheMBean pcProxy;
protected RolesCacheMBean rcProxy;
protected GuardrailsMBean grProxy;
protected Output output;
private boolean failed;

Expand Down Expand Up @@ -311,6 +314,9 @@ protected void connect() throws IOException

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)
{
Expand Down Expand Up @@ -2376,6 +2382,11 @@ public void abortBootstrap(String nodeId, String endpoint)
{
ssProxy.abortBootstrap(nodeId, endpoint);
}

public GuardrailsMBean getGuardrailsMBean()
{
return grProxy;
}
}

class ColumnFamilyStoreMBeanIterator implements Iterator<Map.Entry<String, ColumnFamilyStoreMBean>>
Expand Down
2 changes: 2 additions & 0 deletions src/java/org/apache/cassandra/tools/NodeTool.java
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ public int execute(String... args)
GetDefaultKeyspaceRF.class,
GetEndpoints.class,
GetFullQueryLog.class,
GetGuardrailsConfig.class,
GetInterDCStreamThroughput.class,
GetLoggingLevels.class,
GetMaxHintWindow.class,
Expand Down Expand Up @@ -207,6 +208,7 @@ public int execute(String... args)
SetConcurrentCompactors.class,
SetConcurrentViewBuilders.class,
SetDefaultKeyspaceRF.class,
SetGuardrailsConfig.class,
SetHintedHandoffThrottleInKB.class,
SetInterDCStreamThroughput.class,
SetLoggingLevel.class,
Expand Down
126 changes: 126 additions & 0 deletions src/java/org/apache/cassandra/tools/nodetool/GetGuardrailsConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
* 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.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

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;

@Command(name = "getguardrailsconfig", description = "Print current guardrails configurations")
public class GetGuardrailsConfig extends NodeTool.NodeToolCmd
{
@Option(title = "list_all_guardrails_config",
name = { "--all" },
description = "List all guardrails configuration (including disabled)")
private boolean showFullConfig = false;

private final StringBuilder sb = new StringBuilder();

@Override
public void execute(NodeProbe probe)
{
GuardrailsMBean mbean = probe.getGuardrailsMBean();
sb.append("Guardrails Configuration:\n");
printConfig(mbean);
probe.output().out.println(sb);
}

private void printConfig(GuardrailsMBean mbean)
{
// Get all available getters for Guardrails
Method[] methods = mbean.getClass().getDeclaredMethods();
List<Method> allGetters = Arrays.stream(methods)
.filter(method -> method.getName().startsWith("get"))
.sorted(Comparator.comparing(Method::getName))
.collect(Collectors.toList());
try
{
for (Method getter : allGetters)
{
String guardrailName = getter.getName().substring(3);
Class<?> type = getter.getReturnType();
Object res = getter.invoke(mbean);
printResObject(res, guardrailName, type);
}
}
catch (Exception e)
{
throw new RuntimeException("Error occured when getting the guardrails config", e);
}
}

private void printResObject(Object res, String name, Class<?> returnType)
{
String strVal = "";
boolean isGuardrailEnabled;
if (returnType.equals(int.class) || returnType.equals(Integer.class))
{
isGuardrailEnabled = (Integer)res > 0;
strVal = res.toString();
}
else if (returnType.equals(long.class) || returnType.equals(Long.class))
{
isGuardrailEnabled = (Long)res > 0;
strVal = res.toString();
}
else if (returnType.equals(boolean.class) || returnType.equals(Boolean.class))
{
isGuardrailEnabled = !(Boolean)res;
strVal = res.toString();
}
else if (returnType.equals(String.class))
{
if (res == null || res.toString().isEmpty())
{
strVal = "null";
}
else
{
strVal = res.toString();
}
isGuardrailEnabled = !strVal.equals("null") && !strVal.isEmpty();
}
else if (returnType.equals(Set.class))
{
// skip Set<String> return type, we should have an equivalent CSV method that
// returns comma-separated string list.
return;
}
else
{
// unhandled type
throw new RuntimeException("unhandled return type: " + returnType.getTypeName());
}
if (showFullConfig || isGuardrailEnabled)
{
// print only if ask for all config or the guardrail is enabled
sb.append(name).append(": ").append(strVal).append('\n');
}
}
}
164 changes: 164 additions & 0 deletions src/java/org/apache/cassandra/tools/nodetool/SetGuardrailsConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/*
* 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.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;


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;

@Command(
name = "setguardrailsconfig",
description = "Modify guardrails configurations. " +
"Please use --list to find available setters. " +
"For Threshold setter please provide <warn> <fail> thresholds. " +
"For Properties setter please use the setter ends with CSV and pass in Comma-separated list. " +
"To disable the guardrail, put 'null' as the argument.")
public class SetGuardrailsConfig extends NodeTool.NodeToolCmd
{
@Option(title = "list_guardrails_setters",
name = "--list",
description = "List all available guardrails setters")
private boolean listSetters = false;

@Arguments(usage = "[<setter> <value1> ...]", description = "Call setter to modify guardrail configuration")
private final List<String> args = new ArrayList<>();

@Override
public void execute(NodeProbe probe)
{
// Get all available setters for Guardrails
GuardrailsMBean mbean = probe.getGuardrailsMBean();
Method[] methods = mbean.getClass().getDeclaredMethods();
List<Method> allSetters = Arrays.stream(methods)
.filter(method -> method.getName().startsWith("set"))
.sorted(Comparator.comparing(Method::getName))
.collect(Collectors.toList());
if (listSetters)
{
StringBuilder sb = new StringBuilder();
for (Method setter : allSetters)
{
sb.append(setter.getName()).append('\t');
if (setter.getParameterTypes().length == 0)
{
sb.append("No argument");
}
else if (setter.getParameterTypes().length == 1)
{
sb.append(setter.getParameterTypes()[0].getName());
}
else
{
sb.append(Arrays.stream(setter.getParameterTypes()).map(Class::getName).collect(Collectors.toList()));
}
sb.append('\n');
}
probe.output().out.println(sb);
return;
}

// verify setter name
String setterName = args.get(0);
Method setter = allSetters.stream()
.filter(method -> method.getName().equals(setterName))
.findFirst()
.orElseThrow(() -> new RuntimeException(String.format("Setter method %s not found. " +
"Run nodetool setguardrailsconfig --list " +
"to see available setters",
setterName)));
// verify args count
if (args.size() != setter.getParameterCount() + 1)
{
throw new RuntimeException(String.format("%s is expecting %d args. Getting %d instead.",
setterName,
setter.getParameterCount(),
args.size() - 1));
}
// invoke method with args
List<String> methodArgs = args.subList(1, args.size());
try
{
setter.invoke(mbean, prepareArguments(methodArgs, setter));
}
catch (Exception e)
{
throw new RuntimeException("Error occured when setting the config", e);
}
}

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)
{
if (value.equals("null"))
{
return "";
}
return value;
}
else if (targetType == int.class || targetType == Integer.class)
{
if (value.equals("null"))
{
return -1;
}
return Integer.parseInt(value);
}
else if (targetType == long.class || targetType == Long.class)
{
if (value.equals("null"))
{
return -1;
}
return Long.parseLong(value);
}
else if (targetType == boolean.class || targetType == Boolean.class)
{
return Boolean.parseBoolean(value);
}
else
{
// unhandled type
throw new IllegalArgumentException("unsupported type: " + targetType + '.'+
"Please use the setter ends with CSV and pass in comma-separated list");
}
}
}
Loading