Migrate all nodetool commands from airline to picocli

Replace io.airlift:airline dependency with info.picocli:picocli across
all nodetool command implementations. This migration includes:

- Convert 160+ nodetool command classes from airline to picocli
- Add AbstractCommand base class for commands and utility methods
- Add NodetoolCommand top-level class for the cli utility
- Update all command classes to use @Command, @Option, and @Parameters
- Add plain text files to test command help output
- Add mock test classes for improved test coverage and parse validation
- Modify test infrastructure to work with picocli-based commands
- Add new layouts for cli formatting to preserve backwards compatibility

The migration maintains backward compatibility while providing improved
command-line parsing, better help system, and more robust argument
validation through picocli enhanced features.

patch by Maxim Muzafarov; reviewed by Caleb Rackliffe, Dmitry Konstantinov, Stefan Miklosovic for CASSANDRA-17445
This commit is contained in:
Maxim Muzafarov 2025-07-26 18:07:24 +02:00
parent f18d5ad851
commit 94b251f3ce
No known key found for this signature in database
GPG Key ID: 7FEC714D84388C16
494 changed files with 14954 additions and 4071 deletions

View File

@ -57,6 +57,7 @@
<exclude name="test/conf/sstableloader_with_encryption.yaml"/> <exclude name="test/conf/sstableloader_with_encryption.yaml"/>
<exclude name="test/conf/unit-test-conf/test-native-port.yaml"/> <exclude name="test/conf/unit-test-conf/test-native-port.yaml"/>
<exclude name="test/resources/data/config/YamlConfigurationLoaderTest/*.yaml"/> <exclude name="test/resources/data/config/YamlConfigurationLoaderTest/*.yaml"/>
<exclude name="test/resources/nodetool/help/**"/>
<exclude name="tools/cqlstress-*.yaml"/> <exclude name="tools/cqlstress-*.yaml"/>
<!-- test data --> <!-- test data -->
<exclude name="pylib/cqlshlib/test/test_authproviderhandling_config/*"/> <exclude name="pylib/cqlshlib/test/test_authproviderhandling_config/*"/>

View File

@ -98,6 +98,10 @@
<groupId>org.openjdk.jmh</groupId> <groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId> <artifactId>jmh-generator-annprocess</artifactId>
</dependency> </dependency>
<dependency>
<groupId>io.github.java-diff-utils</groupId>
<artifactId>java-diff-utils</artifactId>
</dependency>
<dependency> <dependency>
<groupId>net.ju-n.compile-command-annotations</groupId> <groupId>net.ju-n.compile-command-annotations</groupId>
<artifactId>compile-command-annotations</artifactId> <artifactId>compile-command-annotations</artifactId>

View File

@ -121,8 +121,8 @@
<artifactId>cassandra-accord</artifactId> <artifactId>cassandra-accord</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.airlift</groupId> <groupId>info.picocli</groupId>
<artifactId>airline</artifactId> <artifactId>picocli</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.dropwizard.metrics</groupId> <groupId>io.dropwizard.metrics</groupId>

View File

@ -662,6 +662,12 @@
<version>1.37</version> <version>1.37</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>io.github.java-diff-utils</groupId>
<artifactId>java-diff-utils</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.apache.ant</groupId> <groupId>org.apache.ant</groupId>
<artifactId>ant-junit</artifactId> <artifactId>ant-junit</artifactId>
@ -752,15 +758,9 @@
</exclusions> </exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.airlift</groupId> <groupId>info.picocli</groupId>
<artifactId>airline</artifactId> <artifactId>picocli</artifactId>
<version>0.8</version> <version>4.7.7</version>
<exclusions>
<exclusion>
<artifactId>jsr305</artifactId>
<groupId>com.google.code.findbugs</groupId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.netty</groupId> <groupId>io.netty</groupId>

View File

@ -1,4 +1,5 @@
5.1 5.1
* Migrate all nodetool commands from airline to picocli (CASSANDRA-17445)
* Journal.TopologyUpdate should not store the local topology as it can be inferred from the global on (CASSANDRA-20785) * Journal.TopologyUpdate should not store the local topology as it can be inferred from the global on (CASSANDRA-20785)
* Accord: Topology serializer has a lot of repeated data, can dedup to shrink the cost (CASSANDRA-20715) * Accord: Topology serializer has a lot of repeated data, can dedup to shrink the cost (CASSANDRA-20715)
* Stream individual files in their own transactions and hand over ownership to a parent transaction on completion (CASSANDRA-20728) * Stream individual files in their own transactions and hand over ownership to a parent transaction on completion (CASSANDRA-20728)

View File

@ -20,6 +20,12 @@ package org.apache.cassandra.config;
// checkstyle: suppress below 'blockSystemPropertyUsage' // checkstyle: suppress below 'blockSystemPropertyUsage'
import java.util.Arrays;
import org.apache.cassandra.exceptions.ConfigurationException;
import static org.apache.cassandra.utils.LocalizeString.toUpperCaseLocalized;
public enum CassandraRelevantEnv public enum CassandraRelevantEnv
{ {
/** /**
@ -28,8 +34,10 @@ public enum CassandraRelevantEnv
*/ */
JAVA_HOME ("JAVA_HOME"), JAVA_HOME ("JAVA_HOME"),
CIRCLECI("CIRCLECI"), CIRCLECI("CIRCLECI"),
CASSANDRA_SKIP_SYNC("CASSANDRA_SKIP_SYNC") CASSANDRA_SKIP_SYNC("CASSANDRA_SKIP_SYNC"),
/** By default, the standard Cassandra CLI layout is used for backward compatibility, however,
* the new Picocli layout can be enabled by setting this property to the {@code "picocli"}. */
CASSANDRA_CLI_LAYOUT("CASSANDRA_CLI_LAYOUT"),
; ;
CassandraRelevantEnv(String key) CassandraRelevantEnv(String key)
@ -56,4 +64,20 @@ public enum CassandraRelevantEnv
public String getKey() { public String getKey() {
return key; return key;
} }
public <T extends Enum<T>> T getEnum(boolean toUppercase, Class<T> enumClass, String defaultVal)
{
String value = System.getenv(key);
value = value == null ? defaultVal : value;
try
{
return Enum.valueOf(enumClass, toUppercase ? toUpperCaseLocalized(value) : value);
}
catch (IllegalArgumentException e)
{
throw new ConfigurationException(String.format("Invalid value for environment variable '%s': " +
"expected one of %s (case-insensitive) but was '%s'",
key, Arrays.toString(enumClass.getEnumConstants()), value));
}
}
} }

View File

@ -18,6 +18,7 @@
package org.apache.cassandra.config; package org.apache.cassandra.config;
import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -87,6 +88,9 @@ public enum CassandraRelevantProperties
CACHEABLE_MUTATION_SIZE_LIMIT("cassandra.cacheable_mutation_size_limit_bytes", convertToString(1_000_000)), CACHEABLE_MUTATION_SIZE_LIMIT("cassandra.cacheable_mutation_size_limit_bytes", convertToString(1_000_000)),
CASSANDRA_ALLOW_SIMPLE_STRATEGY("cassandra.allow_simplestrategy"), CASSANDRA_ALLOW_SIMPLE_STRATEGY("cassandra.allow_simplestrategy"),
CASSANDRA_AVAILABLE_PROCESSORS("cassandra.available_processors"), CASSANDRA_AVAILABLE_PROCESSORS("cassandra.available_processors"),
/** By default, the standard Cassandra CLI layout is used for backward compatibility, however,
* the new Picocli layout can be enabled by setting this property to the {@code "picocli"}. */
CASSANDRA_CLI_LAYOUT("cassandra.cli.layout", "airline"),
/** The classpath storage configuration file. */ /** The classpath storage configuration file. */
CASSANDRA_CONFIG("cassandra.config", "cassandra.yaml"), CASSANDRA_CONFIG("cassandra.config", "cassandra.yaml"),
/** /**
@ -1002,7 +1006,16 @@ public enum CassandraRelevantProperties
public <T extends Enum<T>> T getEnum(boolean toUppercase, Class<T> enumClass) public <T extends Enum<T>> T getEnum(boolean toUppercase, Class<T> enumClass)
{ {
String value = System.getProperty(key, defaultVal); String value = System.getProperty(key, defaultVal);
return Enum.valueOf(enumClass, toUppercase ? toUpperCaseLocalized(value) : value); try
{
return Enum.valueOf(enumClass, toUppercase ? toUpperCaseLocalized(value) : value);
}
catch (IllegalArgumentException e)
{
throw new ConfigurationException(String.format("Invalid value for system propery '%s': " +
"expected one of %s (case-insensitive) but was '%s'",
key, Arrays.toString(enumClass.getEnumConstants()), value));
}
} }
/** /**

View File

@ -469,16 +469,16 @@ public class SSTableImporter
public static class Options public static class Options
{ {
private final Set<String> srcPaths; final Set<String> srcPaths;
private final boolean resetLevel; final boolean resetLevel;
private final boolean clearRepaired; final boolean clearRepaired;
private final boolean verifySSTables; final boolean verifySSTables;
private final boolean verifyTokens; final boolean verifyTokens;
private final boolean invalidateCaches; final boolean invalidateCaches;
private final boolean extendedVerify; final boolean extendedVerify;
private final boolean copyData; final boolean copyData;
private final boolean failOnMissingIndex; final boolean failOnMissingIndex;
public final boolean validateIndexChecksum; final boolean validateIndexChecksum;
public Options(Set<String> srcPaths, boolean resetLevel, boolean clearRepaired, public Options(Set<String> srcPaths, boolean resetLevel, boolean clearRepaired,
boolean verifySSTables, boolean verifyTokens, boolean invalidateCaches, boolean verifySSTables, boolean verifyTokens, boolean invalidateCaches,
@ -523,9 +523,9 @@ public class SSTableImporter
", verifyTokens=" + verifyTokens + ", verifyTokens=" + verifyTokens +
", invalidateCaches=" + invalidateCaches + ", invalidateCaches=" + invalidateCaches +
", extendedVerify=" + extendedVerify + ", extendedVerify=" + extendedVerify +
", copyData= " + copyData + ", copyData=" + copyData +
", failOnMissingIndex= " + failOnMissingIndex + ", failOnMissingIndex=" + failOnMissingIndex +
", validateIndexChecksum= " + validateIndexChecksum + ", validateIndexChecksum=" + validateIndexChecksum +
'}'; '}';
} }

View File

@ -40,7 +40,6 @@ import java.util.function.BiConsumer;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import javax.inject.Inject;
import javax.management.InstanceNotFoundException; import javax.management.InstanceNotFoundException;
import javax.management.IntrospectionException; import javax.management.IntrospectionException;
import javax.management.MBeanAttributeInfo; import javax.management.MBeanAttributeInfo;
@ -62,14 +61,7 @@ import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView; import com.google.common.collect.Sets.SetView;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import io.airlift.airline.Arguments;
import io.airlift.airline.Cli;
import io.airlift.airline.Command;
import io.airlift.airline.Help;
import io.airlift.airline.HelpOption;
import io.airlift.airline.Option;
import org.apache.cassandra.config.YamlConfigurationLoader; import org.apache.cassandra.config.YamlConfigurationLoader;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.io.util.FileInputStreamPlus; import org.apache.cassandra.io.util.FileInputStreamPlus;
import org.apache.cassandra.utils.JsonUtils; import org.apache.cassandra.utils.JsonUtils;
import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.DumperOptions;
@ -80,8 +72,14 @@ import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.nodes.Node; import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.Tag; import org.yaml.snakeyaml.nodes.Tag;
import org.yaml.snakeyaml.representer.Representer; import org.yaml.snakeyaml.representer.Representer;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
public class JMXTool @Command(name = "jmxtool", description = "JMX tool for Apache Cassandra",
mixinStandardHelpOptions = true,
subcommands = { CommandLine.HelpCommand.class, JMXTool.Dump.class, JMXTool.Diff.class })
public class JMXTool implements Callable<Void>
{ {
private static final List<String> METRIC_PACKAGES = Arrays.asList("org.apache.cassandra.metrics", private static final List<String> METRIC_PACKAGES = Arrays.asList("org.apache.cassandra.metrics",
"org.apache.cassandra.db", "org.apache.cassandra.db",
@ -109,16 +107,20 @@ public class JMXTool
return rc; return rc;
}; };
@Command(name = "dump", description = "Dump the Apache Cassandra JMX objects and metadata.") @Override
public Void call() throws Exception
{
CommandLine.usage(this, System.out);
return null;
}
@Command(name = "dump", description = "Dump the Apache Cassandra JMX objects and metadata.", mixinStandardHelpOptions = true)
public static final class Dump implements Callable<Void> public static final class Dump implements Callable<Void>
{ {
@Inject @Option(paramLabel = "url", names = { "-u", "--url" }, description = "JMX url to target")
private HelpOption helpOption;
@Option(title = "url", name = { "-u", "--url" }, description = "JMX url to target")
private String targetUrl = "service:jmx:rmi:///jndi/rmi://localhost:7199/jmxrmi"; private String targetUrl = "service:jmx:rmi:///jndi/rmi://localhost:7199/jmxrmi";
@Option(title = "format", name = { "-f", "--format" }, description = "What format to dump content as; supported values are console (default), json, and yaml") @Option(paramLabel = "format", names = { "-f", "--format" }, description = "What format to dump content as; supported values are console (default), json, and yaml")
private Format format = Format.console; private Format format = Format.console;
public Void call() throws Exception public Void call() throws Exception
@ -188,41 +190,32 @@ public class JMXTool
} }
} }
@Command(name = "diff", description = "Diff two jmx dump files and report their differences") @Command(name = "diff", description = "Diff two jmx dump files and report their differences", mixinStandardHelpOptions = true)
public static final class Diff implements Callable<Void> public static final class Diff implements Callable<Void>
{ {
@Inject @CommandLine.Parameters(description = "Files to diff")
private HelpOption helpOption; private List<String> files;
@Arguments(title = "files", usage = "<left> <right>", description = "Files to diff") @Option(paramLabel = "format", names = { "-f", "--format" }, description = "What format the files are in; only support json and yaml as format")
private List<File> files;
@Option(title = "format", name = { "-f", "--format" }, description = "What format the files are in; only support json and yaml as format")
private Format format = Format.yaml; private Format format = Format.yaml;
@Option(title = "ignore left", name = { "--ignore-missing-on-left" }, description = "Ignore results missing on the left") @Option(paramLabel = "ignore_left", names = { "--ignore-missing-on-left" }, description = "Ignore results missing on the left")
private boolean ignoreMissingLeft; private boolean ignoreMissingLeft;
@Option(title = "ignore right", name = { "--ignore-missing-on-right" }, description = "Ignore results missing on the right") @Option(paramLabel = "ignore_right", names = { "--ignore-missing-on-right" }, description = "Ignore results missing on the right")
private boolean ignoreMissingRight; private boolean ignoreMissingRight;
@Option(title = "exclude objects", name = "--exclude-object", description @Option(paramLabel = "exclude_objects", names = "--exclude-object", converter = PatternConverter.class,
= "Ignores processing specific objects. " + description = "Ignores processing specific objects. Each usage should take a single object, but can use this flag multiple times.")
"Each usage should take a single object, " + private List<Pattern> excludeObjects = new ArrayList<>();
"but can use this flag multiple times.")
private List<CliPattern> excludeObjects = new ArrayList<>();
@Option(title = "exclude attributes", name = "--exclude-attribute", description @Option(paramLabel = "exclude_attributes", names = "--exclude-attribute", converter = PatternConverter.class,
= "Ignores processing specific attributes. " + description = "Ignores processing specific attributes. Each usage should take a single attribute, but can use this flag multiple times.")
"Each usage should take a single attribute, " + private List<Pattern> excludeAttributes = new ArrayList<>();
"but can use this flag multiple times.")
private List<CliPattern> excludeAttributes = new ArrayList<>();
@Option(title = "exclude operations", name = "--exclude-operation", description @Option(paramLabel = "exclud_operations", names = "--exclude-operation", converter = PatternConverter.class,
= "Ignores processing specific operations. " + description = "Ignores processing specific operations. Each usage should take a single operation, but can use this flag multiple times.")
"Each usage should take a single operation, " + private List<Pattern> excludeOperations = new ArrayList<>();
"but can use this flag multiple times.")
private List<CliPattern> excludeOperations = new ArrayList<>();
public Void call() throws Exception public Void call() throws Exception
{ {
@ -243,9 +236,9 @@ public class JMXTool
private void diff(Map<String, Info> left, Map<String, Info> right) private void diff(Map<String, Info> left, Map<String, Info> right)
{ {
DiffResult<String> objectNames = diff(left.keySet(), right.keySet(), name -> { DiffResult<String> objectNames = diff(left.keySet(), right.keySet(), name -> {
for (CliPattern p : excludeObjects) for (Pattern p : excludeObjects)
{ {
if (p.pattern.matcher(name).matches()) if (p.matcher(name).matches())
return false; return false;
} }
return true; return true;
@ -280,9 +273,9 @@ public class JMXTool
Info leftInfo = left.get(key); Info leftInfo = left.get(key);
Info rightInfo = right.get(key); Info rightInfo = right.get(key);
DiffResult<Attribute> attributes = diff(leftInfo.attributeSet(), rightInfo.attributeSet(), attribute -> { DiffResult<Attribute> attributes = diff(leftInfo.attributeSet(), rightInfo.attributeSet(), attribute -> {
for (CliPattern p : excludeAttributes) for (Pattern p : excludeAttributes)
{ {
if (p.pattern.matcher(attribute.name).matches()) if (p.matcher(attribute.name).matches())
return false; return false;
} }
return true; return true;
@ -301,10 +294,10 @@ public class JMXTool
} }
DiffResult<Operation> operations = diff(leftInfo.operationSet(), rightInfo.operationSet(), operation -> { DiffResult<Operation> operations = diff(leftInfo.operationSet(), rightInfo.operationSet(), operation -> {
for (CliPattern p : excludeOperations) for (Pattern p : excludeOperations)
{ {
if (p.pattern.matcher(operation.name).matches() || if (p.matcher(operation.name).matches() ||
p.pattern.matcher(operation.toString().replaceAll(" +", "")).matches()) p.matcher(operation.toString().replaceAll(" +", "")).matches())
return false; return false;
} }
return true; return true;
@ -844,24 +837,18 @@ public class JMXTool
} }
} }
public static final class CliPattern public static class PatternConverter implements CommandLine.ITypeConverter<Pattern>
{ {
private final Pattern pattern; @Override
public Pattern convert(String pattern) throws Exception
public CliPattern(String pattern)
{ {
this.pattern = Pattern.compile(pattern); return Pattern.compile(pattern);
} }
} }
public static void main(String[] args) throws Exception public static void main(String[] args) throws Exception
{ {
Cli.CliBuilder<Callable<Void>> builder = Cli.builder("jmxtool"); CommandLine commandLine = new CommandLine(JMXTool.class);
builder.withDefaultCommand(Help.class); commandLine.execute(args);
builder.withCommands(Help.class, Dump.class, Diff.class);
Cli<Callable<Void>> parser = builder.build();
Callable<Void> command = parser.parse(args);
command.call();
} }
} }

View File

@ -118,7 +118,6 @@ import org.apache.cassandra.service.AutoRepairService;
import org.apache.cassandra.service.AutoRepairServiceMBean; import org.apache.cassandra.service.AutoRepairServiceMBean;
import org.apache.cassandra.service.CacheService; import org.apache.cassandra.service.CacheService;
import org.apache.cassandra.service.CacheServiceMBean; import org.apache.cassandra.service.CacheServiceMBean;
import org.apache.cassandra.service.snapshot.SnapshotManagerMBean;
import org.apache.cassandra.service.GCInspector; import org.apache.cassandra.service.GCInspector;
import org.apache.cassandra.service.GCInspectorMXBean; import org.apache.cassandra.service.GCInspectorMXBean;
import org.apache.cassandra.service.StorageProxy; import org.apache.cassandra.service.StorageProxy;
@ -126,6 +125,7 @@ import org.apache.cassandra.service.StorageProxyMBean;
import org.apache.cassandra.service.StorageServiceMBean; import org.apache.cassandra.service.StorageServiceMBean;
import org.apache.cassandra.service.accord.AccordOperations; import org.apache.cassandra.service.accord.AccordOperations;
import org.apache.cassandra.service.accord.AccordOperationsMBean; import org.apache.cassandra.service.accord.AccordOperationsMBean;
import org.apache.cassandra.service.snapshot.SnapshotManagerMBean;
import org.apache.cassandra.streaming.StreamManagerMBean; import org.apache.cassandra.streaming.StreamManagerMBean;
import org.apache.cassandra.streaming.StreamState; import org.apache.cassandra.streaming.StreamState;
import org.apache.cassandra.streaming.management.StreamStateCompositeData; import org.apache.cassandra.streaming.management.StreamStateCompositeData;
@ -133,7 +133,6 @@ import org.apache.cassandra.tcm.CMSOperations;
import org.apache.cassandra.tcm.CMSOperationsMBean; import org.apache.cassandra.tcm.CMSOperationsMBean;
import org.apache.cassandra.tools.RepairRunner.RepairCmd; import org.apache.cassandra.tools.RepairRunner.RepairCmd;
import org.apache.cassandra.tools.nodetool.GetTimeout; import org.apache.cassandra.tools.nodetool.GetTimeout;
import org.apache.cassandra.tools.nodetool.formatter.TableBuilder;
import org.apache.cassandra.utils.NativeLibrary; import org.apache.cassandra.utils.NativeLibrary;
import static org.apache.cassandra.config.CassandraRelevantProperties.NODETOOL_JMX_NOTIFICATION_POLL_INTERVAL_SECONDS; import static org.apache.cassandra.config.CassandraRelevantProperties.NODETOOL_JMX_NOTIFICATION_POLL_INTERVAL_SECONDS;
@ -146,7 +145,7 @@ public class NodeProbe implements AutoCloseable
{ {
private static final String fmtUrl = "service:jmx:rmi:///jndi/rmi://%s:%d/jmxrmi"; private static final String fmtUrl = "service:jmx:rmi:///jndi/rmi://%s:%d/jmxrmi";
private static final String ssObjName = "org.apache.cassandra.db:type=StorageService"; private static final String ssObjName = "org.apache.cassandra.db:type=StorageService";
private static final int defaultPort = 7199; public static final int defaultPort = 7199;
static long JMX_NOTIFICATION_POLL_INTERVAL_SECONDS = NODETOOL_JMX_NOTIFICATION_POLL_INTERVAL_SECONDS.getLong(); static long JMX_NOTIFICATION_POLL_INTERVAL_SECONDS = NODETOOL_JMX_NOTIFICATION_POLL_INTERVAL_SECONDS.getLong();
@ -185,8 +184,7 @@ public class NodeProbe implements AutoCloseable
protected RolesCacheMBean rcProxy; protected RolesCacheMBean rcProxy;
protected AutoRepairServiceMBean autoRepairProxy; protected AutoRepairServiceMBean autoRepairProxy;
protected GuardrailsMBean grProxy; protected GuardrailsMBean grProxy;
protected Output output; protected volatile Output output;
private boolean failed;
protected CIDRFilteringMetricsTableMBean cfmProxy; protected CIDRFilteringMetricsTableMBean cfmProxy;
@ -469,9 +467,8 @@ public class NodeProbe implements AutoCloseable
jobName, ks); jobName, ks);
break; break;
case 2: case 2:
failed = true; out.printf("Failed marking some sstables compacting in keyspace %s, check server logs for more information.\n", ks);
out.printf("Failed marking some sstables compacting in keyspace %s, check server logs for more information.\n", throw new RuntimeException(String.format("Failed marking some sstables compacting in keyspace %s, check server logs for more information.\n", ks));
ks);
} }
} }
@ -479,8 +476,8 @@ public class NodeProbe implements AutoCloseable
{ {
if (garbageCollect(tombstoneOption, jobs, keyspaceName, tableNames) != 0) if (garbageCollect(tombstoneOption, jobs, keyspaceName, tableNames) != 0)
{ {
failed = true;
out.println("Aborted garbage collection for at least one table in keyspace " + keyspaceName + ", check server logs for more information."); out.println("Aborted garbage collection for at least one table in keyspace " + keyspaceName + ", check server logs for more information.");
throw new RuntimeException("Aborted garbage collection for at least one table in keyspace " + keyspaceName + ", check server logs for more information.");
} }
} }
@ -1819,16 +1816,6 @@ public class NodeProbe implements AutoCloseable
ssProxy.reloadLocalSchema(); ssProxy.reloadLocalSchema();
} }
public boolean isFailed()
{
return failed;
}
public void failed()
{
this.failed = true;
}
public long getReadRepairAttempted() public long getReadRepairAttempted()
{ {
return spProxy.getReadRepairAttempted(); return spProxy.getReadRepairAttempted();
@ -2546,21 +2533,6 @@ public class NodeProbe implements AutoCloseable
return ssProxy.getDefaultKeyspaceReplicationFactor(); return ssProxy.getDefaultKeyspaceReplicationFactor();
} }
public void printSet(PrintStream out, String colName, Set<String> values)
{
if (values == null || values.isEmpty())
return;
TableBuilder table = new TableBuilder();
table.add(colName + ": ");
for (String value : values)
table.add(value);
table.printTo(out);
}
public void abortBootstrap(String nodeId, String endpoint) public void abortBootstrap(String nodeId, String endpoint)
{ {
ssProxy.abortBootstrap(nodeId, endpoint); ssProxy.abortBootstrap(nodeId, endpoint);

View File

@ -17,54 +17,34 @@
*/ */
package org.apache.cassandra.tools; package org.apache.cassandra.tools;
import java.io.Console;
import java.io.FileNotFoundException;
import java.io.IOError; import java.io.IOError;
import java.io.IOException; import java.io.IOException;
import java.net.UnknownHostException; import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map; import javax.inject.Inject;
import java.util.Map.Entry;
import java.util.Scanner;
import java.util.SortedMap;
import javax.management.InstanceNotFoundException; import javax.management.InstanceNotFoundException;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
import com.google.common.base.Throwables; import com.google.common.base.Throwables;
import com.google.common.collect.Maps; import org.apache.cassandra.config.CassandraRelevantEnv;
import org.apache.cassandra.config.CassandraRelevantProperties;
import io.airlift.airline.Cli; import org.apache.cassandra.exceptions.ConfigurationException;
import io.airlift.airline.Help;
import io.airlift.airline.Option;
import io.airlift.airline.OptionType;
import io.airlift.airline.ParseArgumentsMissingException;
import io.airlift.airline.ParseArgumentsUnexpectedException;
import io.airlift.airline.ParseCommandMissingException;
import io.airlift.airline.ParseCommandUnrecognizedException;
import io.airlift.airline.ParseOptionConversionException;
import io.airlift.airline.ParseOptionMissingException;
import io.airlift.airline.ParseOptionMissingValueException;
import org.apache.cassandra.io.util.File; import org.apache.cassandra.io.util.File;
import org.apache.cassandra.io.util.FileWriter; import org.apache.cassandra.io.util.FileWriter;
import org.apache.cassandra.locator.EndpointSnitchInfoMBean; import org.apache.cassandra.tools.nodetool.JmxConnect;
import org.apache.cassandra.tools.nodetool.*; import org.apache.cassandra.tools.nodetool.NodetoolCommand;
import org.apache.cassandra.tools.nodetool.layout.CassandraCliHelpLayout;
import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.FBUtilities;
import picocli.CommandLine;
import static com.google.common.base.Throwables.getStackTraceAsString; import static com.google.common.base.Throwables.getStackTraceAsString;
import static com.google.common.collect.Iterables.toArray;
import static com.google.common.collect.Lists.newArrayList;
import static java.lang.Integer.parseInt;
import static java.lang.String.format;
import static org.apache.cassandra.io.util.File.WriteMode.APPEND; import static org.apache.cassandra.io.util.File.WriteMode.APPEND;
import static org.apache.commons.lang3.ArrayUtils.EMPTY_STRING_ARRAY; import static org.apache.cassandra.utils.LocalizeString.toUpperCaseLocalized;
import static org.apache.commons.lang3.StringUtils.EMPTY;
import static org.apache.commons.lang3.StringUtils.isEmpty;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
public class NodeTool public class NodeTool
{ {
@ -89,237 +69,59 @@ public class NodeTool
this.output = output; this.output = output;
} }
/**
* Execute the command line utility with the given arguments via the JMX connection.
*
* @param args command line arguments
* @return 0 on success, 1 on bad use, 2 on execution error
*/
public int execute(String... args) public int execute(String... args)
{ {
List<Class<? extends NodeToolCmdRunnable>> commands = newArrayList(
AbortBootstrap.class,
AlterTopology.class,
Assassinate.class,
AutoRepairStatus.class,
CassHelp.class,
CIDRFilteringStats.class,
Cleanup.class,
ClearSnapshot.class,
ClientStats.class,
Compact.class,
CompactionHistory.class,
CompactionStats.class,
DataPaths.class,
Decommission.class,
Decommission.Abort.class,
DescribeCluster.class,
DescribeRing.class,
DisableAuditLog.class,
DisableAutoCompaction.class,
DisableBackup.class,
DisableBinary.class,
DisableFullQueryLog.class,
DisableGossip.class,
DisableHandoff.class,
DisableHintsForDC.class,
DisableOldProtocolVersions.class,
Drain.class,
DropCIDRGroup.class,
EnableAuditLog.class,
EnableAutoCompaction.class,
EnableBackup.class,
EnableBinary.class,
EnableFullQueryLog.class,
EnableGossip.class,
EnableHandoff.class,
EnableHintsForDC.class,
EnableOldProtocolVersions.class,
FailureDetectorInfo.class,
Flush.class,
GarbageCollect.class,
GcStats.class,
GetAuditLog.class,
GetAuthCacheConfig.class,
GetAutoRepairConfig.class,
GetBatchlogReplayTrottle.class,
GetCIDRGroupsOfIP.class,
GetColumnIndexSize.class,
GetCompactionThreshold.class,
GetCompactionThroughput.class,
GetConcurrency.class,
GetConcurrentCompactors.class,
GetConcurrentViewBuilders.class,
GetDefaultKeyspaceRF.class,
GetEndpoints.class,
GetFullQueryLog.class,
GuardrailsConfigCommand.GetGuardrailsConfig.class,
GetInterDCStreamThroughput.class,
GetLoggingLevels.class,
GetMaxHintWindow.class,
GetSSTables.class,
GetSeeds.class,
GetSnapshotThrottle.class,
GetStreamThroughput.class,
GetTimeout.class,
GetTraceProbability.class,
GossipInfo.class,
Import.class,
Info.class,
InvalidateCIDRPermissionsCache.class,
InvalidateCounterCache.class,
InvalidateCredentialsCache.class,
InvalidateJmxPermissionsCache.class,
ReloadCIDRGroupsCache.class,
InvalidateKeyCache.class,
InvalidateNetworkPermissionsCache.class,
InvalidatePermissionsCache.class,
InvalidateRolesCache.class,
InvalidateRowCache.class,
Join.class,
ListCIDRGroups.class,
ListPendingHints.class,
ListSnapshots.class,
Move.class,
Move.Abort.class,
NetStats.class,
PauseHandoff.class,
ProfileLoad.class,
ProxyHistograms.class,
RangeKeySample.class,
Rebuild.class,
RebuildIndex.class,
RecompressSSTables.class,
Refresh.class,
RefreshSizeEstimates.class,
ReloadLocalSchema.class,
ReloadSeeds.class,
ReloadSslCertificates.class,
ReloadTriggers.class,
RelocateSSTables.class,
RemoveNode.class,
RemoveNode.Abort.class,
Repair.class,
ReplayBatchlog.class,
ResetFullQueryLog.class,
ResetLocalSchema.class,
ResumeHandoff.class,
Ring.class,
Scrub.class,
SetAuthCacheConfig.class,
SetAutoRepairConfig.class,
SetBatchlogReplayThrottle.class,
SetCacheCapacity.class,
SetCacheKeysToSave.class,
SetColumnIndexSize.class,
SetCompactionThreshold.class,
SetCompactionThroughput.class,
SetConcurrency.class,
SetConcurrentCompactors.class,
SetConcurrentViewBuilders.class,
SetDefaultKeyspaceRF.class,
GuardrailsConfigCommand.SetGuardrailsConfig.class,
SetHintedHandoffThrottleInKB.class,
SetInterDCStreamThroughput.class,
SetLoggingLevel.class,
SetMaxHintWindow.class,
SetSnapshotThrottle.class,
SetStreamThroughput.class,
SetTimeout.class,
SetTraceProbability.class,
Sjk.class,
Snapshot.class,
SSTableRepairedSet.class,
Status.class,
StatusAutoCompaction.class,
StatusBackup.class,
StatusBinary.class,
StatusGossip.class,
StatusHandoff.class,
Stop.class,
StopDaemon.class,
TableHistograms.class,
TableStats.class,
TopPartitions.class,
TpStats.class,
TruncateHints.class,
UpdateCIDRGroup.class,
UpgradeSSTable.class,
Verify.class,
Version.class,
ViewBuildStatus.class,
ForceCompact.class
);
Cli.CliBuilder<NodeToolCmdRunnable> builder = Cli.builder("nodetool");
builder.withDescription("Manage your Cassandra cluster")
.withDefaultCommand(CassHelp.class)
.withCommands(commands);
// bootstrap commands
builder.withGroup("bootstrap")
.withDescription("Monitor/manage node's bootstrap process")
.withDefaultCommand(CassHelp.class)
.withCommand(BootstrapResume.class);
builder.withGroup("repair_admin")
.withDescription("list and fail incremental repair sessions")
.withDefaultCommand(RepairAdmin.ListCmd.class)
.withCommand(RepairAdmin.ListCmd.class)
.withCommand(RepairAdmin.CancelCmd.class)
.withCommand(RepairAdmin.CleanupDataCmd.class)
.withCommand(RepairAdmin.SummarizePendingCmd.class)
.withCommand(RepairAdmin.SummarizeRepairedCmd.class);
builder.withGroup("cms")
.withDescription("Manage cluster metadata")
.withDefaultCommand(CMSAdmin.DescribeCMS.class)
.withCommand(CMSAdmin.DescribeCMS.class)
.withCommand(CMSAdmin.InitializeCMS.class)
.withCommand(CMSAdmin.ReconfigureCMS.class)
.withCommand(CMSAdmin.Snapshot.class)
.withCommand(CMSAdmin.Unregister.class)
.withCommand(CMSAdmin.AbortInitialization.class)
.withCommand(CMSAdmin.DumpDirectory.class)
.withCommand(CMSAdmin.DumpLog.class)
.withCommand(CMSAdmin.ResumeDropAccordTable.class);
builder.withGroup("consensus_admin")
.withDescription("List and mark ranges as migrating between consensus protocols")
.withDefaultCommand(CassHelp.class)
.withCommand(ConsensusMigrationAdmin.BeginMigration.class)
.withCommands(ConsensusMigrationAdmin.ListCmd.class)
.withCommands(ConsensusMigrationAdmin.FinishMigration.class);
builder.withGroup("accord")
.withDescription("Manage the operation of Accord")
.withDefaultCommand(AccordAdmin.Describe.class)
.withCommand(AccordAdmin.Describe.class)
.withCommand(AccordAdmin.MarkStale.class)
.withCommand(AccordAdmin.MarkRejoining.class);
Cli<NodeToolCmdRunnable> parser = builder.build();
int status = 0;
try try
{ {
NodeToolCmdRunnable parse = parser.parse(args); CommandLine commandLine = createCommandLine(new CassandraCliFactory(nodeProbeFactory, output));
commandLine.setOut(new PrintWriter(output.out, true));
commandLine.setErr(new PrintWriter(output.err, true));
configureCliLayout(commandLine);
commandLine.setExecutionStrategy(JmxConnect::executionStrategy)
.setExecutionExceptionHandler((ex, c, arg) -> {
// Used for backward compatibility, some commands are validated when a command is run.
if (ex instanceof IllegalArgumentException |
ex instanceof IllegalStateException)
{
badUse(ex);
return 1;
}
err(Throwables.getRootCause(ex));
return 2;
})
.setParameterExceptionHandler((ex, arg) -> {
badUse(ex);
return 1;
})
// Some of the Cassandra commands don't comply with the POSIX standard, so we need to disable such options.
// Example: ./nodetool -h localhost -p 7100 repair mykeyspayce -hosts 127.0.0.1,127.0.0.2
//
// This also means that option parameters must be separated from the option name by whitespace
// or the = separator character, so -D key=value and -D=key=value will be recognized but
// -Dkey=value will not.
.setPosixClusteredShortOptionsAllowed(false);
printHistory(args); printHistory(args);
parse.run(nodeProbeFactory, output); return commandLine.execute(args);
} catch (IllegalArgumentException | }
IllegalStateException | catch (ConfigurationException e)
ParseArgumentsMissingException |
ParseArgumentsUnexpectedException |
ParseOptionConversionException |
ParseOptionMissingException |
ParseOptionMissingValueException |
ParseCommandMissingException |
ParseCommandUnrecognizedException e)
{ {
badUse(e); badUse(e);
status = 1; return 1;
} catch (Throwable throwable) }
{ catch (Throwable e)
err(Throwables.getRootCause(throwable)); {
status = 2; err(Throwables.getRootCause(e));
return 2;
} }
return status;
} }
private static void printHistory(String... args) private static void printHistory(String... args)
@ -342,6 +144,59 @@ public class NodeTool
} }
} }
public static List<String> getCommandsWithoutRoot(String separator)
{
List<String> commands = new ArrayList<>();
try
{
getCommandsWithoutRoot(createCommandLine(new CassandraCliFactory(new NodeProbeFactory(), Output.CONSOLE)), commands, separator);
return commands;
}
catch (Exception e)
{
throw new RuntimeException("Failed to initialize command line hierarchy", e);
}
}
private static void getCommandsWithoutRoot(CommandLine cli, List<String> commands, String separator)
{
String name = cli.getCommandSpec().qualifiedName(separator);
// Skip the root command as it's not a real command.
if (cli.getCommandSpec().root() != cli.getCommandSpec())
commands.add(name.replace(cli.getCommandSpec().root().qualifiedName() + separator, ""));
for (CommandLine sub : cli.getSubcommands().values())
getCommandsWithoutRoot(sub, commands, separator);
}
public static CommandLine createCommandLine(CommandLine.IFactory factory) throws Exception
{
return new CommandLine(new NodetoolCommand(), factory)
.addMixin(JmxConnect.MIXIN_KEY, factory.create(JmxConnect.class));
}
private static void configureCliLayout(CommandLine commandLine)
{
CliLayout defaultLayout = CliLayout.valueOf(toUpperCaseLocalized(CassandraRelevantProperties.CASSANDRA_CLI_LAYOUT.getDefaultValue()));
CliLayout layoutEnv = CassandraRelevantEnv.CASSANDRA_CLI_LAYOUT.getEnum(true, CliLayout.class,
CassandraRelevantProperties.CASSANDRA_CLI_LAYOUT.getDefaultValue());
CliLayout layoutSys = CassandraRelevantProperties.CASSANDRA_CLI_LAYOUT.getEnum(true, CliLayout.class);
CliLayout layout = layoutEnv != defaultLayout ? layoutEnv : layoutSys;
switch (layout)
{
case AIRLINE:
commandLine.setHelpFactory(CassandraCliHelpLayout::new)
.setUsageHelpWidth(CassandraCliHelpLayout.DEFAULT_USAGE_HELP_WIDTH)
.setHelpSectionKeys(CassandraCliHelpLayout.cassandraHelpSectionKeys());
break;
case PICOCLI:
break;
default:
throw new IllegalStateException("Unknown CLI layout: " + layout);
}
}
protected void badUse(Exception e) protected void badUse(Exception e)
{ {
output.out.println("nodetool: " + e.getMessage()); output.out.println("nodetool: " + e.getMessage());
@ -359,208 +214,63 @@ public class NodeTool
output.err.println(getStackTraceAsString(e)); output.err.println(getStackTraceAsString(e));
} }
public static class CassHelp extends Help implements NodeToolCmdRunnable private enum CliLayout
{ {
public void run(INodeProbeFactory nodeProbeFactory, Output output) AIRLINE,
{ PICOCLI
run();
}
} }
interface NodeToolCmdRunnable private static class CassandraCliFactory implements CommandLine.IFactory
{ {
void run(INodeProbeFactory nodeProbeFactory, Output output); private final CommandLine.IFactory fallback;
} private final INodeProbeFactory nodeProbeFactory;
private final Output output;
public static abstract class NodeToolCmd implements NodeToolCmdRunnable public CassandraCliFactory(INodeProbeFactory nodeProbeFactory, Output output)
{
@Option(type = OptionType.GLOBAL, name = {"-h", "--host"}, description = "Node hostname or ip address")
private String host = "127.0.0.1";
@Option(type = OptionType.GLOBAL, name = {"-p", "--port"}, description = "Remote jmx agent port number")
private String port = "7199";
@Option(type = OptionType.GLOBAL, name = {"-u", "--username"}, description = "Remote jmx agent username")
private String username = EMPTY;
@Option(type = OptionType.GLOBAL, name = {"-pw", "--password"}, description = "Remote jmx agent password")
private String password = EMPTY;
@Option(type = OptionType.GLOBAL, name = {"-pwf", "--password-file"}, description = "Path to the JMX password file")
private String passwordFilePath = EMPTY;
@Option(type = OptionType.GLOBAL, name = { "-pp", "--print-port"}, description = "Operate in 4.0 mode with hosts disambiguated by port number", arity = 0)
protected boolean printPort = false;
private INodeProbeFactory nodeProbeFactory;
protected Output output;
@Override
public void run(INodeProbeFactory nodeProbeFactory, Output output)
{ {
this.fallback = CommandLine.defaultFactory();
this.nodeProbeFactory = nodeProbeFactory; this.nodeProbeFactory = nodeProbeFactory;
this.output = output; this.output = output;
runInternal();
} }
public void runInternal() public <K> K create(Class<K> cls)
{ {
if (isNotEmpty(username)) {
if (isNotEmpty(passwordFilePath))
password = readUserPasswordFromFile(username, passwordFilePath);
if (isEmpty(password))
password = promptAndReadPassword();
}
try (NodeProbe probe = connect())
{
execute(probe);
if (probe.isFailed())
throw new RuntimeException("nodetool failed, check server logs");
}
catch (IOException e)
{
throw new RuntimeException("Error while closing JMX connection", e);
}
}
private String readUserPasswordFromFile(String username, String passwordFilePath) {
String password = EMPTY;
File passwordFile = new File(passwordFilePath);
try (Scanner scanner = new Scanner(passwordFile.toJavaIOFile()).useDelimiter("\\s+"))
{
while (scanner.hasNextLine())
{
if (scanner.hasNext())
{
String jmxRole = scanner.next();
if (jmxRole.equals(username) && scanner.hasNext())
{
password = scanner.next();
break;
}
}
scanner.nextLine();
}
}
catch (FileNotFoundException e)
{
throw new RuntimeException(e);
}
return password;
}
private String promptAndReadPassword()
{
String password = EMPTY;
Console console = System.console();
if (console != null)
password = String.valueOf(console.readPassword("Password:"));
return password;
}
protected abstract void execute(NodeProbe probe);
private NodeProbe connect()
{
NodeProbe nodeClient = null;
try try
{ {
if (username.isEmpty()) K bean = this.fallback.create(cls);
nodeClient = nodeProbeFactory.create(host, parseInt(port)); Class<?> beanClass = bean.getClass();
else do
nodeClient = nodeProbeFactory.create(host, parseInt(port), username, password); {
Field[] fields = beanClass.getDeclaredFields();
nodeClient.setOutput(output); for (Field field : fields)
} catch (IOException | SecurityException e) {
{ if (!field.isAnnotationPresent(Inject.class))
Throwable rootCause = Throwables.getRootCause(e); continue;
output.err.println(format("nodetool: Failed to connect to '%s:%s' - %s: '%s'.", host, port, rootCause.getClass().getSimpleName(), rootCause.getMessage())); if (field.getType().equals(INodeProbeFactory.class))
System.exit(1); {
field.setAccessible(true);
field.set(bean, nodeProbeFactory);
}
else if (field.getType().equals(Output.class))
{
field.setAccessible(true);
field.set(bean, output);
}
else
{
throw new RuntimeException("Unsupported injectable field type: " + field.getType() +
" in class " + beanClass.getName() + ". " +
"Only INodeProbeFactory and Output are supported.");
}
}
}
while ((beanClass = beanClass.getSuperclass()) != null);
return bean;
} }
catch (Exception e)
return nodeClient;
}
protected enum KeyspaceSet
{
ALL, NON_SYSTEM, NON_LOCAL_STRATEGY, ACCORD_MANAGED
}
protected List<String> parseOptionalKeyspace(List<String> cmdArgs, NodeProbe nodeProbe)
{
return parseOptionalKeyspace(cmdArgs, nodeProbe, KeyspaceSet.ALL);
}
protected List<String> parseOptionalKeyspace(List<String> cmdArgs, NodeProbe nodeProbe, KeyspaceSet defaultKeyspaceSet)
{
List<String> keyspaces = new ArrayList<>();
if (cmdArgs == null || cmdArgs.isEmpty())
{ {
if (defaultKeyspaceSet == KeyspaceSet.NON_LOCAL_STRATEGY) throw new CommandLine.InitializationException("Failed to create instance of " + cls, e);
keyspaces.addAll(keyspaces = nodeProbe.getNonLocalStrategyKeyspaces());
else if (defaultKeyspaceSet == KeyspaceSet.NON_SYSTEM)
keyspaces.addAll(keyspaces = nodeProbe.getNonSystemKeyspaces());
else if (defaultKeyspaceSet == KeyspaceSet.ACCORD_MANAGED)
keyspaces.addAll(nodeProbe.getAccordManagedKeyspaces());
else
keyspaces.addAll(nodeProbe.getKeyspaces());
}
else
{
keyspaces.add(cmdArgs.get(0));
}
for (String keyspace : keyspaces)
{
if (!nodeProbe.getKeyspaces().contains(keyspace))
throw new IllegalArgumentException("Keyspace [" + keyspace + "] does not exist.");
}
return Collections.unmodifiableList(keyspaces);
}
protected String[] parseOptionalTables(List<String> cmdArgs)
{
return cmdArgs.size() <= 1 ? EMPTY_STRING_ARRAY : toArray(cmdArgs.subList(1, cmdArgs.size()), String.class);
}
protected String[] parsePartitionKeys(List<String> cmdArgs)
{
return cmdArgs.size() <= 2 ? EMPTY_STRING_ARRAY : toArray(cmdArgs.subList(2, cmdArgs.size()), String.class);
}
}
public static SortedMap<String, SetHostStatWithPort> getOwnershipByDcWithPort(NodeProbe probe, boolean resolveIp,
Map<String, String> tokenToEndpoint,
Map<String, Float> ownerships)
{
SortedMap<String, SetHostStatWithPort> ownershipByDc = Maps.newTreeMap();
EndpointSnitchInfoMBean epSnitchInfo = probe.getEndpointSnitchInfoProxy();
try
{
for (Entry<String, String> tokenAndEndPoint : tokenToEndpoint.entrySet())
{
String dc = epSnitchInfo.getDatacenter(tokenAndEndPoint.getValue());
if (!ownershipByDc.containsKey(dc))
ownershipByDc.put(dc, new SetHostStatWithPort(resolveIp));
ownershipByDc.get(dc).add(tokenAndEndPoint.getKey(), tokenAndEndPoint.getValue(), ownerships);
} }
} }
catch (UnknownHostException e)
{
throw new RuntimeException(e);
}
return ownershipByDc;
} }
} }

View File

@ -32,4 +32,14 @@ public class Output
this.out = out; this.out = out;
this.err = err; this.err = err;
} }
public void printInfo(String msg, Object... args)
{
out.printf(msg, args);
}
public void printError(String msg, Object... args)
{
err.printf(msg, args);
}
} }

View File

@ -21,9 +21,19 @@ package org.apache.cassandra.tools;
import accord.local.RedundantBefore; import accord.local.RedundantBefore;
import accord.primitives.Timestamp; import accord.primitives.Timestamp;
import accord.primitives.TxnId; import accord.primitives.TxnId;
import io.airlift.airline.Cli;
import io.airlift.airline.Command; import java.io.IOException;
import io.airlift.airline.Option; import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.cassandra.config.AccordSpec; import org.apache.cassandra.config.AccordSpec;
import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.config.DurationSpec; import org.apache.cassandra.config.DurationSpec;
@ -44,17 +54,9 @@ import org.apache.cassandra.service.accord.JournalKey;
import org.apache.cassandra.service.accord.serializers.Version; import org.apache.cassandra.service.accord.serializers.Version;
import org.apache.cassandra.tcm.ClusterMetadataService; import org.apache.cassandra.tcm.ClusterMetadataService;
import java.io.IOException; import picocli.CommandLine;
import java.nio.file.FileSystems; import picocli.CommandLine.Command;
import java.nio.file.Files; import picocli.CommandLine.Option;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import static accord.api.Journal.Load.ALL; import static accord.api.Journal.Load.ALL;
import static com.google.common.base.Throwables.getStackTraceAsString; import static com.google.common.base.Throwables.getStackTraceAsString;
@ -91,7 +93,11 @@ import static org.apache.cassandra.config.DatabaseDescriptor.setAccordJournalDir
* *
**/ **/
public class StandaloneJournalUtil @Command(name = "journal_util",
mixinStandardHelpOptions = true,
description = "Standalone Journal Util",
subcommands = { StandaloneJournalUtil.DumpSegments.class, StandaloneJournalUtil.DumpJournal.class })
public class StandaloneJournalUtil implements Runnable
{ {
private static final Output output = Output.CONSOLE; private static final Output output = Output.CONSOLE;
@ -101,27 +107,22 @@ public class StandaloneJournalUtil
DatabaseDescriptor.setPartitioner("org.apache.cassandra.dht.Murmur3Partitioner"); DatabaseDescriptor.setPartitioner("org.apache.cassandra.dht.Murmur3Partitioner");
AccordKeyspace.TABLES = Tables.of(AccordKeyspace.journalMetadata("journal", false)); AccordKeyspace.TABLES = Tables.of(AccordKeyspace.journalMetadata("journal", false));
ClusterMetadataService.empty(Keyspaces.of(AccordKeyspace.metadata())); ClusterMetadataService.empty(Keyspaces.of(AccordKeyspace.metadata()));
Cli.CliBuilder<Runnable> builder = Cli.builder("util");
builder.withDescription("Dump journal").withCommand(DumpSegments.class).withCommand(DumpJournal.class);
Cli<Runnable> parser = builder.build();
int status = 0;
try
{
Runnable parse = parser.parse(args);
parse.run();
}
catch (Throwable throwable)
{
err(throwable);
status = 2;
}
CommandLine cli = new CommandLine(StandaloneJournalUtil.class)
.setExecutionExceptionHandler((ex, cmd, parseResult) -> {
err(ex);
return 2;
});
int status = cli.execute(args);
System.exit(status); System.exit(status);
} }
@Override
public void run()
{
CommandLine.usage(this, output.out);
}
protected static void err(Throwable e) protected static void err(Throwable e)
{ {
output.err.println("error: " + e.getMessage()); output.err.println("error: " + e.getMessage());
@ -132,19 +133,19 @@ public class StandaloneJournalUtil
@Command(name = "dump_segments", description = "Dump journal segments") @Command(name = "dump_segments", description = "Dump journal segments")
public static class DumpSegments implements Runnable public static class DumpSegments implements Runnable
{ {
@Option(name = {"-d", "--dir"}, description = "Directory to find journal segments") @Option(names = {"-d", "--dir"}, description = "Directory to find journal segments")
public String dir; public String dir;
@Option(name = {"-p", "--pattern"}, description = "Kind to filter by") @Option(names = {"-p", "--pattern"}, description = "Kind to filter by")
public String pattern; public String pattern;
@Option(name = {"-k", "--kind"}, description = "Kind to filter by") @Option(names = {"-k", "--kind"}, description = "Kind to filter by")
public String kind; public String kind;
@Option(name = {"-t", "--txnid"}, description = "Transaction id to filter by") @Option(names = {"-t", "--txnid"}, description = "Transaction id to filter by")
public String txnId; public String txnId;
@Option(name = {"-m", "--metadata-only"}, description = "Only dump metadata file contents") @Option(names = {"-m", "--metadata-only"}, description = "Only dump metadata file contents")
public boolean metadataOnly; public boolean metadataOnly;
public void run() public void run()
@ -173,7 +174,8 @@ public class StandaloneJournalUtil
} }
catch (Throwable t) catch (Throwable t)
{ {
t.printStackTrace(output.err); throw new RuntimeException(String.format("Error reading key %s in segment %s at position %d: %s",
key, segment1, position, t.getMessage()), t);
} }
}); });
}); });
@ -189,28 +191,28 @@ public class StandaloneJournalUtil
@Command(name = "dump_journal", description = "Dump journal") @Command(name = "dump_journal", description = "Dump journal")
public static class DumpJournal implements Runnable public static class DumpJournal implements Runnable
{ {
@Option(name = {"-s", "--sstables"}, description = "Path to sstables") @Option(names = {"-s", "--sstables"}, description = "Path to sstables")
public String sstables; public String sstables;
@Option(name = {"-j", "--journal-segments"}, description = "Path to journal segments") @Option(names = {"-j", "--journal-segments"}, description = "Path to journal segments")
public String journalSegments; public String journalSegments;
@Option(name = {"-k", "--kind"}, description = "Kind to filter by") @Option(names = {"-k", "--kind"}, description = "Kind to filter by")
public String kind; public String kind;
@Option(name = {"-t", "--txnid"}, description = "Transaction id to filter by") @Option(names = {"-t", "--txnid"}, description = "Transaction id to filter by")
public String txnId; public String txnId;
@Option(name = {"--since"}, description = "Filter transactions since this timestamp (inclusive)") @Option(names = {"--since"}, description = "Filter transactions since this timestamp (inclusive)")
public String since; public String since;
@Option(name = {"--until"}, description = "Filter transactions until this timestamp (inclusive)") @Option(names = {"--until"}, description = "Filter transactions until this timestamp (inclusive)")
public String until; public String until;
@Option(name = {"-e", "--skip-errors"}, description = "Skip errors: 'true' to skip all, or comma-separated exception class names to skip specific types") @Option(names = {"-e", "--skip-errors"}, description = "Skip errors: 'true' to skip all, or comma-separated exception class names to skip specific types")
public String skipErrors; public String skipErrors;
@Option(name = {"-c", "--construct"}, description = "Construct entry") @Option(names = {"-c", "--construct"}, description = "Construct entry")
public boolean construct; public boolean construct;
@ -345,8 +347,7 @@ public class StandaloneJournalUtil
if (!shouldSkip) if (!shouldSkip)
throw t; throw t;
output.out.println(String.format("Got error reading key %s", key)); throw new RuntimeException(String.format("Error reading key %s: %s", key, t.getMessage()), t);
t.printStackTrace();
} }
} }
} }

View File

@ -17,25 +17,22 @@
*/ */
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Command;
import io.airlift.airline.Option;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import static org.apache.commons.lang3.StringUtils.EMPTY; import static org.apache.commons.lang3.StringUtils.EMPTY;
import static org.apache.commons.lang3.StringUtils.isEmpty; import static org.apache.commons.lang3.StringUtils.isEmpty;
@Command(name = "abortbootstrap", description = "Abort a failed bootstrap") @Command(name = "abortbootstrap", description = "Abort a failed bootstrap")
public class AbortBootstrap extends NodeToolCmd public class AbortBootstrap extends AbstractCommand
{ {
@Option(title = "node id", name = "--node", description = "Node ID of the node that failed bootstrap", required = false) @Option(paramLabel = "node_id", names = "--node", description = "Node ID of the node that failed bootstrap")
private String nodeId = EMPTY; private String nodeId = EMPTY;
@Option(title = "ip", name = "--ip", description = "IP of the node that failed bootstrap", required = false) @Option(paramLabel = "ip", names = "--ip", description = "IP of the node that failed bootstrap")
private String endpoint = EMPTY; private String endpoint = EMPTY;
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)
{ {

View File

@ -0,0 +1,104 @@
/*
* 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 javax.inject.Inject;
import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.Output;
import picocli.CommandLine;
import picocli.CommandLine.ExecutionException;
/**
* Abstract class for all nodetool commands, which provides common methods and fields
* for running commands and outputting results.
* <p>
* The command is executed by calling {@link #execute(NodeProbe)}, in all other cases
* it should not contain any fields or methods that are specific to a particular API
* being executed, or common methods that are shared across multiple commands.
* <p>
* Commands must work only with the API provided by the {@link NodeProbe} instance.
*/
public abstract class AbstractCommand implements Runnable
{
@Inject
protected Output output;
private NodeProbe probe;
public void probe(NodeProbe probe)
{
this.probe = probe;
}
public NodeProbe probe()
{
return probe;
}
public void logger(Output output)
{
this.output = output;
}
@Override
public void run()
{
execute(probe());
}
/**
* Prepare a command for execution. This method is called before the command is executed and
* can be used to perform any necessary setup or validation. If this method returns {@code false},
* the command will not initiate connection and will be executed locally. The default implementation
* returns {@code true} so that the command initiates connection to the node before execution.
*
* @return {@code true} if the command is required to connect to the node, {@code false} otherwise.
* @throws ExecutionException if an error occurs during preparation and execution must be aborted.
*/
protected boolean shouldConnect() throws ExecutionException
{
return true;
}
/**
* Execute the command using the supplied {@link NodeProbe} instance, which is already connected
* to the node and ready to use. If the {@link NodeProbe} is not {@code null}, it is guaranteed that it
* is connected to the node, and the command can use it to perform operations on the node.
* <p>
* WARNING:
* <p>
* Due to the backwards compatibility with the previous Airline-based implementation of
* the nodetool commands, for most of the commands this method is also used to validate the input
* arguments and perform necessary checks before the command is executed. This implies that
* the command <u>throws an exception during execution</u> to avoid unexpected behavior or errors,
* instead of validating the input arguments within the Picocli framework on the Parser stage.
* For this reason, the {@link CommandLine.Parameters#arity()} and {@link CommandLine.Option#arity()}
* which are normally used to validate the input arguments, set to {@code "0..*"} or {@code "0..1"}
* making the arguments optional, and passing the validation to the command's
* {@code execute(NodeProbe probe)} method.
* <p>
* New commands should not rely on the behavior described above and should validate the input arguments
* using Picocli annotations such as {@link CommandLine.Parameters} and {@link CommandLine.Option}.
*
* @param probe The {@link NodeProbe} instance to use, or {@code null} if no connection is required.
*/
protected abstract void execute(NodeProbe probe);
}

View File

@ -21,15 +21,30 @@ package org.apache.cassandra.tools.nodetool;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import io.airlift.airline.Arguments;
import io.airlift.airline.Command;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool; import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;
public abstract class AccordAdmin extends NodeTool.NodeToolCmd @Command(name = "accord",
description = "Manage the operation of Accord",
subcommands = {
AccordAdmin.Describe.class,
AccordAdmin.MarkStale.class,
AccordAdmin.MarkRejoining.class
})
public class AccordAdmin extends AbstractCommand
{ {
@Override
protected void execute(NodeProbe probe)
{
AbstractCommand cmd = new AccordAdmin.Describe();
cmd.probe(probe);
cmd.logger(output);
cmd.run();
}
@Command(name = "describe", description = "Describe current cluster metadata relating to Accord") @Command(name = "describe", description = "Describe current cluster metadata relating to Accord")
public static class Describe extends NodeTool.NodeToolCmd public static class Describe extends AbstractCommand
{ {
@Override @Override
protected void execute(NodeProbe probe) protected void execute(NodeProbe probe)
@ -42,9 +57,9 @@ public abstract class AccordAdmin extends NodeTool.NodeToolCmd
} }
@Command(name = "mark_stale", description = "Mark a replica as being stale and no longer able to participate in durability status coordination") @Command(name = "mark_stale", description = "Mark a replica as being stale and no longer able to participate in durability status coordination")
public static class MarkStale extends AccordAdmin public static class MarkStale extends AbstractCommand
{ {
@Arguments(required = true, description = "One or more node IDs to mark stale", usage = "<nodeId>+") @Parameters(arity = "1..*", description = "One or more node IDs to mark stale")
public List<String> nodeIds; public List<String> nodeIds;
@Override @Override
@ -55,9 +70,9 @@ public abstract class AccordAdmin extends NodeTool.NodeToolCmd
} }
@Command(name = "mark_rejoining", description = "Mark a stale replica as being allowed to participate in durability status coordination again") @Command(name = "mark_rejoining", description = "Mark a stale replica as being allowed to participate in durability status coordination again")
public static class MarkRejoining extends AccordAdmin public static class MarkRejoining extends AbstractCommand
{ {
@Arguments(required = true, description = "One or more node IDs to mark no longer stale", usage = "<nodeId>+") @Parameters(arity = "1", description = "One or more node IDs to mark no longer stale")
public List<String> nodeIds; public List<String> nodeIds;
@Override @Override

View File

@ -20,17 +20,16 @@ package org.apache.cassandra.tools.nodetool;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import io.airlift.airline.Arguments;
import io.airlift.airline.Command;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
@Command(name = "altertopology", description = "Modify the datacenter and/or rack of one or more nodes") @Command(name = "altertopology", description = "Modify the datacenter and/or rack of one or more nodes")
public class AlterTopology extends NodeToolCmd public class AlterTopology extends AbstractCommand
{ {
@Arguments(usage = "<node=dc:rack> [<node=dc:rack>...]", description = "One or more node identifiers, which may be either a node id, host id or broadcast address, each with a target dc:rack") @Parameters(description = { "One or more node identifiers, which may be either a node id, host id or broadcast address, each with a target dc:rack",
"<node=dc:rack> [<node=dc:rack>...]" })
private List<String> args = new ArrayList<>(); private List<String> args = new ArrayList<>();
@Override @Override

View File

@ -17,19 +17,18 @@
*/ */
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import static org.apache.commons.lang3.StringUtils.EMPTY;
import io.airlift.airline.Arguments;
import io.airlift.airline.Command;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;
import static org.apache.commons.lang3.StringUtils.EMPTY;
@Command(name = "assassinate", description = "Forcefully remove a dead node without re-replicating any data. Use as a last resort if you cannot removenode") @Command(name = "assassinate", description = "Forcefully remove a dead node without re-replicating any data. Use as a last resort if you cannot removenode")
public class Assassinate extends NodeToolCmd public class Assassinate extends AbstractCommand
{ {
@Arguments(title = "ip address", usage = "<ip_address>", description = "IP address of the endpoint to assassinate", required = true) @Parameters(paramLabel = "ip_address", description = "IP address of the endpoint to assassinate", arity = "1")
private String endpoint = EMPTY; private String endpoint = EMPTY;
@Override @Override

View File

@ -23,11 +23,10 @@ import java.util.Set;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import io.airlift.airline.Command;
import io.airlift.airline.Option;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool;
import org.apache.cassandra.tools.nodetool.formatter.TableBuilder; import org.apache.cassandra.tools.nodetool.formatter.TableBuilder;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
@ -35,10 +34,10 @@ import static com.google.common.base.Preconditions.checkArgument;
* Provides currently running auto-repair tasks. * Provides currently running auto-repair tasks.
*/ */
@Command(name = "autorepairstatus", description = "Print autorepair status") @Command(name = "autorepairstatus", description = "Print autorepair status")
public class AutoRepairStatus extends NodeTool.NodeToolCmd public class AutoRepairStatus extends AbstractCommand
{ {
@VisibleForTesting @VisibleForTesting
@Option(title = "repair type", name = { "-t", "--repair-type" }, description = "Repair type") @Option(names = { "-t", "--repair-type" }, description = "Repair type")
protected String repairType; protected String repairType;
@Override @Override

View File

@ -0,0 +1,28 @@
/*
* 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 picocli.CommandLine.Command;
@Command(name = "bootstrap",
subcommands = { BootstrapResume.class },
description = "Monitor/manage node's bootstrap process")
public class Bootstrap
{
}

View File

@ -17,24 +17,24 @@
*/ */
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Command;
import java.io.IOError; import java.io.IOError;
import java.io.IOException; import java.io.IOException;
import io.airlift.airline.Option;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import static org.apache.cassandra.config.CassandraRelevantProperties.RESET_BOOTSTRAP_PROGRESS; import static org.apache.cassandra.config.CassandraRelevantProperties.RESET_BOOTSTRAP_PROGRESS;
@Command(name = "resume", description = "Resume bootstrap streaming") @Command(name = "resume", description = "Resume bootstrap streaming")
public class BootstrapResume extends NodeToolCmd public class BootstrapResume extends AbstractCommand
{ {
@Option(title = "force", @Option(paramLabel = "force",
name = { "-f", "--force"}, names = { "-f", "--force" },
description = "Use --force to resume bootstrap regardless of cassandra.reset_bootstrap_progress environment variable. WARNING: This is potentially dangerous, see CASSANDRA-17679") description = { "Use --force to resume bootstrap regardless of ",
boolean force = false; "cassandra.reset_bootstrap_progress environment variable. WARNING:",
"This is potentially dangerous, see CASSANDRA-17679" })
private boolean force = false;
@Override @Override
protected void execute(NodeProbe probe) protected void execute(NodeProbe probe)

View File

@ -22,18 +22,17 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import io.airlift.airline.Command;
import org.apache.cassandra.db.virtual.CIDRFilteringMetricsTable.CIDRFilteringMetricsCountsTable; import org.apache.cassandra.db.virtual.CIDRFilteringMetricsTable.CIDRFilteringMetricsCountsTable;
import org.apache.cassandra.db.virtual.CIDRFilteringMetricsTable.CIDRFilteringMetricsLatenciesTable; import org.apache.cassandra.db.virtual.CIDRFilteringMetricsTable.CIDRFilteringMetricsLatenciesTable;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
import org.apache.cassandra.tools.nodetool.formatter.TableBuilder; import org.apache.cassandra.tools.nodetool.formatter.TableBuilder;
import picocli.CommandLine.Command;
/** /**
* Nodetool command to view stats related to CIDR filtering * Nodetool command to view stats related to CIDR filtering
*/ */
@Command(name = "cidrfilteringstats", description = "Print statistics on CIDR filtering") @Command(name = "cidrfilteringstats", description = "Print statistics on CIDR filtering")
public class CIDRFilteringStats extends NodeToolCmd public class CIDRFilteringStats extends AbstractCommand
{ {
private void printCountsMetrics(NodeProbe probe, PrintStream out) private void printCountsMetrics(NodeProbe probe, PrintStream out)
{ {

View File

@ -27,12 +27,12 @@ import java.util.Map;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import io.airlift.airline.Arguments;
import io.airlift.airline.Command;
import io.airlift.airline.Option;
import org.apache.cassandra.tcm.Epoch; import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool; import org.apache.cassandra.tools.nodetool.layout.CassandraUsage;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import static org.apache.cassandra.tcm.CMSOperations.COMMITS_PAUSED; import static org.apache.cassandra.tcm.CMSOperations.COMMITS_PAUSED;
import static org.apache.cassandra.tcm.CMSOperations.EPOCH; import static org.apache.cassandra.tcm.CMSOperations.EPOCH;
@ -44,10 +44,29 @@ import static org.apache.cassandra.tcm.CMSOperations.NEEDS_RECONFIGURATION;
import static org.apache.cassandra.tcm.CMSOperations.REPLICATION_FACTOR; import static org.apache.cassandra.tcm.CMSOperations.REPLICATION_FACTOR;
import static org.apache.cassandra.tcm.CMSOperations.SERVICE_STATE; import static org.apache.cassandra.tcm.CMSOperations.SERVICE_STATE;
public abstract class CMSAdmin extends NodeTool.NodeToolCmd @Command(name = "cms", description = "Manage cluster metadata",
subcommands = { CMSAdmin.DescribeCMS.class,
CMSAdmin.InitializeCMS.class,
CMSAdmin.ReconfigureCMS.class,
CMSAdmin.Snapshot.class,
CMSAdmin.Unregister.class,
CMSAdmin.AbortInitialization.class,
CMSAdmin.DumpDirectory.class,
CMSAdmin.DumpLog.class,
CMSAdmin.ResumeDropAccordTable.class })
public class CMSAdmin extends AbstractCommand
{ {
@Override
protected void execute(NodeProbe probe)
{
AbstractCommand cmd = new DescribeCMS();
cmd.probe(probe);
cmd.logger(output);
cmd.run();
}
@Command(name = "describe", description = "Describe the current Cluster Metadata Service") @Command(name = "describe", description = "Describe the current Cluster Metadata Service")
public static class DescribeCMS extends NodeTool.NodeToolCmd public static class DescribeCMS extends AbstractCommand
{ {
@Override @Override
protected void execute(NodeProbe probe) protected void execute(NodeProbe probe)
@ -67,9 +86,9 @@ public abstract class CMSAdmin extends NodeTool.NodeToolCmd
} }
@Command(name = "initialize", description = "Upgrade from gossip and initialize CMS") @Command(name = "initialize", description = "Upgrade from gossip and initialize CMS")
public static class InitializeCMS extends NodeTool.NodeToolCmd public static class InitializeCMS extends AbstractCommand
{ {
@Option(title = "ignored endpoints", name = { "-i", "--ignore"}, description = "Hosts to ignore due to them being down") @Option(paramLabel = "ignored_endpoints", names = { "-i", "--ignore" }, description = "Hosts to ignore due to them being down")
private List<String> endpoint = new ArrayList<>(); private List<String> endpoint = new ArrayList<>();
@Override @Override
@ -80,24 +99,25 @@ public abstract class CMSAdmin extends NodeTool.NodeToolCmd
} }
@Command(name = "reconfigure", description = "Reconfigure replication factor of CMS") @Command(name = "reconfigure", description = "Reconfigure replication factor of CMS")
public static class ReconfigureCMS extends NodeTool.NodeToolCmd public static class ReconfigureCMS extends AbstractCommand
{ {
@Option(title = "status", @Option(paramLabel = "status",
name = {"--status"}, names = { "--status" },
description = "Poll status of the reconfigure command. All other flags and arguments are ignored when this one is used.") description = "Poll status of the reconfigure command. All other flags and arguments are ignored when this one is used.")
private boolean status = false; private boolean status = false;
@Option(title = "resume", @Option(paramLabel = "resume",
name = {"-r", "--resume"}, names = { "-r", "--resume" },
description = "Whether or not a previously interrupted sequence should be resumed") description = "Whether or not a previously interrupted sequence should be resumed")
private boolean resume = false; private boolean resume = false;
@Option(title = "cancel", @Option(paramLabel = "cancel",
name = {"-c", "--cancel"}, names = { "-c", "--cancel" },
description = "Cancels any in progress CMS reconfiguration") description = "Cancels any in progress CMS reconfiguration")
private boolean cancel = false; private boolean cancel = false;
@Arguments(usage = "[<replication factor>] or <datacenter>:<replication_factor> ... ", description = "Replication factor of new CMS") @CassandraUsage(usage = "[<replication factor>] or <datacenter>:<replication_factor> ... ", description = "Replication factor of new CMS")
@Parameters(paramLabel = "replication_factor", description = "Replication factors of new CMS in format <replication factor> or <datacenter>:<replication_factor>")
private List<String> args = new ArrayList<>(); private List<String> args = new ArrayList<>();
@Override @Override
@ -178,7 +198,7 @@ public abstract class CMSAdmin extends NodeTool.NodeToolCmd
} }
@Command(name = "snapshot", description = "Request a checkpointing snapshot of cluster metadata") @Command(name = "snapshot", description = "Request a checkpointing snapshot of cluster metadata")
public static class Snapshot extends NodeTool.NodeToolCmd public static class Snapshot extends AbstractCommand
{ {
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)
@ -188,10 +208,10 @@ public abstract class CMSAdmin extends NodeTool.NodeToolCmd
} }
@Command(name = "unregister", description = "Unregister nodes in LEFT state") @Command(name = "unregister", description = "Unregister nodes in LEFT state")
public static class Unregister extends NodeTool.NodeToolCmd public static class Unregister extends AbstractCommand
{ {
@Arguments(required = true, title = "Unregister nodes in LEFT state", description = "One or more nodeIds to unregister, they all need to be in LEFT state", usage = "<nodeId>+") @Parameters(paramLabel = "nodeId", description = "One or more nodeIds to unregister, they all need to be in LEFT state", arity = "1..*")
public List<String> nodeIds; private List<String> nodeIds;
@Override @Override
protected void execute(NodeProbe probe) protected void execute(NodeProbe probe)
@ -201,9 +221,9 @@ public abstract class CMSAdmin extends NodeTool.NodeToolCmd
} }
@Command(name = "abortinitialization", description = "Abort an incomplete initialization") @Command(name = "abortinitialization", description = "Abort an incomplete initialization")
public static class AbortInitialization extends NodeTool.NodeToolCmd public static class AbortInitialization extends AbstractCommand
{ {
@Option(required = true, name = "--initiator", title = "Initiator", description = "The address of the node where `cms initialize` was run.") @Option(required = true, names = { "--initiator" }, description = "The address of the node where `cms initialize` was run.")
public String initiator; public String initiator;
@Override @Override
@ -214,10 +234,11 @@ public abstract class CMSAdmin extends NodeTool.NodeToolCmd
} }
@Command(name = "dumpdirectory", description = "Dump the directory from the current ClusterMetadata") @Command(name = "dumpdirectory", description = "Dump the directory from the current ClusterMetadata")
public static class DumpDirectory extends NodeTool.NodeToolCmd public static class DumpDirectory extends AbstractCommand
{ {
@Option(name = "--tokens", title = "Include tokens", description = "Include tokens in output") @Option(names = { "--tokens" }, description = "Include tokens in output")
public boolean tokens = false; public boolean tokens = false;
@Override @Override
protected void execute(NodeProbe probe) protected void execute(NodeProbe probe)
{ {
@ -226,12 +247,13 @@ public abstract class CMSAdmin extends NodeTool.NodeToolCmd
} }
@Command(name = "dumplog", description = "Dump the metadata log") @Command(name = "dumplog", description = "Dump the metadata log")
public static class DumpLog extends NodeTool.NodeToolCmd public static class DumpLog extends AbstractCommand
{ {
@Option(name = "--start", title = "Start epoch") @Option(names = { "--start" }, description = "Start epoch")
long startEpoch = Epoch.FIRST.getEpoch(); long startEpoch = Epoch.FIRST.getEpoch();
@Option(name = "--end", title = "End epoch") @Option(names = { "--end" }, description = "End epoch")
long endEpoch = Long.MAX_VALUE; long endEpoch = Long.MAX_VALUE;
@Override @Override
protected void execute(NodeProbe probe) protected void execute(NodeProbe probe)
{ {
@ -259,10 +281,11 @@ public abstract class CMSAdmin extends NodeTool.NodeToolCmd
} }
@Command(name = "resumedropaccordtable", description = "Resume a drop accord table operation which has stalled") @Command(name = "resumedropaccordtable", description = "Resume a drop accord table operation which has stalled")
public static class ResumeDropAccordTable extends NodeTool.NodeToolCmd public static class ResumeDropAccordTable extends AbstractCommand
{ {
@Arguments(usage = "[tableId]", description = "Table id of the table being dropped") @Parameters(description = "Table id of the table being dropped")
private String tableId; private String tableId;
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)
{ {

View File

@ -17,32 +17,43 @@
*/ */
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Arguments;
import io.airlift.airline.Command;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import io.airlift.airline.Option;
import org.apache.cassandra.schema.SchemaConstants; import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import org.apache.cassandra.tools.nodetool.layout.CassandraUsage;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import static org.apache.cassandra.tools.nodetool.CommandUtils.concatArgs;
import static org.apache.cassandra.tools.nodetool.CommandUtils.parseOptionalKeyspaceNonLocal;
import static org.apache.cassandra.tools.nodetool.CommandUtils.parseOptionalTables;
@Command(name = "cleanup", description = "Triggers the immediate cleanup of keys no longer belonging to a node. By default, clean all keyspaces") @Command(name = "cleanup", description = "Triggers the immediate cleanup of keys no longer belonging to a node. By default, clean all keyspaces")
public class Cleanup extends NodeToolCmd public class Cleanup extends AbstractCommand
{ {
@Arguments(usage = "[<keyspace> <tables>...]", description = "The keyspace followed by one or many tables") @CassandraUsage(usage = "[<keyspace> <tables>...]", description = "The keyspace followed by one or many tables")
private List<String> args = new ArrayList<>(); private List<String> args = new ArrayList<>();
@Option(title = "jobs", @Parameters(index = "0", description = "The keyspace followed by one or many tables", arity = "0..1")
name = {"-j", "--jobs"}, private String keyspace;
@Parameters (index = "1..*", description = "The tables to cleanup", arity = "0..*")
private String[] tables;
@Option(paramLabel = "jobs",
names = {"-j", "--jobs"},
description = "Number of sstables to cleanup simultanously, set to 0 to use all available compaction threads") description = "Number of sstables to cleanup simultanously, set to 0 to use all available compaction threads")
private int jobs = 2; private int jobs = 2;
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)
{ {
List<String> keyspaces = parseOptionalKeyspace(args, probe, KeyspaceSet.NON_LOCAL_STRATEGY); args = concatArgs(keyspace, tables);
List<String> keyspaces = parseOptionalKeyspaceNonLocal(args, probe);
String[] tableNames = parseOptionalTables(args); String[] tableNames = parseOptionalTables(args);
for (String keyspace : keyspaces) for (String keyspace : keyspaces)

View File

@ -17,13 +17,6 @@
*/ */
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import static com.google.common.collect.Iterables.toArray;
import static org.apache.commons.lang3.StringUtils.EMPTY;
import static org.apache.commons.lang3.StringUtils.join;
import io.airlift.airline.Arguments;
import io.airlift.airline.Command;
import io.airlift.airline.Option;
import java.io.IOException; import java.io.IOException;
import java.time.Instant; import java.time.Instant;
import java.time.format.DateTimeParseException; import java.time.format.DateTimeParseException;
@ -34,25 +27,33 @@ import java.util.Map;
import org.apache.cassandra.config.DurationSpec; import org.apache.cassandra.config.DurationSpec;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import org.apache.cassandra.tools.nodetool.layout.CassandraUsage;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import static com.google.common.collect.Iterables.toArray;
import static org.apache.commons.lang3.StringUtils.EMPTY;
import static org.apache.commons.lang3.StringUtils.join;
@Command(name = "clearsnapshot", description = "Remove the snapshot with the given name from the given keyspaces") @Command(name = "clearsnapshot", description = "Remove the snapshot with the given name from the given keyspaces")
public class ClearSnapshot extends NodeToolCmd public class ClearSnapshot extends AbstractCommand
{ {
@Arguments(usage = "[<keyspaces>...] ", description = "Remove snapshots from the given keyspaces") @CassandraUsage(usage = "[<keyspaces>...]", description = "Remove snapshots from the given keyspaces")
@Parameters(description = "Remove snapshots from the given keyspaces", arity = "0..*")
private List<String> keyspaces = new ArrayList<>(); private List<String> keyspaces = new ArrayList<>();
@Option(title = "snapshot_name", name = "-t", description = "Remove the snapshot with a given name") @Option(paramLabel = "snapshot_name", names = "-t", description = "Remove the snapshot with a given name")
private String snapshotName = EMPTY; private String snapshotName = EMPTY;
@Option(title = "clear_all_snapshots", name = "--all", description = "Removes all snapshots") @Option(paramLabel = "clear_all_snapshots", names = "--all", description = "Removes all snapshots")
private boolean clearAllSnapshots = false; private boolean clearAllSnapshots = false;
@Option(title = "older_than", name = "--older-than", description = "Clear snapshots older than specified time period.") @Option(paramLabel = "older_than", names = "--older-than", description = "Clear snapshots older than specified time period.")
private String olderThan; private String olderThan;
@Option(title = "older_than_timestamp", name = "--older-than-timestamp", @Option(paramLabel = "older_than_timestamp", names = "--older-than-timestamp",
description = "Clear snapshots older than specified timestamp. It has to be a string in ISO format, for example '2022-12-03T10:15:30Z'") description = "Clear snapshots older than specified timestamp. It has to be a string in ISO format, for example '2022-12-03T10:15:30Z'")
private String olderThanTimestamp; private String olderThanTimestamp;
@Override @Override
@ -121,7 +122,8 @@ public class ClearSnapshot extends NodeToolCmd
parameters.put("older_than_timestamp", olderThanTimestamp); parameters.put("older_than_timestamp", olderThanTimestamp);
probe.clearSnapshot(parameters, snapshotName, toArray(keyspaces, String.class)); probe.clearSnapshot(parameters, snapshotName, toArray(keyspaces, String.class));
} catch (IOException e) }
catch (IOException e)
{ {
throw new RuntimeException("Error during clearing snapshots", e); throw new RuntimeException("Error during clearing snapshots", e);
} }

View File

@ -26,30 +26,30 @@ import java.util.Map.Entry;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import io.airlift.airline.Command;
import io.airlift.airline.Option;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
import org.apache.cassandra.tools.nodetool.formatter.TableBuilder; import org.apache.cassandra.tools.nodetool.formatter.TableBuilder;
import org.apache.cassandra.transport.ClientStat; import org.apache.cassandra.transport.ClientStat;
import org.apache.cassandra.transport.ConnectedClient; import org.apache.cassandra.transport.ConnectedClient;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
@Command(name = "clientstats", description = "Print information about connected clients") @Command(name = "clientstats", description = "Print information about connected clients")
public class ClientStats extends NodeToolCmd public class ClientStats extends AbstractCommand
{ {
@Option(title = "list_connections", name = "--all", description = "Lists all connections") @Option(paramLabel = "list_connections", names = "--all", description = "Lists all connections")
private boolean listConnections = false; private boolean listConnections = false;
@Option(title = "by_protocol", name = "--by-protocol", description = "Lists most recent client connections by protocol version") @Option(paramLabel = "by_protocol", names = "--by-protocol", description = "Lists most recent client connections by protocol version")
private boolean connectionsByProtocolVersion = false; private boolean connectionsByProtocolVersion = false;
@Option(title = "clear_history", name = "--clear-history", description = "Clear the history of connected clients") @Option(paramLabel = "clear_history", names = "--clear-history", description = "Clear the history of connected clients")
private boolean clearConnectionHistory = false; private boolean clearConnectionHistory = false;
@Option(title = "list_connections_with_client_options", name = "--client-options", description = "Lists all connections and the client options") @Option(paramLabel = "list_connections_with_client_options", names = "--client-options", description = "Lists all connections and the client options")
private boolean clientOptions = false; private boolean clientOptions = false;
@Option(title = "verbose", name = "--verbose", description = "Lists all connections with additional details (client options, authenticator-specific metadata and more)") @Option(paramLabel = "verbose", names = "--verbose", description = "Lists all connections with additional details (client options, authenticator-specific metadata and more)")
private boolean verbose = false; private boolean verbose = false;
@Override @Override

View File

@ -0,0 +1,259 @@
/*
* 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.Field;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.google.common.collect.Maps;
import org.apache.cassandra.locator.EndpointSnitchInfoMBean;
import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.nodetool.formatter.TableBuilder;
import org.apache.cassandra.tools.nodetool.layout.CassandraUsage;
import org.apache.cassandra.utils.Pair;
import static com.google.common.collect.Iterables.toArray;
import static java.util.stream.Stream.concat;
import static java.util.stream.Stream.ofNullable;
import static org.apache.commons.lang3.ArrayUtils.EMPTY_STRING_ARRAY;
/**
* Utility methods for nodetool commands.
*/
public final class CommandUtils
{
private CommandUtils() {}
public static int maxLength(Collection<?> any)
{
int result = 0;
for (Object value : any)
result = Math.max(result, String.valueOf(value).length());
return result;
}
public static Pair<String, String> findCassandraBackwardCompatibleArgument(Object userObject)
{
Class<?> clazz = userObject.getClass();
do
{
for (Field field : clazz.getDeclaredFields())
{
if (field.isAnnotationPresent(CassandraUsage.class))
{
CassandraUsage ann = field.getAnnotation(CassandraUsage.class);
return Pair.create(ann.usage(), ann.description());
}
}
}
while ((clazz = clazz.getSuperclass()) != null);
return null;
}
public static String[] sortShortestFirst(String[] names)
{
Arrays.sort(names, Comparator.comparing(String::length));
return names;
}
public static List<String> concatArgs(String first, String second)
{
return concat(ofNullable(first), ofNullable(second)).collect(Collectors.toList());
}
public static List<String> concatArgs(String first, String second, String third)
{
return concat(ofNullable(first), concatArgs(second, third).stream()).collect(Collectors.toList());
}
public static List<String> concatArgs(String first, String second, String third, String fourth)
{
return concat(ofNullable(first), concatArgs(second, third, fourth).stream()).collect(Collectors.toList());
}
public static List<String> concatArgs(String first, String[] second)
{
return concat(ofNullable(first), (second == null ? Stream.empty() : Arrays.stream(second))).collect(Collectors.toList());
}
public static List<String> concatArgs(String first, List<String> second)
{
return concat(ofNullable(first), (second == null ? Stream.empty() : second.stream())).collect(Collectors.toList());
}
public static List<String> concatArgs(String first, String second, String[] third)
{
return concat(ofNullable(first), concatArgs(second, third).stream()).collect(Collectors.toList());
}
public static List<String> concatArgs(String first, String second, List<String> third)
{
return concat(ofNullable(first), concatArgs(second, third).stream()).collect(Collectors.toList());
}
public static List<String> concatArgs(String first, String second, String third, String[] fourth)
{
return concat(ofNullable(first), concatArgs(second, third, fourth).stream()).collect(Collectors.toList());
}
public static void printSet(PrintStream out, String colName, Set<String> values)
{
if (values == null || values.isEmpty())
return;
TableBuilder table = new TableBuilder();
table.add(colName + ": ");
for (String value : values)
table.add(value);
table.printTo(out);
}
public static List<String> parseOptionalKeyspaceAccordManaged(List<String> cmdArgs, NodeProbe nodeProbe)
{
return parseOptionalKeyspace(cmdArgs, nodeProbe, KeyspaceSet.ACCORD_MANAGED);
}
public static List<String> parseOptionalKeyspace(List<String> cmdArgs, NodeProbe nodeProbe)
{
return parseOptionalKeyspace(cmdArgs, nodeProbe, KeyspaceSet.ALL);
}
public static List<String> parseOptionalKeyspaceNonLocal(List<String> cmdArgs, NodeProbe nodeProbe)
{
return parseOptionalKeyspace(cmdArgs, nodeProbe, KeyspaceSet.NON_LOCAL_STRATEGY);
}
private static List<String> parseOptionalKeyspace(List<String> cmdArgs, NodeProbe nodeProbe, KeyspaceSet defaultKeyspaceSet)
{
List<String> keyspaces = new ArrayList<>();
if (cmdArgs == null || cmdArgs.isEmpty())
{
if (defaultKeyspaceSet == KeyspaceSet.NON_LOCAL_STRATEGY)
keyspaces.addAll(keyspaces = nodeProbe.getNonLocalStrategyKeyspaces());
else if (defaultKeyspaceSet == KeyspaceSet.NON_SYSTEM)
keyspaces.addAll(keyspaces = nodeProbe.getNonSystemKeyspaces());
else if (defaultKeyspaceSet == KeyspaceSet.ACCORD_MANAGED)
keyspaces.addAll(nodeProbe.getAccordManagedKeyspaces());
else
keyspaces.addAll(nodeProbe.getKeyspaces());
}
else
{
keyspaces.add(cmdArgs.get(0));
}
for (String keyspace : keyspaces)
{
if (!nodeProbe.getKeyspaces().contains(keyspace))
throw new IllegalArgumentException("Keyspace [" + keyspace + "] does not exist.");
}
return Collections.unmodifiableList(keyspaces);
}
/**
* Parses the optional table names from the command arguments for nodetool commands.
* <p>
* The nodetool commands can operate on either all tables within a keyspace, or on a specific
* subset of tables. This method extracts the table names from the provided cli arguments, assuming
* the first argument is the keyspace name and any subsequent arguments are table names.
* <p>
* If no table names are provided (e.g. only the keyspace is specified), this method returns
* an empty array, which signals to the MBeans that the operation should apply to all tables
* in the keyspace. This approach provides flexibility to target either all tables or specific
* tables as needed.
*
* @param cmdArgs the list of command arguments, where the first argument is typically the
* keyspace name (ignored), and any subsequent arguments are table names
* @return an array of table names, or an empty array (meaning 'all tables') if no extra args are specified.
*/
public static String[] parseOptionalTables(List<String> cmdArgs)
{
return cmdArgs.size() <= 1 ? EMPTY_STRING_ARRAY : toArray(cmdArgs.subList(1, cmdArgs.size()), String.class);
}
/**
* Parses the optional partition key values from the command arguments for nodetool commands.
* <p>
* Some nodetool commands can operate on a specific partition within a table, which requires
* specifying the partition key values after the keyspace and table names in the cli arguments.
* This method extracts the partition keys from the provided command arguments, assuming
* the first argument is the keyspace name, the second is the table name, and any subsequent
* arguments are partition key values.
* <p>
* If no partition key values are provided (e.g. only keyspace and table are specified), this
* method returns an empty array, which signals to the MBeans or command logic that the operation
* should apply to all partitions.
*
* @param cmdArgs the list of command arguments, where the first argument is the keyspace name,
* the second is the table name, and any subsequent arguments are partition key values
* @return an array of partition key values, or an empty array if none are specified (meaning 'all partitions').
*/
public static String[] parsePartitionKeys(List<String> cmdArgs)
{
return cmdArgs.size() <= 2 ? EMPTY_STRING_ARRAY : toArray(cmdArgs.subList(2, cmdArgs.size()), String.class);
}
public static SortedMap<String, SetHostStatWithPort> getOwnershipByDcWithPort(NodeProbe probe, boolean resolveIp,
Map<String, String> tokenToEndpoint,
Map<String, Float> ownerships)
{
SortedMap<String, SetHostStatWithPort> ownershipByDc = Maps.newTreeMap();
EndpointSnitchInfoMBean epSnitchInfo = probe.getEndpointSnitchInfoProxy();
try
{
for (Map.Entry<String, String> tokenAndEndPoint : tokenToEndpoint.entrySet())
{
String dc = epSnitchInfo.getDatacenter(tokenAndEndPoint.getValue());
if (!ownershipByDc.containsKey(dc))
ownershipByDc.put(dc, new SetHostStatWithPort(resolveIp));
ownershipByDc.get(dc).add(tokenAndEndPoint.getKey(), tokenAndEndPoint.getValue(), ownerships);
}
}
catch (UnknownHostException e)
{
throw new RuntimeException(e);
}
return ownershipByDc;
}
private enum KeyspaceSet
{
ALL, NON_SYSTEM, NON_LOCAL_STRATEGY, ACCORD_MANAGED
}
}

View File

@ -17,41 +17,46 @@
*/ */
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import static org.apache.commons.lang3.StringUtils.EMPTY;
import io.airlift.airline.Arguments;
import io.airlift.airline.Command;
import io.airlift.airline.Option;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import org.apache.cassandra.tools.nodetool.layout.CassandraUsage;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import static org.apache.cassandra.tools.nodetool.CommandUtils.parseOptionalKeyspace;
import static org.apache.cassandra.tools.nodetool.CommandUtils.parseOptionalTables;
import static org.apache.commons.lang3.StringUtils.EMPTY;
// TODO CASSANDRA-20793 Types of input aguments shouldn't be mixed in the same command. The keyspace, table and SSTable file arguments should have their own commands.
@Command(name = "compact", description = "Force a (major) compaction on one or more tables or user-defined compaction on given SSTables") @Command(name = "compact", description = "Force a (major) compaction on one or more tables or user-defined compaction on given SSTables")
public class Compact extends NodeToolCmd public class Compact extends AbstractCommand
{ {
@Arguments(usage = "[<keyspace> <tables>...] or <SSTable file>...", description = "The keyspace followed by one or many tables or list of SSTable data files when using --user-defined") @CassandraUsage(usage = "[<keyspace> <tables>...] or <SSTable file>...",
description = "The keyspace followed by one or many tables or list of SSTable data files when using --user-defined")
@Parameters(index = "0..*", description = "The keyspace followed by one or many tables or " +
"list of SSTable data files when using --user-defined")
private List<String> args = new ArrayList<>(); private List<String> args = new ArrayList<>();
@Option(title = "split_output", name = {"-s", "--split-output"}, description = "Use -s to not create a single big file") @Option(paramLabel = "split_output", names = { "-s", "--split-output" }, description = "Use -s to not create a single big file")
private boolean splitOutput = false; private boolean splitOutput = false;
@Option(title = "user-defined", name = {"--user-defined"}, description = "Use --user-defined to submit listed files for user-defined compaction") @Option(paramLabel = "user_defined", names = { "--user-defined" }, description = "Use --user-defined to submit listed files for user-defined compaction")
private boolean userDefined = false; private boolean userDefined = false;
@Option(title = "start_token", name = {"-st", "--start-token"}, description = "Use -st to specify a token at which the compaction range starts (inclusive)") @Option(paramLabel = "start_token", names = { "-st", "--start-token" }, description = "Use -st to specify a token at which the compaction range starts (inclusive)")
private String startToken = EMPTY; private String startToken = EMPTY;
@Option(title = "end_token", name = {"-et", "--end-token"}, description = "Use -et to specify a token at which compaction range ends (inclusive)") @Option(paramLabel = "end_token", names = { "-et", "--end-token" }, description = "Use -et to specify a token at which compaction range ends (inclusive)")
private String endToken = EMPTY; private String endToken = EMPTY;
@Option(title = "partition_key", name = {"--partition"}, description = "String representation of the partition key") @Option(paramLabel = "partition_key", names = { "--partition" }, description = "String representation of the partition key")
private String partitionKey = EMPTY; private String partitionKey = EMPTY;
@Option(title = "jobs", @Option(paramLabel = "jobs",
name = {"-j", "--jobs"}, names = {"-j", "--jobs"},
description = "Use -j to specify the maximum number of threads to use for parallel compaction. " + description = "Use -j to specify the maximum number of threads to use for parallel compaction. " +
"If not set, up to half the compaction threads will be used. " + "If not set, up to half the compaction threads will be used. " +
"If set to 0, the major compaction will use all threads and will not permit other compactions to run until it completes (use with caution).") "If set to 0, the major compaction will use all threads and will not permit other compactions to run until it completes (use with caution).")

View File

@ -17,26 +17,25 @@
*/ */
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Command;
import io.airlift.airline.Option;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
import org.apache.cassandra.tools.nodetool.stats.CompactionHistoryHolder; import org.apache.cassandra.tools.nodetool.stats.CompactionHistoryHolder;
import org.apache.cassandra.tools.nodetool.stats.CompactionHistoryPrinter; import org.apache.cassandra.tools.nodetool.stats.CompactionHistoryPrinter;
import org.apache.cassandra.tools.nodetool.stats.StatsHolder; import org.apache.cassandra.tools.nodetool.stats.StatsHolder;
import org.apache.cassandra.tools.nodetool.stats.StatsPrinter; import org.apache.cassandra.tools.nodetool.stats.StatsPrinter;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
@Command(name = "compactionhistory", description = "Print history of compaction") @Command(name = "compactionhistory", description = "Print history of compaction")
public class CompactionHistory extends NodeToolCmd public class CompactionHistory extends AbstractCommand
{ {
@Option(title = "format", @Option(paramLabel = "format",
name = {"-F", "--format"}, names = { "-F", "--format" },
description = "Output format (json, yaml)") description = "Output format (json, yaml)")
private String outputFormat = ""; private String outputFormat = "";
@Option(title = "human_readable", @Option(paramLabel = "human_readable",
name = {"-H", "--human-readable"}, names = { "-H", "--human-readable" },
description = "Display bytes in human readable form, i.e. KiB, MiB, GiB, TiB") description = "Display bytes in human readable form, i.e. KiB, MiB, GiB, TiB")
private boolean humanReadable = false; private boolean humanReadable = false;
@Override @Override

View File

@ -24,29 +24,27 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import io.airlift.airline.Command;
import io.airlift.airline.Option;
import org.apache.cassandra.db.compaction.CompactionInfo; import org.apache.cassandra.db.compaction.CompactionInfo;
import org.apache.cassandra.db.compaction.CompactionInfo.Unit; import org.apache.cassandra.db.compaction.CompactionInfo.Unit;
import org.apache.cassandra.io.util.FileUtils; import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.metrics.CassandraMetricsRegistry; import org.apache.cassandra.metrics.CassandraMetricsRegistry;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
import org.apache.cassandra.tools.nodetool.formatter.TableBuilder; import org.apache.cassandra.tools.nodetool.formatter.TableBuilder;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import static java.lang.String.format; import static java.lang.String.format;
@Command(name = "compactionstats", description = "Print statistics on compactions") @Command(name = "compactionstats", description = "Print statistics on compactions")
public class CompactionStats extends NodeToolCmd public class CompactionStats extends AbstractCommand
{ {
@Option(title = "human_readable", @Option(paramLabel = "human_readable",
name = {"-H", "--human-readable"}, names = { "-H", "--human-readable" },
description = "Display bytes in human readable form, i.e. KiB, MiB, GiB, TiB") description = "Display bytes in human readable form, i.e. KiB, MiB, GiB, TiB")
private boolean humanReadable = false; private boolean humanReadable = false;
@Option(title = "vtable_output", @Option(paramLabel = "vtable_output",
name = {"-V", "--vtable"}, names = { "-V", "--vtable" },
description = "Display fields matching vtable output") description = "Display fields matching vtable output")
private boolean vtableOutput = false; private boolean vtableOutput = false;

View File

@ -24,35 +24,53 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import io.airlift.airline.Arguments;
import io.airlift.airline.Command;
import io.airlift.airline.Option;
import org.apache.cassandra.service.consensus.migration.ConsensusMigrationTarget; import org.apache.cassandra.service.consensus.migration.ConsensusMigrationTarget;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool;
import org.apache.cassandra.tools.RepairRunner.RepairCmd; import org.apache.cassandra.tools.RepairRunner.RepairCmd;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static java.util.Collections.singleton; import static java.util.Collections.singleton;
import static org.apache.cassandra.tools.nodetool.CommandUtils.parseOptionalKeyspaceAccordManaged;
/** /**
* For managing migration from one consensus protocol to another. * For managing migration from one consensus protocol to another.
* *
* Mark ranges as migrating, and list the migrating ranges. * Mark ranges as migrating, and list the migrating ranges.
*/ */
public abstract class ConsensusMigrationAdmin extends NodeTool.NodeToolCmd @CommandLine.Command(name = "consensus_admin", description = "List and mark ranges as migrating between consensus protocols",
subcommands = { ConsensusMigrationAdmin.BeginMigration.class,
ConsensusMigrationAdmin.FinishMigration.class,
ConsensusMigrationAdmin.ListCmd.class })
public class ConsensusMigrationAdmin extends AbstractCommand
{ {
@Command(name = "list", description = "List migrating tables and ranges") @Override
public static class ListCmd extends ConsensusMigrationAdmin protected void execute(NodeProbe probe)
{ {
@Arguments(usage = "[<keyspace> <tables>...]", description = "The keyspace followed by one or many tables") AbstractCommand cmd = new CMSAdmin.DescribeCMS();
private List<String> schemaArgs = new ArrayList<>(); cmd.probe(probe);
cmd.logger(output);
cmd.run();
}
@Option(title = "format", name = {"-f", "--format"}, description = "Output format, YAML and JSON are the only supported formats, default YAML, prefix with `minified-` to turn off pretty printing") @Command(name = "list", description = "List migrating tables and ranges")
public static class ListCmd extends AbstractCommand
{
@Parameters(index = "0", arity = "0..1", description = "The keyspace followed by one or many tables")
public String keyspace;
@Parameters(index = "1..*", arity = "0..*", description = "The tables")
public String[] tables;
@Option(names = {"-f", "--format"}, description = "Output format, YAML and JSON are the only supported formats, default YAML, prefix with `minified-` to turn off pretty printing")
private String format = "yaml"; private String format = "yaml";
protected void execute(NodeProbe probe) protected void execute(NodeProbe probe)
{ {
List<String> schemaArgs = CommandUtils.concatArgs(keyspace, tables);
Set<String> keyspaceNames = schemaArgs.size() > 0 ? singleton(schemaArgs.get(0)) : null; Set<String> keyspaceNames = schemaArgs.size() > 0 ? singleton(schemaArgs.get(0)) : null;
Set<String> tableNames = schemaArgs.size() > 1 ? new HashSet<>(schemaArgs.subList(1, schemaArgs.size())) : null; Set<String> tableNames = schemaArgs.size() > 1 ? new HashSet<>(schemaArgs.subList(1, schemaArgs.size())) : null;
String output = probe.getStorageService().listConsensusMigrations(keyspaceNames, tableNames, format); String output = probe.getStorageService().listConsensusMigrations(keyspaceNames, tableNames, format);
@ -61,22 +79,26 @@ public abstract class ConsensusMigrationAdmin extends NodeTool.NodeToolCmd
} }
@Command(name = "begin-migration", description = "Mark the range as migrating for the specified token range and tables") @Command(name = "begin-migration", description = "Mark the range as migrating for the specified token range and tables")
public static class BeginMigration extends ConsensusMigrationAdmin public static class BeginMigration extends AbstractCommand
{ {
@Option(title = "start_token", name = {"-st", "--start-token"}, description = "Use -st to specify a token at which the repair range starts") @Option(paramLabel = "start_token", names = { "-st", "--start-token" }, description = "Use -st to specify a token at which the repair range starts")
private String startToken = null; private String startToken = null;
@Option(title = "end_token", name = {"-et", "--end-token"}, description = "Use -et to specify a token at which repair range ends") @Option(paramLabel = "end_token", names = { "-et", "--end-token" }, description = "Use -et to specify a token at which repair range ends")
private String endToken = null; private String endToken = null;
@Arguments(usage = "[<keyspace> <tables>...]", description = "The keyspace followed by one or many tables") @Parameters(index = "0", arity = "0..1", description = "The keyspace followed by one or many tables")
private List<String> schemaArgs = new ArrayList<>(); private String keyspace;
@Parameters(index = "1..*", arity = "0..*", description = "The tables")
private String[] tables;
protected void execute(NodeProbe probe) protected void execute(NodeProbe probe)
{ {
checkArgument((endToken != null && startToken != null) || (endToken == null && startToken == null), "Must specify start and end token together"); checkArgument((endToken != null && startToken != null) || (endToken == null && startToken == null), "Must specify start and end token together");
List<String> schemaArgs = CommandUtils.concatArgs(keyspace, tables);
String maybeRangesStr = startToken != null ? startToken + ":" + endToken : null; String maybeRangesStr = startToken != null ? startToken + ":" + endToken : null;
List<String> keyspaceNames = parseOptionalKeyspace(schemaArgs, probe, KeyspaceSet.ACCORD_MANAGED); List<String> keyspaceNames = parseOptionalKeyspaceAccordManaged(schemaArgs, probe);
List<String> maybeTableNames = schemaArgs.size() > 1 ? schemaArgs.subList(1, schemaArgs.size()) : null; List<String> maybeTableNames = schemaArgs.size() > 1 ? schemaArgs.subList(1, schemaArgs.size()) : null;
probe.getStorageService().migrateConsensusProtocol(keyspaceNames, maybeTableNames, maybeRangesStr); probe.getStorageService().migrateConsensusProtocol(keyspaceNames, maybeTableNames, maybeRangesStr);
probe.output().out.println("Marked requested ranges as migrating. Repair needs to be run in order to complete the migration"); probe.output().out.println("Marked requested ranges as migrating. Repair needs to be run in order to complete the migration");
@ -84,16 +106,19 @@ public abstract class ConsensusMigrationAdmin extends NodeTool.NodeToolCmd
} }
@Command(name = "finish-migration", description = "Complete the migration for a range that has already begun migration") @Command(name = "finish-migration", description = "Complete the migration for a range that has already begun migration")
public static class FinishMigration extends ConsensusMigrationAdmin public static class FinishMigration extends AbstractCommand
{ {
@Option(title = "start_token", name = {"-st", "--start-token"}, description = "Use -st to specify a token at which the repair range starts (exclusive)") @Option(paramLabel = "start_token", names = {"-st", "--start-token"}, description = "Use -st to specify a token at which the repair range starts (exclusive)")
private String startToken = null; private String startToken = null;
@Option(title = "end_token", name = {"-et", "--end-token"}, description = "Use -et to specify a token at which repair range ends (inclusive)") @Option(paramLabel = "end_token", names = {"-et", "--end-token"}, description = "Use -et to specify a token at which repair range ends (inclusive)")
private String endToken = null; private String endToken = null;
@Arguments(usage = "[<keyspace> <tables>...]", description = "The keyspace followed by one or many tables") @Parameters(index = "0", arity = "0..1", description = "The keyspace followed by one or many tables")
private List<String> schemaArgs = new ArrayList<>(); private String keyspace;
@Parameters(index = "1..*", arity = "0..*", description = "The tables")
private String[] tables;
private static class FinishMigrationRepairCommand extends RepairCmd private static class FinishMigrationRepairCommand extends RepairCmd
{ {
@ -123,8 +148,9 @@ public abstract class ConsensusMigrationAdmin extends NodeTool.NodeToolCmd
protected void execute(NodeProbe probe) protected void execute(NodeProbe probe)
{ {
checkArgument((endToken != null) == (startToken != null), "Start and end token must be specified together"); checkArgument((endToken != null) == (startToken != null), "Start and end token must be specified together");
List<String> schemaArgs = CommandUtils.concatArgs(keyspace, tables);
String maybeRangesStr = startToken != null ? startToken + ":" + endToken : null; String maybeRangesStr = startToken != null ? startToken + ":" + endToken : null;
List<String> keyspaceNames = parseOptionalKeyspace(schemaArgs, probe, KeyspaceSet.ACCORD_MANAGED); List<String> keyspaceNames = parseOptionalKeyspaceAccordManaged(schemaArgs, probe);
List<String> maybeTableNames = schemaArgs.size() > 1 ? schemaArgs.subList(1, schemaArgs.size()) : null; List<String> maybeTableNames = schemaArgs.size() > 1 ? schemaArgs.subList(1, schemaArgs.size()) : null;
List<RepairCmd> repairCmds = new ArrayList<>(keyspaceNames.size() * 2); List<RepairCmd> repairCmds = new ArrayList<>(keyspaceNames.size() * 2);
// Finish can't actually finish with one set of repairs when migrating from Paxos -> Accord // Finish can't actually finish with one set of repairs when migrating from Paxos -> Accord

View File

@ -20,22 +20,23 @@ package org.apache.cassandra.tools.nodetool;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import io.airlift.airline.Arguments;
import io.airlift.airline.Command;
import io.airlift.airline.Option;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import org.apache.cassandra.tools.nodetool.layout.CassandraUsage;
import org.apache.cassandra.tools.nodetool.stats.DataPathsHolder; import org.apache.cassandra.tools.nodetool.stats.DataPathsHolder;
import org.apache.cassandra.tools.nodetool.stats.DataPathsPrinter; import org.apache.cassandra.tools.nodetool.stats.DataPathsPrinter;
import org.apache.cassandra.tools.nodetool.stats.StatsPrinter; import org.apache.cassandra.tools.nodetool.stats.StatsPrinter;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
@Command(name = "datapaths", description = "Print all directories where data of tables are stored") @Command(name = "datapaths", description = "Print all directories where data of tables are stored")
public class DataPaths extends NodeToolCmd public class DataPaths extends AbstractCommand
{ {
@Arguments(usage = "[<keyspace.table>...]", description = "List of table (or keyspace) names") @CassandraUsage(usage = "[<keyspace.table>...]", description = "List of table (or keyspace) names")
@Parameters(paramLabel = "keyspace.table", description = "List of table (or keyspace) names")
private List<String> tableNames = new ArrayList<>(); private List<String> tableNames = new ArrayList<>();
@Option(title = "format", name = {"-F", "--format"}, description = "Output format (json, yaml)") @Option(paramLabel = "format", names = { "-F", "--format" }, description = "Output format (json, yaml)")
private String outputFormat = ""; private String outputFormat = "";
@Override @Override

View File

@ -17,18 +17,17 @@
*/ */
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Command;
import io.airlift.airline.Option;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
@Command(name = "decommission", description = "Decommission the *node I am connecting to*") @Command(name = "decommission", description = "Decommission the *node I am connecting to*")
public class Decommission extends NodeToolCmd public class Decommission extends AbstractCommand
{ {
@Option(title = "force",
name = {"-f", "--force"}, @Option(paramLabel = "force",
description = "Force decommission of this node even when it reduces the number of replicas to below configured RF") names = { "-f", "--force" },
description = "Force decommission of this node even when it reduces the number of replicas to below configured RF")
private boolean force = false; private boolean force = false;
@Override @Override
@ -57,9 +56,9 @@ public class Decommission extends NodeToolCmd
} }
@Command(name = "abortdecommission", description = "Abort an ongoing, failed decommission") @Command(name = "abortdecommission", description = "Abort an ongoing, failed decommission")
public static class Abort extends NodeToolCmd public static class Abort extends AbstractCommand
{ {
@Option(title = "node id", name = "--node") @Option(paramLabel = "nodeId", names = { "--node" })
private String nodeId; private String nodeId;
@Override @Override

View File

@ -25,16 +25,14 @@ import java.util.SortedMap;
import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ArrayListMultimap;
import io.airlift.airline.Command;
import org.apache.cassandra.locator.DynamicEndpointSnitch; import org.apache.cassandra.locator.DynamicEndpointSnitch;
import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.locator.LocationInfoMBean; import org.apache.cassandra.locator.LocationInfoMBean;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool; import picocli.CommandLine.Command;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
@Command(name = "describecluster", description = "Print the name, snitch, partitioner and schema version of a cluster") @Command(name = "describecluster", description = "Print the name, snitch, partitioner and schema version of a cluster")
public class DescribeCluster extends NodeToolCmd public class DescribeCluster extends WithPortDisplayAbstractCommand
{ {
private boolean resolveIp = false; private boolean resolveIp = false;
private String keyspace = null; private String keyspace = null;
@ -114,7 +112,7 @@ public class DescribeCluster extends NodeToolCmd
System.exit(1); System.exit(1);
} }
SortedMap<String, SetHostStatWithPort> dcs = NodeTool.getOwnershipByDcWithPort(probe, resolveIp, tokensToEndpoints, ownerships); SortedMap<String, SetHostStatWithPort> dcs = CommandUtils.getOwnershipByDcWithPort(probe, resolveIp, tokensToEndpoints, ownerships);
out.println("\nData Centers: "); out.println("\nData Centers: ");
for (Map.Entry<String, SetHostStatWithPort> dc : dcs.entrySet()) for (Map.Entry<String, SetHostStatWithPort> dc : dcs.entrySet())

View File

@ -17,21 +17,20 @@
*/ */
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import static org.apache.commons.lang3.StringUtils.EMPTY;
import io.airlift.airline.Arguments;
import io.airlift.airline.Command;
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream; import java.io.PrintStream;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;
import static org.apache.commons.lang3.StringUtils.EMPTY;
@Command(name = "describering", description = "Shows the token ranges info of a given keyspace") @Command(name = "describering", description = "Shows the token ranges info of a given keyspace")
public class DescribeRing extends NodeToolCmd public class DescribeRing extends WithPortDisplayAbstractCommand
{ {
@Arguments(description = "The keyspace name", required = true) @Parameters(description = "The keyspace name", arity = "1")
String keyspace = EMPTY; private String keyspace = EMPTY;
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)

View File

@ -18,12 +18,11 @@
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Command;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import picocli.CommandLine.Command;
@Command(name = "disableauditlog", description = "Disable the audit log") @Command(name = "disableauditlog", description = "Disable the audit log")
public class DisableAuditLog extends NodeToolCmd public class DisableAuditLog extends AbstractCommand
{ {
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)

View File

@ -17,25 +17,36 @@
*/ */
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Arguments;
import io.airlift.airline.Command;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import org.apache.cassandra.tools.nodetool.layout.CassandraUsage;
import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;
import static org.apache.cassandra.tools.nodetool.CommandUtils.parseOptionalKeyspace;
import static org.apache.cassandra.tools.nodetool.CommandUtils.parseOptionalTables;
import static org.apache.cassandra.tools.nodetool.CommandUtils.concatArgs;
@Command(name = "disableautocompaction", description = "Disable autocompaction for the given keyspace and table") @Command(name = "disableautocompaction", description = "Disable autocompaction for the given keyspace and table")
public class DisableAutoCompaction extends NodeToolCmd public class DisableAutoCompaction extends AbstractCommand
{ {
@Arguments(usage = "[<keyspace> <tables>...]", description = "The keyspace followed by one or many tables") @CassandraUsage(usage = "[<keyspace> <tables>...]", description = "The keyspace followed by one or many tables")
private List<String> args = new ArrayList<>(); private List<String> args = new ArrayList<>();
@Parameters(index = "0", description = "The keyspace name", arity = "0..1")
private String keyspace;
@Parameters(index = "1..*", description = "The table names", arity = "0..*")
private String[] tables;
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)
{ {
args = concatArgs(keyspace, tables);
List<String> keyspaces = parseOptionalKeyspace(args, probe); List<String> keyspaces = parseOptionalKeyspace(args, probe);
String[] tablenames = parseOptionalTables(args); String[] tablenames = parseOptionalTables(args);

View File

@ -17,13 +17,11 @@
*/ */
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Command;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import picocli.CommandLine.Command;
@Command(name = "disablebackup", description = "Disable incremental backup") @Command(name = "disablebackup", description = "Disable incremental backup")
public class DisableBackup extends NodeToolCmd public class DisableBackup extends AbstractCommand
{ {
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)

View File

@ -17,15 +17,14 @@
*/ */
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Command;
import io.airlift.airline.Option;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
@Command(name = "disablebinary", description = "Disable native transport (binary protocol)") @Command(name = "disablebinary", description = "Disable native transport (binary protocol)")
public class DisableBinary extends NodeToolCmd public class DisableBinary extends AbstractCommand
{ {
@Option(title = "force", name = { "-f", "--force"}, description = "Use -f to interrupt client requests that have already started") @Option(paramLabel = "force", names = { "-f", "--force" }, description = "Use -f to interrupt client requests that have already started")
private boolean force = false; private boolean force = false;
@Override @Override

View File

@ -18,12 +18,11 @@
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Command;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import picocli.CommandLine.Command;
@Command(name = "disablefullquerylog", description = "Disable the full query log") @Command(name = "disablefullquerylog", description = "Disable the full query log")
public class DisableFullQueryLog extends NodeToolCmd public class DisableFullQueryLog extends AbstractCommand
{ {
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)

View File

@ -17,13 +17,11 @@
*/ */
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Command;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import picocli.CommandLine.Command;
@Command(name = "disablegossip", description = "Disable gossip (effectively marking the node down)") @Command(name = "disablegossip", description = "Disable gossip (effectively marking the node down)")
public class DisableGossip extends NodeToolCmd public class DisableGossip extends AbstractCommand
{ {
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)

View File

@ -17,13 +17,11 @@
*/ */
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Command;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import picocli.CommandLine.Command;
@Command(name = "disablehandoff", description = "Disable storing hinted handoffs") @Command(name = "disablehandoff", description = "Disable storing hinted handoffs")
public class DisableHandoff extends NodeToolCmd public class DisableHandoff extends AbstractCommand
{ {
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)

View File

@ -20,17 +20,17 @@ package org.apache.cassandra.tools.nodetool;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import io.airlift.airline.Arguments;
import io.airlift.airline.Command;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool; import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
@Command(name = "disablehintsfordc", description = "Disable hints for a data center") @Command(name = "disablehintsfordc", description = "Disable hints for a data center")
public class DisableHintsForDC extends NodeTool.NodeToolCmd public class DisableHintsForDC extends AbstractCommand
{ {
@Arguments(usage = "<datacenter>", description = "The data center to disable") @Parameters(paramLabel = "datacenter", description = "The data center to disable")
private List<String> args = new ArrayList<>(); private List<String> args = new ArrayList<>();
public void execute(NodeProbe probe) public void execute(NodeProbe probe)

View File

@ -18,12 +18,11 @@
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Command;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool; import picocli.CommandLine.Command;
@Command(name = "disableoldprotocolversions", description = "Disable old protocol versions") @Command(name = "disableoldprotocolversions", description = "Disable old protocol versions")
public class DisableOldProtocolVersions extends NodeTool.NodeToolCmd public class DisableOldProtocolVersions extends AbstractCommand
{ {
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)

View File

@ -17,16 +17,15 @@
*/ */
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Command;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import picocli.CommandLine.Command;
@Command(name = "drain", description = "Drain the node (stop accepting writes and flush all tables)") @Command(name = "drain", description = "Drain the node (stop accepting writes and flush all tables)")
public class Drain extends NodeToolCmd public class Drain extends AbstractCommand
{ {
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)

View File

@ -17,34 +17,26 @@
*/ */
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import java.util.ArrayList;
import java.util.List;
import io.airlift.airline.Arguments;
import io.airlift.airline.Command;
import org.apache.cassandra.auth.AuthKeyspace; import org.apache.cassandra.auth.AuthKeyspace;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
import static com.google.common.base.Preconditions.checkArgument; import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;
/** /**
* Nodetool command to drop a CIDR group and associated mapping from the table {@link AuthKeyspace#CIDR_GROUPS} * Nodetool command to drop a CIDR group and associated mapping from the table {@link AuthKeyspace#CIDR_GROUPS}
*/ */
@Command(name = "dropcidrgroup", description = "Drop an existing cidr group") @Command(name = "dropcidrgroup", description = "Drop an existing cidr group")
public class DropCIDRGroup extends NodeToolCmd public class DropCIDRGroup extends AbstractCommand
{ {
@Arguments(usage = "<cidrGroup>", description = "Requires a cidr group name") @Parameters(paramLabel = "cidrGroup", description = "Requires a cidr group name", index = "0", arity = "1")
private List<String> args = new ArrayList<>(); private String cidrGroup;
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)
{ {
checkArgument(args.size() == 1, "dropcidrgroup command requires a cidr group name"); probe.dropCidrGroup(cidrGroup);
String cidrGroupName = args.get(0); probe.output().out.println("Deleted CIDR group " + cidrGroup);
probe.dropCidrGroup(cidrGroupName);
probe.output().out.println("Deleted CIDR group " + cidrGroupName);
} }
} }

View File

@ -20,54 +20,53 @@ package org.apache.cassandra.tools.nodetool;
import java.util.Collections; import java.util.Collections;
import io.airlift.airline.Command;
import io.airlift.airline.Option;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
@Command(name = "enableauditlog", description = "Enable the audit log") @Command(name = "enableauditlog", description = "Enable the audit log")
public class EnableAuditLog extends NodeToolCmd public class EnableAuditLog extends AbstractCommand
{ {
@Option(title = "logger", name = { "--logger" }, description = "Logger name to be used for AuditLogging. Default BinAuditLogger. If not set the value from cassandra.yaml will be used") @Option(paramLabel = "logger", names = { "--logger" }, description = "Logger name to be used for AuditLogging. Default BinAuditLogger. If not set the value from cassandra.yaml will be used")
private String logger = null; private String logger = null;
@Option(title = "included_keyspaces", name = { "--included-keyspaces" }, description = "Comma separated list of keyspaces to be included for audit log. If not set the value from cassandra.yaml will be used") @Option(paramLabel = "included_keyspaces", names = { "--included-keyspaces" }, description = "Comma separated list of keyspaces to be included for audit log. If not set the value from cassandra.yaml will be used")
private String included_keyspaces = null; private String included_keyspaces = null;
@Option(title = "excluded_keyspaces", name = { "--excluded-keyspaces" }, description = "Comma separated list of keyspaces to be excluded for audit log. If not set the value from cassandra.yaml will be used") @Option(paramLabel = "excluded_keyspaces", names = { "--excluded-keyspaces" }, description = "Comma separated list of keyspaces to be excluded for audit log. If not set the value from cassandra.yaml will be used")
private String excluded_keyspaces = null; private String excluded_keyspaces = null;
@Option(title = "included_categories", name = { "--included-categories" }, description = "Comma separated list of Audit Log Categories to be included for audit log. If not set the value from cassandra.yaml will be used") @Option(paramLabel = "included_categories", names = { "--included-categories" }, description = "Comma separated list of Audit Log Categories to be included for audit log. If not set the value from cassandra.yaml will be used")
private String included_categories = null; private String included_categories = null;
@Option(title = "excluded_categories", name = { "--excluded-categories" }, description = "Comma separated list of Audit Log Categories to be excluded for audit log. If not set the value from cassandra.yaml will be used") @Option(paramLabel = "excluded_categories", names = { "--excluded-categories" }, description = "Comma separated list of Audit Log Categories to be excluded for audit log. If not set the value from cassandra.yaml will be used")
private String excluded_categories = null; private String excluded_categories = null;
@Option(title = "included_users", name = { "--included-users" }, description = "Comma separated list of users to be included for audit log. If not set the value from cassandra.yaml will be used") @Option(paramLabel = "included_users", names = { "--included-users" }, description = "Comma separated list of users to be included for audit log. If not set the value from cassandra.yaml will be used")
private String included_users = null; private String included_users = null;
@Option(title = "excluded_users", name = { "--excluded-users" }, description = "Comma separated list of users to be excluded for audit log. If not set the value from cassandra.yaml will be used") @Option(paramLabel = "excluded_users", names = { "--excluded-users" }, description = "Comma separated list of users to be excluded for audit log. If not set the value from cassandra.yaml will be used")
private String excluded_users = null; private String excluded_users = null;
@Option(title = "roll_cycle", name = {"--roll-cycle"}, description = "How often to roll the log file (MINUTELY, HOURLY, DAILY).") @Option(paramLabel = "roll_cycle", names = { "--roll-cycle" }, description = "How often to roll the log file (MINUTELY, HOURLY, DAILY).")
private String rollCycle = null; private String rollCycle = null;
@Option(title = "blocking", name = {"--blocking"}, description = "If the queue is full whether to block producers or drop samples [true|false].") @Option(paramLabel = "blocking", names = { "--blocking" }, description = "If the queue is full whether to block producers or drop samples [true|false].")
private String blocking = null; private String blocking = null;
@Option(title = "max_queue_weight", name = {"--max-queue-weight"}, description = "Maximum number of bytes of query data to queue to disk before blocking or dropping samples.") @Option(paramLabel = "max_queue_weight", names = { "--max-queue-weight" }, description = "Maximum number of bytes of query data to queue to disk before blocking or dropping samples.")
private int maxQueueWeight = Integer.MIN_VALUE; private int maxQueueWeight = Integer.MIN_VALUE;
@Option(title = "max_log_size", name = {"--max-log-size"}, description = "How many bytes of log data to store before dropping segments. Might not be respected if a log file hasn't rolled so it can be deleted.") @Option(paramLabel = "max_log_size", names = { "--max-log-size" }, description = "How many bytes of log data to store before dropping segments. Might not be respected if a log file hasn't rolled so it can be deleted.")
private long maxLogSize = Long.MIN_VALUE; private long maxLogSize = Long.MIN_VALUE;
@Option(title = "archive_command", name = {"--archive-command"}, description = "Command that will handle archiving rolled audit log files." + @Option(paramLabel = "archive_command", names = { "--archive-command" }, description = "Command that will handle archiving rolled audit log files." +
" Format is \"/path/to/script.sh %path\" where %path will be replaced with the file to archive" + " Format is \"/path/to/script.sh %%path\" where %%path will be replaced with the file to archive" +
" Enable this by setting the audit_logging_options.allow_nodetool_archive_command: true in the config.") " Enable this by setting the audit_logging_options.allow_nodetool_archive_command: true in the config.")
private String archiveCommand = null; private String archiveCommand = null;
@Option(title = "archive_retries", name = {"--max-archive-retries"}, description = "Max number of archive retries.") @Option(paramLabel = "archive_retries", names = { "--max-archive-retries" }, description = "Max number of archive retries.")
private int archiveRetries = Integer.MIN_VALUE; private int archiveRetries = Integer.MIN_VALUE;
@Override @Override

View File

@ -17,25 +17,37 @@
*/ */
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Arguments;
import io.airlift.airline.Command;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
import org.apache.cassandra.tools.nodetool.layout.CassandraUsage;
import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;
import static org.apache.cassandra.tools.nodetool.CommandUtils.parseOptionalKeyspace;
import static org.apache.cassandra.tools.nodetool.CommandUtils.parseOptionalTables;
import static org.apache.cassandra.tools.nodetool.CommandUtils.concatArgs;
@Command(name = "enableautocompaction", description = "Enable autocompaction for the given keyspace and table") @Command(name = "enableautocompaction", description = "Enable autocompaction for the given keyspace and table")
public class EnableAutoCompaction extends NodeToolCmd public class EnableAutoCompaction extends AbstractCommand
{ {
@Arguments(usage = "[<keyspace> <tables>...]", description = "The keyspace followed by one or many tables") @CassandraUsage(usage = "[<keyspace> <tables>...]", description = "The keyspace followed by one or many tables")
private List<String> args = new ArrayList<>(); private List<String> args = new ArrayList<>();
@Parameters(index = "0", description = "The keyspace to enable auto-compaction on", arity = "0..1")
private String keyspace;
@Parameters(index = "1..*", description = "The tables to enable auto-compaction on", arity = "0..*")
private String[] tables;
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)
{ {
args = concatArgs(keyspace, tables);
List<String> keyspaces = parseOptionalKeyspace(args, probe); List<String> keyspaces = parseOptionalKeyspace(args, probe);
String[] tableNames = parseOptionalTables(args); String[] tableNames = parseOptionalTables(args);

View File

@ -17,13 +17,11 @@
*/ */
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Command;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import picocli.CommandLine.Command;
@Command(name = "enablebackup", description = "Enable incremental backup") @Command(name = "enablebackup", description = "Enable incremental backup")
public class EnableBackup extends NodeToolCmd public class EnableBackup extends AbstractCommand
{ {
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)

View File

@ -17,13 +17,11 @@
*/ */
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Command;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import picocli.CommandLine.Command;
@Command(name = "enablebinary", description = "Reenable native transport (binary protocol)") @Command(name = "enablebinary", description = "Reenable native transport (binary protocol)")
public class EnableBinary extends NodeToolCmd public class EnableBinary extends AbstractCommand
{ {
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)

View File

@ -18,35 +18,34 @@
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Command;
import io.airlift.airline.Option;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
@Command(name = "enablefullquerylog", description = "Enable full query logging, defaults for the options are configured in cassandra.yaml") @Command(name = "enablefullquerylog", description = "Enable full query logging, defaults for the options are configured in cassandra.yaml")
public class EnableFullQueryLog extends NodeToolCmd public class EnableFullQueryLog extends AbstractCommand
{ {
@Option(title = "roll_cycle", name = {"--roll-cycle"}, description = "How often to roll the log file (MINUTELY, HOURLY, DAILY).") @Option(paramLabel = "roll_cycle", names = { "--roll-cycle" }, description = "How often to roll the log file (MINUTELY, HOURLY, DAILY).")
private String rollCycle = null; private String rollCycle = null;
@Option(title = "blocking", name = {"--blocking"}, description = "If the queue is full whether to block producers or drop samples [true|false].") @Option(paramLabel = "blocking", names = { "--blocking" }, description = "If the queue is full whether to block producers or drop samples [true|false].")
private String blocking = null; private String blocking = null;
@Option(title = "max_queue_weight", name = {"--max-queue-weight"}, description = "Maximum number of bytes of query data to queue to disk before blocking or dropping samples.") @Option(paramLabel = "max_queue_weight", names = { "--max-queue-weight" }, description = "Maximum number of bytes of query data to queue to disk before blocking or dropping samples.")
private int maxQueueWeight = Integer.MIN_VALUE; private int maxQueueWeight = Integer.MIN_VALUE;
@Option(title = "max_log_size", name = {"--max-log-size"}, description = "How many bytes of log data to store before dropping segments. Might not be respected if a log file hasn't rolled so it can be deleted.") @Option(paramLabel = "max_log_size", names = { "--max-log-size" }, description = "How many bytes of log data to store before dropping segments. Might not be respected if a log file hasn't rolled so it can be deleted.")
private long maxLogSize = Long.MIN_VALUE; private long maxLogSize = Long.MIN_VALUE;
@Option(title = "path", name = {"--path"}, description = "Path to store the full query log at. Will have it's contents recursively deleted.") @Option(paramLabel = "path", names = { "--path" }, description = "Path to store the full query log at. Will have it's contents recursively deleted.")
private String path = null; private String path = null;
@Option(title = "archive_command", name = {"--archive-command"}, description = "Command that will handle archiving rolled full query log files." + @Option(paramLabel = "archive_command", names = { "--archive-command" }, description = "Command that will handle archiving rolled full query log files." +
" Format is \"/path/to/script.sh %path\" where %path will be replaced with the file to archive" + " Format is \"/path/to/script.sh %%path\" where %%path will be replaced with the file to archive" +
" Enable this by setting the full_query_logging_options.allow_nodetool_archive_command: true in the config.") " Enable this by setting the full_query_logging_options.allow_nodetool_archive_command: true in the config.")
private String archiveCommand = null; private String archiveCommand = null;
@Option(title = "archive_retries", name = {"--max-archive-retries"}, description = "Max number of archive retries.") @Option(paramLabel = "archive_retries", names = { "--max-archive-retries" }, description = "Max number of archive retries.")
private int archiveRetries = Integer.MIN_VALUE; private int archiveRetries = Integer.MIN_VALUE;
@Override @Override

View File

@ -17,13 +17,11 @@
*/ */
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Command;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import picocli.CommandLine.Command;
@Command(name = "enablegossip", description = "Reenable gossip") @Command(name = "enablegossip", description = "Reenable gossip")
public class EnableGossip extends NodeToolCmd public class EnableGossip extends AbstractCommand
{ {
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)

View File

@ -17,13 +17,11 @@
*/ */
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Command;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import picocli.CommandLine.Command;
@Command(name = "enablehandoff", description = "Reenable future hints storing on the current node") @Command(name = "enablehandoff", description = "Reenable future hints storing on the current node")
public class EnableHandoff extends NodeToolCmd public class EnableHandoff extends AbstractCommand
{ {
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)

View File

@ -17,26 +17,19 @@
*/ */
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import java.util.ArrayList;
import java.util.List;
import io.airlift.airline.Arguments;
import io.airlift.airline.Command;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool;
import static com.google.common.base.Preconditions.checkArgument; import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;
@Command(name = "enablehintsfordc", description = "Enable hints for a data center that was previsouly disabled") @Command(name = "enablehintsfordc", description = "Enable hints for a data center that was previsouly disabled")
public class EnableHintsForDC extends NodeTool.NodeToolCmd public class EnableHintsForDC extends AbstractCommand
{ {
@Arguments(usage = "<datacenter>", description = "The data center to enable") @Parameters(paramLabel = "datacenter", description = "The data center to enable", index = "0", arity = "1")
private List<String> args = new ArrayList<>(); private String datacenter;
public void execute(NodeProbe probe) public void execute(NodeProbe probe)
{ {
checkArgument(args.size() == 1, "enablehintsfordc requires exactly one data center"); probe.enableHintsForDC(datacenter);
probe.enableHintsForDC(args.get(0));
} }
} }

View File

@ -18,13 +18,11 @@
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Command;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool; import picocli.CommandLine.Command;
@Command(name = "enableoldprotocolversions", description = "Enable old protocol versions") @Command(name = "enableoldprotocolversions", description = "Enable old protocol versions")
public class EnableOldProtocolVersions extends NodeTool.NodeToolCmd public class EnableOldProtocolVersions extends AbstractCommand
{ {
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)

View File

@ -17,18 +17,15 @@
*/ */
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Command;
import java.util.List; import java.util.List;
import javax.management.openmbean.CompositeData; import javax.management.openmbean.CompositeData;
import javax.management.openmbean.TabularData; import javax.management.openmbean.TabularData;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import picocli.CommandLine.Command;
@Command(name = "failuredetector", description = "Shows the failure detector information for the cluster") @Command(name = "failuredetector", description = "Shows the failure detector information for the cluster")
public class FailureDetectorInfo extends NodeToolCmd public class FailureDetectorInfo extends WithPortDisplayAbstractCommand
{ {
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)

View File

@ -17,24 +17,35 @@
*/ */
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Arguments;
import io.airlift.airline.Command;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import org.apache.cassandra.tools.nodetool.layout.CassandraUsage;
import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;
import static org.apache.cassandra.tools.nodetool.CommandUtils.parseOptionalKeyspace;
import static org.apache.cassandra.tools.nodetool.CommandUtils.parseOptionalTables;
import static org.apache.cassandra.tools.nodetool.CommandUtils.concatArgs;
@Command(name = "flush", description = "Flush one or more tables") @Command(name = "flush", description = "Flush one or more tables")
public class Flush extends NodeToolCmd public class Flush extends AbstractCommand
{ {
@Arguments(usage = "[<keyspace> <tables>...]", description = "The keyspace followed by one or many tables") @CassandraUsage(usage = "[<keyspace> <tables>...]", description = "The keyspace followed by one or many tables")
private List<String> args = new ArrayList<>(); private List<String> args = new ArrayList<>();
@Parameters(index = "0", description = "The keyspace followed by one or many tables to flush", arity = "0..1")
private String keyspace;
@Parameters(index = "1..*", description = "The tables to flush", arity = "0..*")
private String[] tables;
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)
{ {
args = concatArgs(keyspace, tables);
List<String> keyspaces = parseOptionalKeyspace(args, probe); List<String> keyspaces = parseOptionalKeyspace(args, probe);
String[] tableNames = parseOptionalTables(args); String[] tableNames = parseOptionalTables(args);

View File

@ -18,26 +18,37 @@
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Arguments;
import io.airlift.airline.Command;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import org.apache.cassandra.tools.nodetool.layout.CassandraUsage;
import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static org.apache.cassandra.tools.nodetool.CommandUtils.parsePartitionKeys;
import static org.apache.cassandra.tools.nodetool.CommandUtils.concatArgs;
@Command(name = "forcecompact", description = "Force a (major) compaction on a table") @Command(name = "forcecompact", description = "Force a (major) compaction on a table")
public class ForceCompact extends NodeToolCmd public class ForceCompact extends AbstractCommand
{ {
@Arguments(usage = "[<keyspace> <table> <keys>]", description = "The keyspace, table, and a list of partition keys ignoring the gc_grace_seconds") @CassandraUsage(usage = "[<keyspace> <table> <keys>]", description = "The keyspace, table, and a list of partition keys ignoring the gc_grace_seconds")
private List<String> args = new ArrayList<>(); private List<String> args = new ArrayList<>();
@Parameters(index = "0", description = "The keyspace name to compact", arity = "0..1")
private String keyspace;
@Parameters(index = "1", description = "The table name to compact", arity = "0..1")
private String table;
@Parameters(index = "2..*", description = "The partition keys to compact", arity = "0..*")
private String[] keys;
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)
{ {
args = concatArgs(keyspace, table, keys);
// Check if the input has valid size // Check if the input has valid size
checkArgument(args.size() >= 3, "forcecompact requires keyspace, table and keys args"); checkArgument(args.size() >= 3, "forcecompact requires keyspace, table and keys args");

View File

@ -17,30 +17,40 @@
*/ */
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Arguments;
import io.airlift.airline.Command;
import io.airlift.airline.Option;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.apache.cassandra.schema.CompactionParams;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
import org.apache.cassandra.tools.nodetool.layout.CassandraUsage;
import picocli.CommandLine.Parameters;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import static org.apache.cassandra.tools.nodetool.CommandUtils.parseOptionalKeyspace;
import static org.apache.cassandra.tools.nodetool.CommandUtils.parseOptionalTables;
import static org.apache.cassandra.tools.nodetool.CommandUtils.concatArgs;
@Command(name = "garbagecollect", description = "Remove deleted data from one or more tables") @Command(name = "garbagecollect", description = "Remove deleted data from one or more tables")
public class GarbageCollect extends NodeToolCmd public class GarbageCollect extends AbstractCommand
{ {
@Arguments(usage = "[<keyspace> <tables>...]", description = "The keyspace followed by one or many tables") @CassandraUsage(usage = "[<keyspace> <tables>...]", description = "The keyspace followed by one or many tables")
private List<String> args = new ArrayList<>(); private List<String> args = new ArrayList<>();
@Option(title = "granularity", @Parameters(index = "0", description = "The keyspace followed by one or many tables to garbage collect", arity = "0..1")
name = {"-g", "--granularity"}, private String keyspace;
allowedValues = {"ROW", "CELL"},
description = "Granularity of garbage removal. ROW (default) removes deleted partitions and rows, CELL also removes overwritten or deleted cells.")
private String tombstoneOption = "ROW";
@Option(title = "jobs", @Parameters(index = "1..*", description = "The tables to garbage collect", arity = "0..*")
name = {"-j", "--jobs"}, private String[] tables;
@Option(paramLabel = "granularity",
names = { "-g", "--granularity" },
description = "Granularity of garbage removal. ROW (default) removes deleted partitions and rows, CELL also removes overwritten or deleted cells.")
private CompactionParams.TombstoneOption tombstoneOption = CompactionParams.TombstoneOption.ROW;
@Option(paramLabel = "jobs",
names = { "-j", "--jobs" },
description = "Number of sstables to cleanup simultanously, set to 0 to use all available compaction " + description = "Number of sstables to cleanup simultanously, set to 0 to use all available compaction " +
"threads. Defaults to 1 so that collections of newer tables can see the data is deleted " + "threads. Defaults to 1 so that collections of newer tables can see the data is deleted " +
"and also remove tombstones.") "and also remove tombstones.")
@ -49,6 +59,8 @@ public class GarbageCollect extends NodeToolCmd
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)
{ {
args = concatArgs(keyspace, tables);
List<String> keyspaces = parseOptionalKeyspace(args, probe); List<String> keyspaces = parseOptionalKeyspace(args, probe);
String[] tableNames = parseOptionalTables(args); String[] tableNames = parseOptionalTables(args);
@ -56,7 +68,7 @@ public class GarbageCollect extends NodeToolCmd
{ {
try try
{ {
probe.garbageCollect(probe.output().out, tombstoneOption, jobs, keyspace, tableNames); probe.garbageCollect(probe.output().out, tombstoneOption.toString(), jobs, keyspace, tableNames);
} catch (Exception e) } catch (Exception e)
{ {
throw new RuntimeException("Error occurred during garbage collection", e); throw new RuntimeException("Error occurred during garbage collection", e);

View File

@ -17,24 +17,23 @@
*/ */
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Command;
import io.airlift.airline.Option;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
import org.apache.cassandra.tools.nodetool.stats.GcStatsHolder; import org.apache.cassandra.tools.nodetool.stats.GcStatsHolder;
import org.apache.cassandra.tools.nodetool.stats.GcStatsPrinter; import org.apache.cassandra.tools.nodetool.stats.GcStatsPrinter;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
@Command(name = "gcstats", description = "Print GC Statistics") @Command(name = "gcstats", description = "Print GC Statistics")
public class GcStats extends NodeToolCmd public class GcStats extends AbstractCommand
{ {
@Option(title = "format", @Option(paramLabel = "format",
name = { "-F", "--format" }, names = { "-F", "--format" },
description = "Output format (json, yaml, table)") description = "Output format (json, yaml, table)")
private String outputFormat = ""; private String outputFormat = "";
@Option(title = "human_readable", @Option(paramLabel = "human_readable",
name = { "-H", "--human-readable" }, names = { "-H", "--human-readable" },
description = "Display gcstats with human-readable units") description = "Display gcstats with human-readable units")
private boolean humanReadable = false; private boolean humanReadable = false;
@Override @Override

View File

@ -18,14 +18,13 @@
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Command;
import org.apache.cassandra.audit.AuditLogOptions; import org.apache.cassandra.audit.AuditLogOptions;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool;
import org.apache.cassandra.tools.nodetool.formatter.TableBuilder; import org.apache.cassandra.tools.nodetool.formatter.TableBuilder;
import picocli.CommandLine.Command;
@Command(name = "getauditlog", description = "Print configuration of audit log if enabled, otherwise the configuration reflected in cassandra.yaml") @Command(name = "getauditlog", description = "Print configuration of audit log if enabled, otherwise the configuration reflected in cassandra.yaml")
public class GetAuditLog extends NodeTool.NodeToolCmd public class GetAuditLog extends AbstractCommand
{ {
@Override @Override
protected void execute(NodeProbe probe) protected void execute(NodeProbe probe)

View File

@ -18,18 +18,16 @@
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Command;
import io.airlift.airline.Option;
import org.apache.cassandra.auth.AuthCacheMBean; import org.apache.cassandra.auth.AuthCacheMBean;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool; import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
@Command(name = "getauthcacheconfig", description = "Get configuration of Auth cache") @Command(name = "getauthcacheconfig", description = "Get configuration of Auth cache")
public class GetAuthCacheConfig extends NodeTool.NodeToolCmd public class GetAuthCacheConfig extends AbstractCommand
{ {
@SuppressWarnings("unused") @Option(paramLabel = "cache-name",
@Option(title = "cache-name", names = { "--cache-name" },
name = {"--cache-name"},
description = "Name of Auth cache (required)", description = "Name of Auth cache (required)",
required = true) required = true)
private String cacheName; private String cacheName;

View File

@ -21,15 +21,14 @@ import java.io.PrintStream;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import io.airlift.airline.Command;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import picocli.CommandLine.Command;
/** /**
* Prints all the configurations for AutoRepair through nodetool. * Prints all the configurations for AutoRepair through nodetool.
*/ */
@Command(name = "getautorepairconfig", description = "Print autorepair configurations") @Command(name = "getautorepairconfig", description = "Print autorepair configurations")
public class GetAutoRepairConfig extends NodeToolCmd public class GetAutoRepairConfig extends AbstractCommand
{ {
@VisibleForTesting @VisibleForTesting
protected static PrintStream out = System.out; protected static PrintStream out = System.out;

View File

@ -17,13 +17,12 @@
*/ */
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Command;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import picocli.CommandLine.Command;
@Command(name = "getbatchlogreplaythrottle", description = "Print batchlog replay throttle in KB/s. " + @Command(name = "getbatchlogreplaythrottle", description = "Print batchlog replay throttle in KB/s. " +
"This is reduced proportionally to the number of nodes in the cluster.") "This is reduced proportionally to the number of nodes in the cluster.")
public class GetBatchlogReplayTrottle extends NodeToolCmd public class GetBatchlogReplayTrottle extends AbstractCommand
{ {
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)

View File

@ -17,34 +17,24 @@
*/ */
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import io.airlift.airline.Arguments;
import io.airlift.airline.Command;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;
import static com.google.common.base.Preconditions.checkArgument; import static org.apache.cassandra.tools.nodetool.CommandUtils.printSet;
/** /**
* Nodetool command to get CIDR groups(s) of given IP * Nodetool command to get CIDR groups(s) of given IP
*/ */
@Command(name = "getcidrgroupsofip", description = "Print CIDR groups associated with given IP") @Command(name = "getcidrgroupsofip", description = "Print CIDR groups associated with given IP")
public class GetCIDRGroupsOfIP extends NodeToolCmd public class GetCIDRGroupsOfIP extends AbstractCommand
{ {
@Arguments(usage = "<IP address>", description = "Requires IP address as a string") @Parameters(paramLabel = "ip_address", description = "Requires IP address as a string", arity = "1", index = "0")
private List<String> args = new ArrayList<>(); private String ipStr;
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)
{ {
PrintStream out = probe.output().out; printSet(probe.output().out, "CIDR Groups", probe.getCidrGroupsOfIp(ipStr));
checkArgument(args.size() == 1, "Requires IP address as input");
String ipStr = args.get(0);
probe.printSet(out, "CIDR Groups", probe.getCidrGroupsOfIp(ipStr));
} }
} }

View File

@ -18,12 +18,11 @@
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Command;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import picocli.CommandLine.Command;
@Command(name = "getcolumnindexsize", description = "Print the granularity of the collation index of rows within a partition in KiB") @Command(name = "getcolumnindexsize", description = "Print the granularity of the collation index of rows within a partition in KiB")
public class GetColumnIndexSize extends NodeToolCmd public class GetColumnIndexSize extends AbstractCommand
{ {
@Override @Override
protected void execute(NodeProbe probe) protected void execute(NodeProbe probe)

View File

@ -18,25 +18,34 @@
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import io.airlift.airline.Arguments; import static org.apache.cassandra.tools.nodetool.CommandUtils.concatArgs;
import io.airlift.airline.Command;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.apache.cassandra.db.ColumnFamilyStoreMBean; import org.apache.cassandra.db.ColumnFamilyStoreMBean;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import org.apache.cassandra.tools.nodetool.layout.CassandraUsage;
import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;
@Command(name = "getcompactionthreshold", description = "Print min and max compaction thresholds for a given table") @Command(name = "getcompactionthreshold", description = "Print min and max compaction thresholds for a given table")
public class GetCompactionThreshold extends NodeToolCmd public class GetCompactionThreshold extends AbstractCommand
{ {
@Arguments(usage = "<keyspace> <table>", description = "The keyspace with a table") @CassandraUsage(usage = "<keyspace> <table>", description = "The keyspace with a table")
private List<String> args = new ArrayList<>(); private List<String> args = new ArrayList<>();
@Parameters(index = "0", arity = "1", description = "The keyspace to get the compaction threshold for")
private String keyspace;
@Parameters(index = "1", arity = "1", description = "The table to get the compaction threshold for")
private String table;
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)
{ {
args = concatArgs(keyspace, table);
checkArgument(args.size() == 2, "getcompactionthreshold requires ks and cf args"); checkArgument(args.size() == 2, "getcompactionthreshold requires ks and cf args");
String ks = args.get(0); String ks = args.get(0);
String cf = args.get(1); String cf = args.get(1);

View File

@ -21,17 +21,14 @@ import java.util.Map;
import com.google.common.math.DoubleMath; import com.google.common.math.DoubleMath;
import io.airlift.airline.Command;
import io.airlift.airline.Option;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
@Command(name = "getcompactionthroughput", description = "Print the MiB/s throughput cap for compaction in the system as a rounded number") @Command(name = "getcompactionthroughput", description = "Print the MiB/s throughput cap for compaction in the system as a rounded number")
public class GetCompactionThroughput extends NodeToolCmd public class GetCompactionThroughput extends AbstractCommand
{ {
@SuppressWarnings("UnusedDeclaration") @Option(names = { "-d", "--precise-mib" }, description = "Print the MiB/s throughput cap for compaction in the system as a precise number (double)")
@Option(name = { "-d", "--precise-mib" }, description = "Print the MiB/s throughput cap for compaction in the system as a precise number (double)")
private boolean compactionThroughputAsDouble; private boolean compactionThroughputAsDouble;
@Override @Override

View File

@ -21,17 +21,16 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import io.airlift.airline.Arguments;
import io.airlift.airline.Command;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import picocli.CommandLine.Parameters;
import picocli.CommandLine.Command;
@Command(name = "getconcurrency", description = "Get maximum concurrency for processing stages") @Command(name = "getconcurrency", description = "Get maximum concurrency for processing stages")
public class GetConcurrency extends NodeToolCmd public class GetConcurrency extends AbstractCommand
{ {
@Arguments(title = "[stage-names]", @Parameters(paramLabel = "stage-names",
usage = "[stage-names]", description = "Optional list of stage names, otherwise display all stages",
description = "optional list of stage names, otherwise display all stages") index = "0..*")
private List<String> args = new ArrayList<>(); private List<String> args = new ArrayList<>();
@Override @Override

View File

@ -18,16 +18,15 @@
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Command;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import picocli.CommandLine.Command;
@Command(name = "getconcurrentcompactors", description = "Get the number of concurrent compactors in the system.") @Command(name = "getconcurrentcompactors", description = "Get the number of concurrent compactors in the system.")
public class GetConcurrentCompactors extends NodeToolCmd public class GetConcurrentCompactors extends AbstractCommand
{ {
protected void execute(NodeProbe probe) protected void execute(NodeProbe probe)
{ {
probe.output().out.println("Current concurrent compactors in the system is: \n" + probe.output().out.println("Current concurrent compactors in the system is: \n" +
probe.getConcurrentCompactors()); probe.getConcurrentCompactors());
} }
} }

View File

@ -18,12 +18,11 @@
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Command;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import picocli.CommandLine.Command;
@Command(name = "getconcurrentviewbuilders", description = "Get the number of concurrent view builders in the system") @Command(name = "getconcurrentviewbuilders", description = "Get the number of concurrent view builders in the system")
public class GetConcurrentViewBuilders extends NodeToolCmd public class GetConcurrentViewBuilders extends AbstractCommand
{ {
protected void execute(NodeProbe probe) protected void execute(NodeProbe probe)
{ {

View File

@ -18,12 +18,11 @@
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Command;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool; import picocli.CommandLine.Command;
@Command(name = "getdefaultrf", description = "Gets default keyspace replication factor.") @Command(name = "getdefaultrf", description = "Gets default keyspace replication factor.")
public class GetDefaultKeyspaceRF extends NodeTool.NodeToolCmd public class GetDefaultKeyspaceRF extends AbstractCommand
{ {
protected void execute(NodeProbe probe) protected void execute(NodeProbe probe)
{ {

View File

@ -17,26 +17,38 @@
*/ */
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import static com.google.common.base.Preconditions.checkArgument;
import io.airlift.airline.Arguments;
import io.airlift.airline.Command;
import java.net.InetAddress; import java.net.InetAddress;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import org.apache.cassandra.tools.nodetool.layout.CassandraUsage;
import picocli.CommandLine.Parameters;
import picocli.CommandLine.Command;
import static com.google.common.base.Preconditions.checkArgument;
import static org.apache.cassandra.tools.nodetool.CommandUtils.concatArgs;
@Command(name = "getendpoints", description = "Print the end points that owns the key") @Command(name = "getendpoints", description = "Print the end points that owns the key")
public class GetEndpoints extends NodeToolCmd public class GetEndpoints extends WithPortDisplayAbstractCommand
{ {
@Arguments(usage = "<keyspace> <table> <key>", description = "The keyspace, the table, and the partition key for which we need to find the endpoint") @CassandraUsage(usage = "<keyspace> <table> <key>", description = "The keyspace, the table, and the partition key for which we need to find the endpoint")
private List<String> args = new ArrayList<>(); private List<String> args = new ArrayList<>();
@Parameters(index = "0", arity = "0..1", description = "The keyspace for which we need to find the endpoint")
private String keyspace;
@Parameters(index = "1", arity = "0..1", description = "The table for which we need to find the endpoint")
private String table;
@Parameters(index = "2", arity = "0..1", description = "The partition key for which we need to find the endpoint")
private String key;
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)
{ {
args = concatArgs(keyspace, table, key);
checkArgument(args.size() == 3, "getendpoints requires keyspace, table and partition key arguments"); checkArgument(args.size() == 3, "getendpoints requires keyspace, table and partition key arguments");
String ks = args.get(0); String ks = args.get(0);
String table = args.get(1); String table = args.get(1);

View File

@ -18,14 +18,13 @@
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Command;
import org.apache.cassandra.fql.FullQueryLoggerOptions; import org.apache.cassandra.fql.FullQueryLoggerOptions;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
import org.apache.cassandra.tools.nodetool.formatter.TableBuilder; import org.apache.cassandra.tools.nodetool.formatter.TableBuilder;
import picocli.CommandLine.Command;
@Command(name = "getfullquerylog", description = "Print configuration of fql if enabled, otherwise the configuration reflected in cassandra.yaml") @Command(name = "getfullquerylog", description = "Print configuration of fql if enabled, otherwise the configuration reflected in cassandra.yaml")
public class GetFullQueryLog extends NodeToolCmd public class GetFullQueryLog extends AbstractCommand
{ {
protected void execute(NodeProbe probe) protected void execute(NodeProbe probe)
{ {

View File

@ -19,25 +19,21 @@ package org.apache.cassandra.tools.nodetool;
import com.google.common.math.DoubleMath; import com.google.common.math.DoubleMath;
import io.airlift.airline.Command;
import io.airlift.airline.Option;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
@Command(name = "getinterdcstreamthroughput", description = "Print the throughput cap for inter-datacenter streaming and entire SSTable inter-datacenter streaming in the system" + @Command(name = "getinterdcstreamthroughput", description = "Print the throughput cap for inter-datacenter streaming and entire SSTable inter-datacenter streaming in the system" +
"in rounded megabits. For precise number, please, use option -d") "in rounded megabits. For precise number, please, use option -d")
public class GetInterDCStreamThroughput extends NodeToolCmd public class GetInterDCStreamThroughput extends AbstractCommand
{ {
@SuppressWarnings("UnusedDeclaration") @Option(names = { "-e", "--entire-sstable-throughput" }, description = "Print entire SSTable streaming throughput in MiB/s")
@Option(name = { "-e", "--entire-sstable-throughput" }, description = "Print entire SSTable streaming throughput in MiB/s")
private boolean entireSSTableThroughput; private boolean entireSSTableThroughput;
@SuppressWarnings("UnusedDeclaration") @Option(names = { "-m", "--mib" }, description = "Print the throughput cap for inter-datacenter streaming in MiB/s")
@Option(name = { "-m", "--mib" }, description = "Print the throughput cap for inter-datacenter streaming in MiB/s")
private boolean interDCStreamThroughputMiB; private boolean interDCStreamThroughputMiB;
@SuppressWarnings("UnusedDeclaration") @Option(names = { "-d", "--precise-mbit" }, description = "Print the throughput cap for inter-datacenter streaming in precise Mbits (double)")
@Option(name = { "-d", "--precise-mbit" }, description = "Print the throughput cap for inter-datacenter streaming in precise Mbits (double)")
private boolean interDCStreamThroughputDoubleMbit; private boolean interDCStreamThroughputDoubleMbit;
@Override @Override

View File

@ -17,15 +17,13 @@
*/ */
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Command;
import java.util.Map; import java.util.Map;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import picocli.CommandLine.Command;
@Command(name = "getlogginglevels", description = "Get the runtime logging levels") @Command(name = "getlogginglevels", description = "Get the runtime logging levels")
public class GetLoggingLevels extends NodeToolCmd public class GetLoggingLevels extends AbstractCommand
{ {
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)

View File

@ -18,12 +18,11 @@
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Command;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool; import picocli.CommandLine.Command;
@Command(name = "getmaxhintwindow", description = "Print the max hint window in ms") @Command(name = "getmaxhintwindow", description = "Print the max hint window in ms")
public class GetMaxHintWindow extends NodeTool.NodeToolCmd public class GetMaxHintWindow extends AbstractCommand
{ {
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)

View File

@ -17,36 +17,48 @@
*/ */
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import static com.google.common.base.Preconditions.checkArgument;
import io.airlift.airline.Arguments;
import io.airlift.airline.Command;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import io.airlift.airline.Option;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import org.apache.cassandra.tools.nodetool.layout.CassandraUsage;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import static com.google.common.base.Preconditions.checkArgument;
import static org.apache.cassandra.tools.nodetool.CommandUtils.concatArgs;
@Command(name = "getsstables", description = "Print the sstable filenames that own the key") @Command(name = "getsstables", description = "Print the sstable filenames that own the key")
public class GetSSTables extends NodeToolCmd public class GetSSTables extends AbstractCommand
{ {
@Option(title = "hex_format", @Option(paramLabel = "hex_format",
name = {"-hf", "--hex-format"}, names = { "-hf", "--hex-format" },
description = "Specify the key in hexadecimal string format") description = "Specify the key in hexadecimal string format")
private boolean hexFormat = false; private boolean hexFormat = false;
@Option(name={"-l", "--show-levels"}, description="If the table is using leveled compaction the level of each sstable will be included in the output (Default: false)") @Option(names = { "-l", "--show-levels" }, description = "If the table is using leveled compaction the level of each sstable will be included in the output (Default: false)")
private boolean showLevels = false; private boolean showLevels = false;
@Arguments(usage = "<keyspace> <cfname> <key>", description = "The keyspace, the column family, and the key") @CassandraUsage(usage = "<keyspace> <cfname> <key>", description = "The keyspace, the column family, and the key")
private List<String> args = new ArrayList<>(); private List<String> args = new ArrayList<>();
@Parameters(paramLabel = "keyspace", index = "0", arity = "0..1", description = "The keyspace")
private String keyspace;
@Parameters(paramLabel = "cfname", index = "1", arity = "0..1", description = "The column family")
private String cfname;
@Parameters(paramLabel = "key", index = "2", arity = "0..1", description = "The key")
private String key;
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)
{ {
args = concatArgs(keyspace, cfname, key);
checkArgument(args.size() == 3, "getsstables requires ks, cf and key args"); checkArgument(args.size() == 3, "getsstables requires ks, cf and key args");
String ks = args.get(0); String ks = args.get(0);
String cf = args.get(1); String cf = args.get(1);

View File

@ -19,13 +19,11 @@ package org.apache.cassandra.tools.nodetool;
import java.util.List; import java.util.List;
import io.airlift.airline.Command;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import picocli.CommandLine.Command;
@Command(name = "getseeds", description = "Get the currently in use seed node IP list excluding the node IP") @Command(name = "getseeds", description = "Get the currently in use seed node IP list excluding the node IP")
public class GetSeeds extends NodeToolCmd public class GetSeeds extends AbstractCommand
{ {
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)

View File

@ -17,20 +17,19 @@
*/ */
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Command;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import picocli.CommandLine.Command;
@Command(name = "getsnapshotthrottle", description = "Print the snapshot_links_per_second throttle for snapshot/clearsnapshot") @Command(name = "getsnapshotthrottle", description = "Print the snapshot_links_per_second throttle for snapshot/clearsnapshot")
public class GetSnapshotThrottle extends NodeToolCmd public class GetSnapshotThrottle extends AbstractCommand
{ {
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)
{ {
long throttle = probe.getSnapshotLinksPerSecond(); long throttle = probe.getSnapshotLinksPerSecond();
if (throttle > 0) if (throttle > 0)
System.out.println("Current snapshot throttle: " + throttle + " links/s"); output.printInfo("Current snapshot throttle: %s links/s", throttle);
else else
System.out.println("Snapshot throttle is disabled"); output.printInfo("Snapshot throttle is disabled");
} }
} }

View File

@ -19,25 +19,21 @@ package org.apache.cassandra.tools.nodetool;
import com.google.common.math.DoubleMath; import com.google.common.math.DoubleMath;
import io.airlift.airline.Command;
import io.airlift.airline.Option;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
@Command(name = "getstreamthroughput", description = "Print the throughput cap for streaming and entire SSTable streaming in the system in rounded megabits. " + @Command(name = "getstreamthroughput", description = "Print the throughput cap for streaming and entire SSTable streaming in the system in rounded megabits. " +
"For precise number, please, use option -d") "For precise number, please, use option -d")
public class GetStreamThroughput extends NodeToolCmd public class GetStreamThroughput extends AbstractCommand
{ {
@SuppressWarnings("UnusedDeclaration") @Option(names = { "-e", "--entire-sstable-throughput" }, description = "Print entire SSTable streaming throughput in MiB/s")
@Option(name = { "-e", "--entire-sstable-throughput" }, description = "Print entire SSTable streaming throughput in MiB/s")
private boolean entireSSTableThroughput; private boolean entireSSTableThroughput;
@SuppressWarnings("UnusedDeclaration") @Option(names = { "-m", "--mib" }, description = "Print the throughput cap for streaming in MiB/s")
@Option(name = { "-m", "--mib" }, description = "Print the throughput cap for streaming in MiB/s")
private boolean streamThroughputMiB; private boolean streamThroughputMiB;
@SuppressWarnings("UnusedDeclaration") @Option(names = { "-d", "--precise-mbit" }, description = "Print the throughput cap for streaming in precise Mbits (double)")
@Option(name = { "-d", "--precise-mbit" }, description = "Print the throughput cap for streaming in precise Mbits (double)")
private boolean streamThroughputDoubleMbit; private boolean streamThroughputDoubleMbit;
@Override @Override

View File

@ -17,32 +17,32 @@
*/ */
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Arguments;
import io.airlift.airline.Command;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import org.apache.cassandra.tools.nodetool.layout.CassandraUsage;
import static com.google.common.base.Preconditions.checkArgument; import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;
@Command(name = "gettimeout", description = "Print the timeout of the given type in ms") @Command(name = "gettimeout", description = "Print the timeout of the given type in ms")
public class GetTimeout extends NodeToolCmd public class GetTimeout extends AbstractCommand
{ {
public static final String TIMEOUT_TYPES = "read, range, write, counterwrite, cascontention, truncate, internodeconnect, internodeuser, internodestreaminguser, misc (general rpc_timeout_in_ms)"; public static final String TIMEOUT_TYPES = "read, range, write, counterwrite, cascontention, truncate, internodeconnect, internodeuser, internodestreaminguser, misc (general rpc_timeout_in_ms)";
@Arguments(usage = "<timeout_type>", description = "The timeout type, one of (" + TIMEOUT_TYPES + ")") @CassandraUsage(usage = "<timeout_type>", description = "The timeout type, one of (" + TIMEOUT_TYPES + ")")
private List<String> args = new ArrayList<>(); private List<String> args = new ArrayList<>();
@Parameters (index = "0", description = "The timeout type, one of (" + TIMEOUT_TYPES + ')', arity = "1")
private String timeout_type;
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)
{ {
checkArgument(args.size() == 1, "gettimeout requires a timeout type, one of (" + TIMEOUT_TYPES + ")");
try try
{ {
probe.output().out.println("Current timeout for type " + args.get(0) + ": " + probe.getTimeout(args.get(0)) + " ms"); probe.output().out.println("Current timeout for type " + timeout_type + ": " + probe.getTimeout(timeout_type) + " ms");
} catch (Exception e) } catch (Exception e)
{ {
throw new IllegalArgumentException(e.getMessage()); throw new IllegalArgumentException(e.getMessage());

View File

@ -17,13 +17,11 @@
*/ */
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Command;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import picocli.CommandLine.Command;
@Command(name = "gettraceprobability", description = "Print the current trace probability value") @Command(name = "gettraceprobability", description = "Print the current trace probability value")
public class GetTraceProbability extends NodeToolCmd public class GetTraceProbability extends AbstractCommand
{ {
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)

View File

@ -17,16 +17,14 @@
*/ */
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Command;
import io.airlift.airline.Option;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
@Command(name = "gossipinfo", description = "Shows the gossip information for the cluster") @Command(name = "gossipinfo", description = "Shows the gossip information for the cluster")
public class GossipInfo extends NodeToolCmd public class GossipInfo extends WithPortDisplayAbstractCommand
{ {
@Option(title = "resolve_ip", name = {"-r", "--resolve-ip"}, description = "Show node domain names instead of IPs") @Option(paramLabel = "resolve_ip", names = { "-r", "--resolve-ip" }, description = "Show node domain names instead of IPs")
private boolean resolveIp = false; private boolean resolveIp = false;
@Override @Override

View File

@ -18,7 +18,6 @@
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import java.io.PrintStream;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -37,48 +36,41 @@ import java.util.stream.Stream;
import com.google.common.annotations.VisibleForTesting; 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.db.guardrails.GuardrailsMBean;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool;
import org.apache.cassandra.tools.nodetool.formatter.TableBuilder; import org.apache.cassandra.tools.nodetool.formatter.TableBuilder;
import org.apache.cassandra.tools.nodetool.layout.CassandraUsage;
import org.apache.cassandra.utils.LocalizeString; import org.apache.cassandra.utils.LocalizeString;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import static java.lang.String.format; import static java.lang.String.format;
import static java.util.Arrays.stream; import static java.util.Arrays.stream;
import static java.util.Comparator.comparing; import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toList;
public abstract class GuardrailsConfigCommand extends NodeTool.NodeToolCmd public abstract class GuardrailsConfigCommand extends AbstractCommand
{ {
@Command(name = "getguardrailsconfig", description = "Print runtime configuration of guardrails.") @Command(name = "getguardrailsconfig", description = "Print runtime configuration of guardrails.")
public static class GetGuardrailsConfig extends GuardrailsConfigCommand public static class GetGuardrailsConfig extends GuardrailsConfigCommand
{ {
@Option(name = { "--category", "-c" }, @Option(names = { "--category", "-c" },
description = "Category of guardrails to filter, can be one of 'values', 'thresholds', 'flags', 'others'.", description = "Category of guardrails to filter, can be one of 'values', 'thresholds', 'flags', 'others'.")
allowedValues = { "values", "thresholds", "flags", "others" }) private GuardrailCategory guardrailCategory;
private String guardrailCategory;
@Option(name = { "--expand" }, @Option(names = { "--expand" },
description = "Expand all guardrail names so they reflect their counterparts in cassandra.yaml") description = "Expand all guardrail names so they reflect their counterparts in cassandra.yaml")
private boolean expand = false; private boolean expand = false;
@Arguments(description = "Specific name of a guardrail to get configuration of.") @Parameters(index = "0", arity = "0..1", description = "Specific name of a guardrail to get configuration of or all guardrails if not specified.")
private List<String> args = new ArrayList<>(); private String guardrailName;
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)
{ {
GuardrailCategory categoryEnum = GuardrailCategory.parseCategory(guardrailCategory, probe.output().out); if (guardrailName != null && guardrailCategory != null)
if (args.size() > 1)
throw new IllegalStateException("Specify only one guardrail name to get the configuration of or no name to get the configuration of all of them.");
String guardrailName = !args.isEmpty() ? args.get(0) : null;
if (guardrailName != null && categoryEnum != null)
throw new IllegalStateException("Do not specify additional arguments when --category/-c is set."); throw new IllegalStateException("Do not specify additional arguments when --category/-c is set.");
Map<String, List<Method>> allGetters = parseGuardrailNames(probe.getGuardrailsMBean().getClass().getDeclaredMethods(), guardrailName); Map<String, List<Method>> allGetters = parseGuardrailNames(probe.getGuardrailsMBean().getClass().getDeclaredMethods(), guardrailName);
@ -89,7 +81,7 @@ public abstract class GuardrailsConfigCommand extends NodeTool.NodeToolCmd
throw new IllegalStateException(format("Guardrail %s not found.", guardrailName)); throw new IllegalStateException(format("Guardrail %s not found.", guardrailName));
} }
display(probe, allGetters, categoryEnum, expand); display(probe, allGetters, guardrailCategory, expand);
} }
@VisibleForTesting @VisibleForTesting
@ -179,17 +171,28 @@ public abstract class GuardrailsConfigCommand extends NodeTool.NodeToolCmd
{ {
private static final Pattern SETTER_PATTERN = Pattern.compile("^set"); private static final Pattern SETTER_PATTERN = Pattern.compile("^set");
@Arguments(usage = "[<setter> <value1> ...]", @CassandraUsage(usage = "[<setter> <value1> ...]",
description = "For flags, possible values are 'true' or 'false'. " + description = "For flags, possible values are 'true' or 'false'. " +
"For thresholds, two values are expected, first for failure, second for warning. " + "For thresholds, two values are expected, first for failure, second for warning. " +
"For values, enumeration of values expected or one value where multiple items are separated by comma. " + "For values, enumeration of values expected or one value where multiple items are separated by comma. " +
"Setting for thresholds accepting strings and value guardrails are reset by specifying 'null' or '[]' value. " + "Setting for thresholds accepting strings and value guardrails are reset by specifying 'null' or '[]' value. " +
"For thresholds accepting integers, the reset value is -1.") "For thresholds accepting integers, the reset value is -1.")
private final List<String> args = new ArrayList<>(); private List<String> args = new ArrayList<>();
@Parameters(index = "0", arity = "0..1")
private String setterName;
@Parameters(index = "1..*", arity = "0..*", description = "Arguments for the setter. For flags, possible values are 'true' or 'false'. " +
"For thresholds, two values are expected, first for failure, second for warning. " +
"For values, enumeration of values expected or one value where multiple items are separated by comma. " +
"Setting for thresholds accepting strings and value guardrails are reset by specifying 'null' or '[]' value. " +
"For thresholds accepting integers, the reset value is -1.")
private List<String> setterArgs = new ArrayList<>();
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)
{ {
args = CommandUtils.concatArgs(setterName, setterArgs);
if (args.isEmpty()) if (args.isEmpty())
throw new IllegalStateException("No arguments."); throw new IllegalStateException("No arguments.");
@ -376,27 +379,6 @@ public abstract class GuardrailsConfigCommand extends NodeTool.NodeToolCmd
thresholds, thresholds,
flags, flags,
others; others;
public static GuardrailCategory parseCategory(String category, PrintStream out)
{
if (category == null)
return null;
try
{
return GuardrailCategory.valueOf(LocalizeString.toLowerCaseLocalized(category));
}
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;
}
}
} }
void display(NodeProbe probe, Map<String, List<Method>> methods, GuardrailCategory userCategory, boolean verbose) void display(NodeProbe probe, Map<String, List<Method>> methods, GuardrailCategory userCategory, boolean verbose)

View File

@ -0,0 +1,162 @@
/*
* 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.PrintWriter;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import org.apache.cassandra.tools.nodetool.layout.CassandraCliHelpLayout;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.IHelpCommandInitializable2;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_COMMAND_LIST;
import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_COMMAND_LIST_HEADING;
import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_EXIT_CODE_LIST;
import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_EXIT_CODE_LIST_HEADING;
import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_FOOTER;
import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_FOOTER_HEADING;
import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_HEADER;
import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_HEADER_HEADING;
import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_SYNOPSIS;
@Command(name = "help",
helpCommand = true,
description = "Display help information")
public class Help implements IHelpCommandInitializable2, Runnable
{
@Option(names = { "--help" }, hidden = true, usageHelp = true, descriptionKey = "helpCommand.help",
description = "Show usage help for the help command and exit.")
private boolean helpRequested;
@Parameters(paramLabel = "command", arity = "1..*", descriptionKey = "helpCommand.command",
description = "The COMMAND to display the usage help message for.")
private List<String> commands;
private CommandLine self;
private PrintWriter out;
private CommandLine.Help.ColorScheme colorScheme;
/**
* Invokes {@code #usage(PrintStream, CommandLine.Help.ColorScheme) usage} for the specified command,
* or for the parent command.
*/
@Override
public void run()
{
CommandLine parent = self == null ? null : self.getParent();
if (parent == null)
return;
CommandLine.Help.ColorScheme colors = colorScheme == null ?
CommandLine.Help.defaultColorScheme(CommandLine.Help.Ansi.AUTO) :
colorScheme;
if (commands == null)
{
// If the parent command is the top-level command, print help for the top-level command.
printTopCommandUsage(parent, colors, out);
return;
}
if (parent.isAbbreviatedSubcommandsAllowed())
throw new CommandLine.ParameterException(parent, "Abbreviated subcommands are not allowed.");
// Print help for the last command in the list of commands.
CommandLine subcommand = parent;
for (String command : commands)
{
subcommand = subcommand.getSubcommands().get(command);
if (subcommand == null)
throw new CommandLine.ParameterException(parent, "Unknown subcommand '" + command + "'.", null, command);
}
subcommand.usage(out, colors);
}
public static void printTopCommandUsage(CommandLine command, CommandLine.Help.ColorScheme colors, PrintWriter writer)
{
if (command == null)
return;
StringBuilder sb = new StringBuilder();
CommandLine.Help help = command.getHelpFactory().create(command.getCommandSpec(), colors);
if (!(help instanceof CassandraCliHelpLayout))
{
command.usage(writer, colors);
return;
}
Map<String, CommandLine.IHelpSectionRenderer> helpSectionMap = cassandraTopLevelHelpSectionKeys((CassandraCliHelpLayout) help);
for (String key : command.getHelpSectionKeys())
{
CommandLine.IHelpSectionRenderer renderer = helpSectionMap.get(key);
if (renderer == null)
continue;
String rendered = renderer.render(help);
if (!Strings.isNullOrEmpty(rendered))
sb.append(rendered);
}
writer.println(sb);
writer.flush();
}
/**
* Top-level help command (includes all the available nodetool commands) has a different layout, so we need to
* provide a different set of keys for the help sections.
* @param layout The help class layout.
* @return Map of supported keys for the help sections.
*/
public static Map<String, CommandLine.IHelpSectionRenderer> cassandraTopLevelHelpSectionKeys(CassandraCliHelpLayout layout)
{
Map<String, CommandLine.IHelpSectionRenderer> sectionMap = new LinkedHashMap<>();
sectionMap.put(SECTION_KEY_HEADER_HEADING, CommandLine.Help::headerHeading);
sectionMap.put(SECTION_KEY_HEADER, CommandLine.Help::header);
sectionMap.put(SECTION_KEY_SYNOPSIS, layout::topLevelSynopsis);
sectionMap.put(SECTION_KEY_COMMAND_LIST_HEADING, layout::topLevelCommandListHeading);
sectionMap.put(SECTION_KEY_COMMAND_LIST, layout::topLevelCommandList);
sectionMap.put(SECTION_KEY_EXIT_CODE_LIST_HEADING, CommandLine.Help::exitCodeListHeading);
sectionMap.put(SECTION_KEY_EXIT_CODE_LIST, CommandLine.Help::exitCodeList);
sectionMap.put(SECTION_KEY_FOOTER_HEADING, CommandLine.Help::footerHeading);
sectionMap.put(SECTION_KEY_FOOTER, CommandLine.Help::footer);
return sectionMap;
}
/**
* The printHelpIfRequested method calls the init method on commands marked
* as helpCommand before the help command's run or call method is called.
*/
public void init(CommandLine helpCommandLine,
CommandLine.Help.ColorScheme colorScheme,
PrintWriter out,
PrintWriter err)
{
this.self = Preconditions.checkNotNull(helpCommandLine, "helpCommandLine");
this.colorScheme = Preconditions.checkNotNull(colorScheme, "colorScheme");
this.out = Preconditions.checkNotNull(out, "outWriter");
}
}

View File

@ -18,13 +18,6 @@
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import static com.google.common.base.Preconditions.checkArgument;
import io.airlift.airline.Arguments;
import io.airlift.airline.Command;
import io.airlift.airline.Option;
import java.io.PrintStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -32,67 +25,87 @@ import java.util.List;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import org.apache.cassandra.tools.nodetool.layout.CassandraUsage;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import static com.google.common.base.Preconditions.checkArgument;
import static org.apache.cassandra.tools.nodetool.CommandUtils.concatArgs;
@Command(name = "import", description = "Import new SSTables to the system") @Command(name = "import", description = "Import new SSTables to the system")
public class Import extends NodeToolCmd public class Import extends AbstractCommand
{ {
@Arguments(usage = "<keyspace> <table> <directory> ...", description = "The keyspace, table name and directories to import sstables from") public static final String IMPORT_FAIL_MESSAGE = "Some directories failed to import, check server logs for details";
@CassandraUsage(usage = "<keyspace> <table> <directory> ...", description = "The keyspace, table name and directories to import sstables from")
private List<String> args = new ArrayList<>(); private List<String> args = new ArrayList<>();
@Option(title = "keep_level", @Parameters(index = "0", paramLabel = "keyspace", description = "The keyspace name")
name = {"-l", "--keep-level"}, private String keyspace;
@Parameters(index = "1", paramLabel = "table", description = "The table name")
private String table;
@Parameters(index = "2..*", paramLabel = "directories", description = "The directories to import sstables from")
private String[] directories;
@Option(paramLabel = "keep_level",
names = { "-l", "--keep-level" },
description = "Keep the level on the new sstables") description = "Keep the level on the new sstables")
private boolean keepLevel = false; private boolean keepLevel = false;
@Option(title = "keep_repaired", @Option(paramLabel = "keep_repaired",
name = {"-r", "--keep-repaired"}, names = { "-r", "--keep-repaired" },
description = "Keep any repaired information from the sstables") description = "Keep any repaired information from the sstables")
private boolean keepRepaired = false; private boolean keepRepaired = false;
@Option(title = "no_verify_sstables", @Option(paramLabel = "no_verify_sstables",
name = {"-v", "--no-verify"}, names = { "-v", "--no-verify" },
description = "Don't verify new sstables") description = "Don't verify new sstables")
private boolean noVerify = false; private boolean noVerify = false;
@Option(title = "no_verify_tokens", @Option(paramLabel = "no_verify_tokens",
name = {"-t", "--no-tokens"}, names = { "-t", "--no-tokens" },
description = "Don't verify that all tokens in the new sstable are owned by the current node") description = "Don't verify that all tokens in the new sstable are owned by the current node")
private boolean noVerifyTokens = false; private boolean noVerifyTokens = false;
@Option(title = "no_invalidate_caches", @Option(paramLabel = "no_invalidate_caches",
name = {"-c", "--no-invalidate-caches"}, names = { "-c", "--no-invalidate-caches" },
description = "Don't invalidate the row cache when importing") description = "Don't invalidate the row cache when importing")
private boolean noInvalidateCaches = false; private boolean noInvalidateCaches = false;
@Option(title = "quick", @Option(paramLabel = "quick",
name = {"-q", "--quick"}, names = { "-q", "--quick" },
description = "Do a quick import without verifying sstables, clearing row cache or checking in which data directory to put the file") description = "Do a quick import without verifying sstables, clearing row cache or checking in which data directory to put the file")
private boolean quick = false; private boolean quick = false;
@Option(title = "extended_verify", @Option(paramLabel = "extended_verify",
name = {"-e", "--extended-verify"}, names = { "-e", "--extended-verify" },
description = "Run an extended verify, verifying all values in the new sstables") description = "Run an extended verify, verifying all values in the new sstables")
private boolean extendedVerify = false; private boolean extendedVerify = false;
@Option(title = "copy_data", // The previous option -p collides with the --port in the JMX, so we need to alter it to -cd.
name = {"-p", "--copy-data"}, // It is safe to alter the name since the option is not used by users as it doesn't work.
@Option(paramLabel = "copy_data",
names = { "-cd", "--copy-data" },
description = "Copy data from source directories instead of moving them") description = "Copy data from source directories instead of moving them")
private boolean copyData = false; private boolean copyData = false;
@Option(title = "require_index_components", @Option(paramLabel = "require_index_components",
name = {"-ri", "--require-index-components"}, names = { "-ri", "--require-index-components" },
description = "Require existing index components for SSTables with attached indexes") description = "Require existing index components for SSTables with attached indexes")
private boolean failOnMissingIndex = false; private boolean failOnMissingIndex = false;
@Option(title = "no_index_validation", @Option(paramLabel = "no_index_validation",
name = {"-niv", "--no-index-validation"}, names = { "-niv", "--no-index-validation" },
description = "Skip SSTable-attached index checksum validation") description = "Skip SSTable-attached index checksum validation")
private boolean noIndexValidation = false; private boolean noIndexValidation = false;
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)
{ {
args = concatArgs(keyspace, table, directories);
checkArgument(args.size() >= 3, "import requires keyspace, table name and directories"); checkArgument(args.size() >= 3, "import requires keyspace, table name and directories");
if (quick) if (quick)
@ -110,11 +123,9 @@ public class Import extends NodeToolCmd
extendedVerify, copyData, failOnMissingIndex, !noIndexValidation); extendedVerify, copyData, failOnMissingIndex, !noIndexValidation);
if (!failedDirs.isEmpty()) if (!failedDirs.isEmpty())
{ {
PrintStream err = probe.output().err;
err.println("Some directories failed to import, check server logs for details:");
for (String directory : failedDirs) for (String directory : failedDirs)
err.println(directory); output.printError(directory);
System.exit(1); throw new RuntimeException(IMPORT_FAIL_MESSAGE);
} }
} }
} }

View File

@ -17,28 +17,25 @@
*/ */
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Command;
import io.airlift.airline.Option;
import java.io.PrintStream; import java.io.PrintStream;
import java.lang.management.MemoryUsage; import java.lang.management.MemoryUsage;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import javax.management.InstanceNotFoundException; import javax.management.InstanceNotFoundException;
import org.apache.cassandra.db.ColumnFamilyStoreMBean; import org.apache.cassandra.db.ColumnFamilyStoreMBean;
import org.apache.cassandra.io.util.FileUtils; import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.service.CacheServiceMBean; import org.apache.cassandra.service.CacheServiceMBean;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
@Command(name = "info", description = "Print node information (uptime, load, ...)") @Command(name = "info", description = "Print node information (uptime, load, ...)")
public class Info extends NodeToolCmd public class Info extends AbstractCommand
{ {
@Option(name = {"-T", "--tokens"}, description = "Display all tokens") @Option(names = { "-T", "--tokens" }, description = "Display all tokens")
private boolean tokens = false; private boolean tokens = false;
@Override @Override

View File

@ -20,18 +20,19 @@ package org.apache.cassandra.tools.nodetool;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import io.airlift.airline.Arguments;
import io.airlift.airline.Command;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import org.apache.cassandra.tools.nodetool.layout.CassandraUsage;
import picocli.CommandLine.Parameters;
import picocli.CommandLine.Command;
/** /**
* Nodetool command to invalidate CIDR permissions cache, for a give role or for all roles in the cache. * Nodetool command to invalidate CIDR permissions cache, for a give role or for all roles in the cache.
*/ */
@Command(name = "invalidatecidrpermissionscache", description = "Invalidate the cidr permissions cache") @Command(name = "invalidatecidrpermissionscache", description = "Invalidate the cidr permissions cache")
public class InvalidateCIDRPermissionsCache extends NodeToolCmd public class InvalidateCIDRPermissionsCache extends AbstractCommand
{ {
@Arguments(usage = "[<role>...]", description = "List of roles to invalidate. By default, all roles") @CassandraUsage(usage = "[<role>...]", description = "List of roles to invalidate. By default, all roles")
@Parameters(paramLabel = "roles", description = "List of roles to invalidate. By default, all roles", index = "0..*")
private List<String> args = new ArrayList<>(); private List<String> args = new ArrayList<>();
@Override @Override

View File

@ -17,13 +17,12 @@
*/ */
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Command;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import picocli.CommandLine.Command;
@Command(name = "invalidatecountercache", description = "Invalidate the counter cache") @Command(name = "invalidatecountercache", description = "Invalidate the counter cache")
public class InvalidateCounterCache extends NodeToolCmd public class InvalidateCounterCache extends AbstractCommand
{ {
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)

View File

@ -20,15 +20,16 @@ package org.apache.cassandra.tools.nodetool;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import io.airlift.airline.Arguments;
import io.airlift.airline.Command;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import org.apache.cassandra.tools.nodetool.layout.CassandraUsage;
import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;
@Command(name = "invalidatecredentialscache", description = "Invalidate the credentials cache") @Command(name = "invalidatecredentialscache", description = "Invalidate the credentials cache")
public class InvalidateCredentialsCache extends NodeToolCmd public class InvalidateCredentialsCache extends AbstractCommand
{ {
@Arguments(usage = "[<role>...]", description = "List of roles to invalidate. By default, all roles") @CassandraUsage(usage = "[<role>...]", description = "List of roles to invalidate. By default, all roles")
@Parameters(paramLabel = "roles", description = "List of roles to invalidate. By default, all roles", index = "0..*")
private List<String> args = new ArrayList<>(); private List<String> args = new ArrayList<>();
@Override @Override

View File

@ -20,15 +20,16 @@ package org.apache.cassandra.tools.nodetool;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import io.airlift.airline.Arguments;
import io.airlift.airline.Command;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import org.apache.cassandra.tools.nodetool.layout.CassandraUsage;
import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;
@Command(name = "invalidatejmxpermissionscache", description = "Invalidate the JMX permissions cache") @Command(name = "invalidatejmxpermissionscache", description = "Invalidate the JMX permissions cache")
public class InvalidateJmxPermissionsCache extends NodeToolCmd public class InvalidateJmxPermissionsCache extends AbstractCommand
{ {
@Arguments(usage = "[<role>...]", description = "List of roles to invalidate. By default, all roles") @CassandraUsage(usage = "[<role>...]", description = "List of roles to invalidate. By default, all roles")
@Parameters(paramLabel = "roles", description = "List of roles to invalidate. By default, all roles", index = "0..*")
private List<String> args = new ArrayList<>(); private List<String> args = new ArrayList<>();
@Override @Override

View File

@ -17,13 +17,11 @@
*/ */
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Command;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import picocli.CommandLine.Command;
@Command(name = "invalidatekeycache", description = "Invalidate the key cache") @Command(name = "invalidatekeycache", description = "Invalidate the key cache")
public class InvalidateKeyCache extends NodeToolCmd public class InvalidateKeyCache extends AbstractCommand
{ {
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)

View File

@ -20,15 +20,16 @@ package org.apache.cassandra.tools.nodetool;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import io.airlift.airline.Arguments;
import io.airlift.airline.Command;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import org.apache.cassandra.tools.nodetool.layout.CassandraUsage;
import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;
@Command(name = "invalidatenetworkpermissionscache", description = "Invalidate the network permissions cache") @Command(name = "invalidatenetworkpermissionscache", description = "Invalidate the network permissions cache")
public class InvalidateNetworkPermissionsCache extends NodeToolCmd public class InvalidateNetworkPermissionsCache extends AbstractCommand
{ {
@Arguments(usage = "[<role>...]", description = "List of roles to invalidate. By default, all roles") @CassandraUsage(usage = "[<role>...]", description = "List of roles to invalidate. By default, all roles")
@Parameters(paramLabel = "roles", description = "List of roles to invalidate. By default, all roles", index = "0..*")
private List<String> args = new ArrayList<>(); private List<String> args = new ArrayList<>();
@Override @Override

View File

@ -22,89 +22,88 @@ import java.util.List;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import io.airlift.airline.Arguments;
import io.airlift.airline.Command;
import io.airlift.airline.Option;
import org.apache.cassandra.auth.DataResource; import org.apache.cassandra.auth.DataResource;
import org.apache.cassandra.auth.FunctionResource; import org.apache.cassandra.auth.FunctionResource;
import org.apache.cassandra.auth.JMXResource; import org.apache.cassandra.auth.JMXResource;
import org.apache.cassandra.auth.RoleResource; import org.apache.cassandra.auth.RoleResource;
import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import picocli.CommandLine.Parameters;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
@Command(name = "invalidatepermissionscache", description = "Invalidate the permissions cache") @Command(name = "invalidatepermissionscache", description = "Invalidate the permissions cache")
public class InvalidatePermissionsCache extends NodeToolCmd public class InvalidatePermissionsCache extends AbstractCommand
{ {
@Arguments(usage = "[<role>]", description = "A role for which permissions to specified resources need to be invalidated") @Parameters(paramLabel = "role", description = "A role for which permissions to specified resources need to be invalidated", arity = "0..1", index = "0")
private List<String> args = new ArrayList<>(); private String roleName;
// Data Resources // Data Resources
@Option(title = "all-keyspaces", @Option(paramLabel = "all-keyspaces",
name = {"--all-keyspaces"}, names = { "--all-keyspaces" },
description = "Invalidate permissions for 'ALL KEYSPACES'") description = "Invalidate permissions for 'ALL KEYSPACES'")
private boolean allKeyspaces; private boolean allKeyspaces;
@Option(title = "keyspace", @Option(paramLabel = "keyspace",
name = {"--keyspace"}, names = { "--keyspace" },
description = "Keyspace to invalidate permissions for") description = "Keyspace to invalidate permissions for")
private String keyspace; private String keyspace;
@Option(title = "all-tables", @Option(paramLabel = "all-tables",
name = {"--all-tables"}, names = { "--all-tables" },
description = "Invalidate permissions for 'ALL TABLES'") description = "Invalidate permissions for 'ALL TABLES'")
private boolean allTables; private boolean allTables;
@Option(title = "table", @Option(paramLabel = "table",
name = {"--table"}, names = { "--table" },
description = "Table to invalidate permissions for (you must specify --keyspace for using this option)") description = "Table to invalidate permissions for (you must specify --keyspace for using this option)")
private String table; private String table;
// Roles Resources // Roles Resources
@Option(title = "all-roles", @Option(paramLabel = "all-roles",
name = {"--all-roles"}, names = { "--all-roles" },
description = "Invalidate permissions for 'ALL ROLES'") description = "Invalidate permissions for 'ALL ROLES'")
private boolean allRoles; private boolean allRoles;
@Option(title = "role", @Option(paramLabel = "role",
name = {"--role"}, names = { "--role" },
description = "Role to invalidate permissions for") description = "Role to invalidate permissions for")
private String role; private String role;
// Functions Resources // Functions Resources
@Option(title = "all-functions", @Option(paramLabel = "all-functions",
name = {"--all-functions"}, names = { "--all-functions" },
description = "Invalidate permissions for 'ALL FUNCTIONS'") description = "Invalidate permissions for 'ALL FUNCTIONS'")
private boolean allFunctions; private boolean allFunctions;
@Option(title = "functions-in-keyspace", @Option(paramLabel = "functions-in-keyspace",
name = {"--functions-in-keyspace"}, names = { "--functions-in-keyspace" },
description = "Keyspace to invalidate permissions for") description = "Keyspace to invalidate permissions for")
private String functionsInKeyspace; private String functionsInKeyspace;
@Option(title = "function", @Option(paramLabel = "function",
name = {"--function"}, names = { "--function" },
description = "Function to invalidate permissions for (you must specify --functions-in-keyspace for using " + description = "Function to invalidate permissions for (you must specify --functions-in-keyspace for using " +
"this option; function format: name[arg1^..^agrN], for example: foo[Int32Type^DoubleType])") "this option; function format: name[arg1^..^agrN], for example: foo[Int32Type^DoubleType])")
private String function; private String function;
// MBeans Resources // MBeans Resources
@Option(title = "all-mbeans", @Option(paramLabel = "all-mbeans",
name = {"--all-mbeans"}, names = { "--all-mbeans" },
description = "Invalidate permissions for 'ALL MBEANS'") description = "Invalidate permissions for 'ALL MBEANS'")
private boolean allMBeans; private boolean allMBeans;
@Option(title = "mbean", @Option(paramLabel = "mbean",
name = {"--mbean"}, names = { "--mbean" },
description = "MBean to invalidate permissions for") description = "MBean to invalidate permissions for")
private String mBean; private String mBean;
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)
{ {
if (args.isEmpty()) if (StringUtils.isEmpty(roleName))
{ {
checkArgument(!allKeyspaces && StringUtils.isEmpty(keyspace) && StringUtils.isEmpty(table) checkArgument(!allKeyspaces && StringUtils.isEmpty(keyspace) && StringUtils.isEmpty(table)
&& !allRoles && StringUtils.isEmpty(role) && !allRoles && StringUtils.isEmpty(role)
@ -116,8 +115,6 @@ public class InvalidatePermissionsCache extends NodeToolCmd
} }
else else
{ {
checkArgument(args.size() == 1,
"A single <role> is only supported / you have a typo in the resource options spelling");
List<String> resourceNames = new ArrayList<>(); List<String> resourceNames = new ArrayList<>();
// Data Resources // Data Resources
@ -166,8 +163,6 @@ public class InvalidatePermissionsCache extends NodeToolCmd
if (StringUtils.isNotEmpty(mBean)) if (StringUtils.isNotEmpty(mBean))
resourceNames.add(JMXResource.mbean(mBean).getName()); resourceNames.add(JMXResource.mbean(mBean).getName());
String roleName = args.get(0);
if (resourceNames.isEmpty()) if (resourceNames.isEmpty())
throw new IllegalArgumentException("No resource options specified"); throw new IllegalArgumentException("No resource options specified");

View File

@ -20,16 +20,17 @@ package org.apache.cassandra.tools.nodetool;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import io.airlift.airline.Arguments;
import io.airlift.airline.Command;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import org.apache.cassandra.tools.nodetool.layout.CassandraUsage;
import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;
@Command(name = "invalidaterolescache", description = "Invalidate the roles cache") @Command(name = "invalidaterolescache", description = "Invalidate the roles cache")
public class InvalidateRolesCache extends NodeToolCmd public class InvalidateRolesCache extends AbstractCommand
{ {
@Arguments(usage = "[<role>...]", description = "List of roles to invalidate. By default, all roles") @CassandraUsage(usage = "[<role>...]", description = "List of roles to invalidate. By default, all roles")
@Parameters(paramLabel = "role", description = "List of roles to invalidate. By default, all roles")
private List<String> args = new ArrayList<>(); private List<String> args = new ArrayList<>();
@Override @Override

View File

@ -17,13 +17,11 @@
*/ */
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import io.airlift.airline.Command;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import picocli.CommandLine.Command;
@Command(name = "invalidaterowcache", description = "Invalidate the row cache") @Command(name = "invalidaterowcache", description = "Invalidate the row cache")
public class InvalidateRowCache extends NodeToolCmd public class InvalidateRowCache extends AbstractCommand
{ {
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)

View File

@ -0,0 +1,243 @@
/*
* 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.Console;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.List;
import java.util.Scanner;
import javax.inject.Inject;
import com.google.common.base.Throwables;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.tools.INodeProbeFactory;
import org.apache.cassandra.tools.NodeProbe;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.ExecutionException;
import picocli.CommandLine.IExecutionStrategy;
import picocli.CommandLine.InitializationException;
import picocli.CommandLine.Model.CommandSpec;
import picocli.CommandLine.Option;
import picocli.CommandLine.ParameterException;
import picocli.CommandLine.ParseResult;
import picocli.CommandLine.RunLast;
import picocli.CommandLine.Spec;
import static java.lang.Integer.parseInt;
import static org.apache.cassandra.tools.NodeProbe.defaultPort;
import static org.apache.commons.lang3.StringUtils.EMPTY;
import static org.apache.commons.lang3.StringUtils.isEmpty;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
/**
* Command options for NodeTool commands that are executed via JMX.
*/
@Command(name = "connect", description = "Connect to a Cassandra node via JMX")
public class JmxConnect extends AbstractCommand implements AutoCloseable
{
public static final String MIXIN_KEY = "jmx";
/** The command specification, used to access command-specific properties. */
@Spec
protected CommandSpec spec; // injected by picocli
@Option(names = { "-h", "--host" }, description = "Node hostname or ip address", arity = "0..1")
private String host = "127.0.0.1";
@Option(names = { "-p", "--port" }, description = "Remote jmx agent port number", arity = "0..1")
private String port = String.valueOf(defaultPort);
@Option(names = { "-u", "--username" }, description = "Remote jmx agent username", arity = "0..1")
private String username = EMPTY;
@Option(names = { "-pw", "--password" }, description = "Remote jmx agent password", arity = "0..1")
private String password = EMPTY;
@Option(names = { "-pwf", "--password-file" }, description = "Path to the JMX password file", arity = "0..1")
private String passwordFilePath = EMPTY;
@Inject
private INodeProbeFactory nodeProbeFactory;
/**
* This method is called by picocli and used depending on the execution strategy.
* @param parseResult The parsed command line.
* @return The exit code.
*/
public static int executionStrategy(ParseResult parseResult)
{
CommandSpec jmx = parseResult.commandSpec().mixins().get(MIXIN_KEY);
if (jmx == null)
throw new InitializationException("No JmxConnect command found in the top-level hierarchy");
try (JmxConnectionCommandInvoker invoker = new JmxConnectionCommandInvoker((JmxConnect) jmx.userObject()))
{
return invoker.execute(parseResult);
}
catch (JmxConnectionCommandInvoker.CloseException e)
{
jmx.commandLine()
.getErr()
.println("Failed to connect to JMX: " + e.getMessage());
return jmx.commandLine().getExitCodeExceptionMapper().getExitCode(e);
}
}
/**
* Initialize the JMX connection to the Cassandra node using the provided options.
*/
@Override
protected void execute(NodeProbe probe)
{
assert probe == null;
try
{
if (isNotEmpty(username))
{
if (isNotEmpty(passwordFilePath))
password = readUserPasswordFromFile(username, passwordFilePath);
if (isEmpty(password))
password = promptAndReadPassword();
}
probe(username.isEmpty() ? nodeProbeFactory.create(host, parseInt(port))
: nodeProbeFactory.create(host, parseInt(port), username, password));
}
catch (IOException | SecurityException e)
{
Throwable rootCause = Throwables.getRootCause(e);
output.printError("nodetool: Failed to connect to '%s:%s' - %s: '%s'.%n", host, port,
rootCause.getClass().getSimpleName(), rootCause.getMessage());
throw new InitializationException("Failed to connect to JMX", e);
}
}
@Override
public void close() throws Exception
{
if (probe() == null)
return;
((AutoCloseable) probe()).close();
}
private static String readUserPasswordFromFile(String username, String passwordFilePath)
{
String password = EMPTY;
File passwordFile = new File(passwordFilePath);
try (Scanner scanner = new Scanner(passwordFile.toJavaIOFile()).useDelimiter("\\s+"))
{
while (scanner.hasNextLine())
{
if (scanner.hasNext())
{
String jmxRole = scanner.next();
if (jmxRole.equals(username) && scanner.hasNext())
{
password = scanner.next();
break;
}
}
scanner.nextLine();
}
}
catch (FileNotFoundException e)
{
throw new UncheckedIOException(e);
}
return password;
}
private static String promptAndReadPassword()
{
String password = EMPTY;
Console console = System.console();
if (console != null)
password = String.valueOf(console.readPassword("Password:"));
return password;
}
private static class JmxConnectionCommandInvoker implements IExecutionStrategy, AutoCloseable
{
private final JmxConnect connect;
public JmxConnectionCommandInvoker(JmxConnect connect)
{
this.connect = connect;
}
@Override
public int execute(ParseResult parseResult) throws ExecutionException, ParameterException
{
CommandSpec lastParent = lastExecutableSubcommandWithSameParent(parseResult.asCommandLineList());
if (lastParent.userObject() instanceof AbstractCommand)
{
AbstractCommand command = (AbstractCommand) lastParent.userObject();
if (command.shouldConnect())
connect.run();
command.probe(connect.probe());
}
return new RunLast().execute(parseResult);
}
@Override
public void close() throws CloseException
{
try
{
if (connect.probe() != null)
((AutoCloseable) connect.probe()).close();
}
catch (Exception e)
{
throw new CloseException("Failed to close JMX connection", e);
}
}
private static CommandLine.Model.CommandSpec lastExecutableSubcommandWithSameParent(List<CommandLine> parsedCommands)
{
int start = parsedCommands.size() - 1;
for (int i = parsedCommands.size() - 2; i >= 0; i--)
{
if (parsedCommands.get(i).getParent() != parsedCommands.get(i + 1).getParent())
break;
start = i;
}
return parsedCommands.get(start).getCommandSpec();
}
private static class CloseException extends RuntimeException
{
public CloseException(String message, Throwable cause)
{
super(message, cause);
}
}
}
}

View File

@ -18,15 +18,15 @@
package org.apache.cassandra.tools.nodetool; package org.apache.cassandra.tools.nodetool;
import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Preconditions.checkState;
import io.airlift.airline.Command;
import java.io.IOException; import java.io.IOException;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import picocli.CommandLine.Command;
@Command(name = "join", description = "Join the ring") @Command(name = "join", description = "Join the ring")
public class Join extends NodeToolCmd public class Join extends AbstractCommand
{ {
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)

View File

@ -15,38 +15,35 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.apache.cassandra.tools; package org.apache.cassandra.tools.nodetool;
import java.io.PrintStream; import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
import io.airlift.airline.Arguments;
import io.airlift.airline.Command;
import org.apache.cassandra.auth.AuthKeyspace; import org.apache.cassandra.auth.AuthKeyspace;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import org.apache.cassandra.tools.NodeProbe;
import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;
import static org.apache.cassandra.tools.nodetool.CommandUtils.printSet;
/** /**
* Nodetool command to list available CIDR groups, in the table {@link AuthKeyspace#CIDR_GROUPS} * Nodetool command to list available CIDR groups, in the table {@link AuthKeyspace#CIDR_GROUPS}
*/ */
@Command(name = "listcidrgroups", description = "List existing cidr groups") @Command(name = "listcidrgroups", description = "List existing cidr groups")
public class ListCIDRGroups extends NodeToolCmd public class ListCIDRGroups extends AbstractCommand
{ {
@Arguments(usage = "[<cidrGroup>]", description = "LIST operation can be invoked with or without cidr group name") @Parameters(paramLabel = "cidrGroup", description = "LIST operation can be invoked with or without cidr group name", arity = "0..1")
private List<String> args = new ArrayList<>(); private String cidrGroup;
@Override @Override
public void execute(NodeProbe probe) public void execute(NodeProbe probe)
{ {
PrintStream out = probe.output().out; if (StringUtils.isEmpty(cidrGroup))
if (args.size() < 1)
{ {
probe.printSet(out, "CIDR Groups", probe.listAvailableCidrGroups()); printSet(probe.output().out, "CIDR Groups", probe.listAvailableCidrGroups());
return; return;
} }
String cidrGroup = args.get(0); printSet(probe.output().out, "CIDRs", probe.listCidrsOfCidrGroup(cidrGroup));
probe.printSet(out, "CIDRs", probe.listCidrsOfCidrGroup(cidrGroup));
} }
} }

Some files were not shown because too many files have changed in this diff Show More