diff --git a/.build/cassandra-deps-template.xml b/.build/cassandra-deps-template.xml
index 437c0c31b5a9..32c7e4a2c0af 100644
--- a/.build/cassandra-deps-template.xml
+++ b/.build/cassandra-deps-template.xml
@@ -120,6 +120,14 @@
io.airlift
airline
+
+ info.picocli
+ picocli
+
+
+ io.github.java-diff-utils
+ java-diff-utils
+
io.dropwizard.metrics
metrics-core
diff --git a/.build/parent-pom-template.xml b/.build/parent-pom-template.xml
index 0c0e5d4ccc9e..23caee6fd61c 100644
--- a/.build/parent-pom-template.xml
+++ b/.build/parent-pom-template.xml
@@ -714,6 +714,16 @@
jbcrypt
0.4
+
+ info.picocli
+ picocli
+ 4.7.5
+
+
+ io.github.java-diff-utils
+ java-diff-utils
+ 4.12
+
io.airlift
airline
diff --git a/bin/nodetool b/bin/nodetool
index f78b02e34418..b1fb4f5a091d 100755
--- a/bin/nodetool
+++ b/bin/nodetool
@@ -98,13 +98,19 @@ if [ "x$MAX_HEAP_SIZE" = "x" ]; then
MAX_HEAP_SIZE="128m"
fi
+if [ "x$NODETOOL_RUNNER" = "x" ]; then
+ NODETOOL_RUNNER="org.apache.cassandra.tools.NodeTool"
+fi
+
+# shellcheck disable=SC2116
CMD=$(echo "$JAVA" $JAVA_AGENT -ea -cp "$CLASSPATH" $JVM_OPTS -Xmx$MAX_HEAP_SIZE \
-XX:ParallelGCThreads=1 \
-Dcassandra.storagedir="$cassandra_storagedir" \
-Dcassandra.logdir="$CASSANDRA_LOG_DIR" \
-Dlogback.configurationFile=logback-tools.xml \
- $JVM_ARGS \
- org.apache.cassandra.tools.NodeTool -p $JMX_PORT $ARGS)
+ "$JVM_ARGS" \
+ $NODETOOL_RUNNER \
+ -p "$JMX_PORT" "$ARGS")
if [ "x$ARCHIVE_COMMAND" != "x" ]
then
diff --git a/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java b/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java
index e1f6d28d86a9..cfec7274f1f8 100644
--- a/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java
+++ b/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java
@@ -77,6 +77,7 @@ public enum CassandraRelevantProperties
CACHEABLE_MUTATION_SIZE_LIMIT("cassandra.cacheable_mutation_size_limit_bytes", convertToString(1_000_000)),
CASSANDRA_ALLOW_SIMPLE_STRATEGY("cassandra.allow_simplestrategy"),
CASSANDRA_AVAILABLE_PROCESSORS("cassandra.available_processors"),
+ CASSANDRA_CLI_PICOCLI_LAYOUT("cassandra.cli.picocli.layout", "false"),
/** The classpath storage configuration file. */
CASSANDRA_CONFIG("cassandra.config", "cassandra.yaml"),
/**
diff --git a/src/java/org/apache/cassandra/management/BaseCommand.java b/src/java/org/apache/cassandra/management/BaseCommand.java
new file mode 100644
index 000000000000..3de461bb12f5
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/BaseCommand.java
@@ -0,0 +1,51 @@
+/*
+ * 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.management;
+
+import picocli.CommandLine;
+
+/**
+ * Base class for all nodetool commands.
+ */
+public abstract class BaseCommand implements Runnable
+{
+ /** The command specification, used to access command-specific properties. */
+ @CommandLine.Spec
+ protected CommandLine.Model.CommandSpec spec; // injected by picocli
+ /** The ServiceBridge instance to interact with the Cassandra node. */
+ protected ServiceBridge bridge;
+
+ /**
+ * The ServiceBridge instance is injected by the picocli framework during command execution and is used to
+ * interact with the Cassandra node. This method is called by picocli and used depending on the execution strategy.
+ * @param bridge The ServiceBridge instance to inject.
+ */
+ public void setBridge(ServiceBridge bridge)
+ {
+ this.bridge = bridge;
+ }
+
+ @Override
+ public void run()
+ {
+ execute(bridge);
+ }
+
+ protected abstract void execute(ServiceBridge probe);
+}
diff --git a/src/java/org/apache/cassandra/management/CassandraHelpCommand.java b/src/java/org/apache/cassandra/management/CassandraHelpCommand.java
new file mode 100644
index 000000000000..6d8d6c912b87
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/CassandraHelpCommand.java
@@ -0,0 +1,120 @@
+/*
+ * 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.management;
+
+import java.io.PrintWriter;
+import java.util.Map;
+
+import com.google.common.base.Preconditions;
+
+import picocli.CommandLine;
+
+@CommandLine.Command(name = "help",
+ header = "Display help information about the specified command.",
+ synopsisHeading = "%nUsage: ",
+ helpCommand = true,
+ description = { "%nWhen no COMMAND is given, the usage help for the main command is displayed.",
+ "If a COMMAND is specified, the help for that command is shown.%n" })
+public class CassandraHelpCommand implements CommandLine.IHelpCommandInitializable2, Runnable
+{
+ @CommandLine.Option(names = { "--help" }, usageHelp = true, descriptionKey = "helpCommand.help",
+ description = "Show usage help for the help command and exit.")
+ private boolean helpRequested;
+
+ @CommandLine.Parameters(paramLabel = "command", arity = "0..1", descriptionKey = "helpCommand.command",
+ description = "The COMMAND to display the usage help message for.")
+ private 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.
+ */
+ 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;
+ }
+
+ Map parentSubcommands = parent.getCommandSpec().subcommands();
+ CommandLine subcommand = parentSubcommands.get(commands);
+
+ if (parent.isAbbreviatedSubcommandsAllowed())
+ throw new CommandLine.ParameterException(parent, "Abbreviated subcommands are not allowed.", null, commands);
+
+ if (subcommand == null)
+ throw new CommandLine.ParameterException(parent, "Unknown subcommand '" + commands + "'.", null, commands);
+
+ 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 CassandraHelpLayout))
+ {
+ command.usage(writer, colors);
+ return;
+ }
+
+ Map helpSectionMap = CassandraHelpLayout.cassandraTopLevelHelpSectionKeys((CassandraHelpLayout) help);
+ for (String key : command.getHelpSectionKeys())
+ {
+ CommandLine.IHelpSectionRenderer renderer = helpSectionMap.get(key);
+ if (renderer == null)
+ continue;
+ sb.append(renderer.render(help));
+ }
+
+ writer.println(sb);
+ writer.flush();
+ }
+
+ /**
+ * 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");
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/CassandraHelpLayout.java b/src/java/org/apache/cassandra/management/CassandraHelpLayout.java
new file mode 100644
index 000000000000..c965f7826d57
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/CassandraHelpLayout.java
@@ -0,0 +1,489 @@
+/*
+ * 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.management;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import picocli.CommandLine;
+
+import static org.apache.cassandra.management.CommandUtils.leadingSpaces;
+import static org.apache.commons.lang3.ArrayUtils.isEmpty;
+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_DESCRIPTION;
+import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_DESCRIPTION_HEADING;
+import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_END_OF_OPTIONS;
+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_OPTION_LIST;
+import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_OPTION_LIST_HEADING;
+import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_PARAMETER_LIST;
+import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_PARAMETER_LIST_HEADING;
+import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_SYNOPSIS;
+import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_SYNOPSIS_HEADING;
+
+/**
+ * Help facotry for the Cassandra nodetool to generate the help output. This class is used to match
+ * the command output with the previously available nodetool help output format.
+ */
+public class CassandraHelpLayout extends CommandLine.Help
+{
+ public static final int DEFAULT_USAGE_HELP_WIDTH = 90;
+ private static final String DESCRIPTION_HEADING = "NAME%n";
+ private static final String SYNOPSIS_HEADING = "SYNOPSIS%n";
+ private static final String OPTIONS_HEADING = "OPTIONS%n";
+ private static final int COLUMN_INDENT = 8;
+ private static final int DESCRIPTION_INDENT = 4;
+ private static final int SUBCOMMANDS_INDENT = 4;
+ private static final CommandLine.Model.OptionSpec CASSANDRA_END_OF_OPTIONS_OPTION =
+ CommandLine.Model.OptionSpec.builder("--")
+ .description("This option can be used to separate command-line options from the " +
+ "list of argument, (useful when arguments might be mistaken for " +
+ "command-line options")
+ .arity("0")
+ .build();
+ private static final String TOP_LEVEL_SYNOPSIS_LIST_PREFIX = "usage:";
+ private static final String TOP_LEVEL_COMMAND_HEADING = "The most commonly used nodetool commands are:%n";
+ private static final String SYNOPSIS_SUBCOMMANDS_LABEL = " []";
+
+ public CassandraHelpLayout(CommandLine.Model.CommandSpec spec, ColorScheme scheme)
+ {
+ super(spec, scheme);
+ }
+
+ @Override
+ public String descriptionHeading(Object... params)
+ {
+ return createHeading(DESCRIPTION_HEADING, params);
+ }
+
+ /**
+ * @param params Arguments referenced by the format specifiers in the header strings
+ * @return the header string.
+ */
+ @Override
+ public String description(Object... params) {
+ CommandLine.Model.CommandSpec spec = commandSpec();
+ String fullName = spec.qualifiedName();
+
+ TextTable table = TextTable.forColumns(colorScheme(),
+ new Column(spec.usageMessage().width() - COLUMN_INDENT, COLUMN_INDENT,
+ Column.Overflow.WRAP));
+ table.setAdjustLineBreaksForWideCJKCharacters(spec.usageMessage().adjustLineBreaksForWideCJKCharacters());
+ table.indentWrappedLines = 0;
+
+ table.addRowValues(colorScheme().commandText(fullName)
+ .concat(" - ")
+ .concat(colorScheme().text(String.join(" ", spec.usageMessage().description()))));
+ table.addRowValues(Ansi.OFF.new Text("", colorScheme()));
+ return table.toString(new StringBuilder()).toString();
+ }
+
+ @Override
+ public String synopsisHeading(Object... params)
+ {
+ return createHeading(SYNOPSIS_HEADING, params);
+ }
+
+ @Override
+ public String synopsis(int synopsisHeadingLength)
+ {
+ return printDetailedSynopsis("", COLUMN_INDENT, true);
+ }
+
+ private Ansi.Text createCassandraSynopsisCommandText()
+ {
+ Ansi.Text commandText = ansi().new Text(0);
+ if (!commandSpec().subcommands().isEmpty())
+ return commandText.concat(SYNOPSIS_SUBCOMMANDS_LABEL);
+ return commandText;
+ }
+
+ private String printDetailedSynopsis(String synopsisPrefix, int columnIndent, boolean showEndOfOptionsDelimiter)
+ {
+ // Cassandra uses end of options delimiter in usage help.
+ commandSpec().usageMessage().showEndOfOptionsDelimiterInUsageHelp(showEndOfOptionsDelimiter);
+
+ CommandLine.Model.CommandSpec commandSpec = commandSpec();
+ ColorScheme colorScheme = colorScheme();
+
+ Set argsInGroups = new HashSet<>();
+ Ansi.Text groupsText = createDetailedSynopsisGroupsText(argsInGroups);
+ List optionsList = createCassandraSynopsisOptionsText(argsInGroups);
+ Ansi.Text endOfOptionsText = createDetailedSynopsisEndOfOptionsText();
+ Ansi.Text positionalParamText = createCassandraSynopsisPositionalsText(argsInGroups);
+ Ansi.Text commandText = createCassandraSynopsisCommandText();
+
+ int width = commandSpec.usageMessage().width();
+ boolean isEmptyParent = commandSpec.parent() == null;
+ Ansi.Text mainCommandText = isEmptyParent ? colorScheme.commandText(commandSpec.name()) :
+ colorScheme.commandText(commandSpec.parent().qualifiedName());
+ TextTable textTable = TextTable.forColumns(colorScheme, new Column(width, columnIndent, Column.Overflow.WRAP));
+ textTable.indentWrappedLines = COLUMN_INDENT;
+ textTable.setAdjustLineBreaksForWideCJKCharacters(commandSpec.usageMessage().adjustLineBreaksForWideCJKCharacters());
+
+ // All other fields added to the synopsis are left-adjusted, so we don't need to align them.
+ Ansi.Text text = groupsText.concat(isEmptyParent ? Ansi.OFF.new Text(0) :
+ colorScheme.text(" ").concat(commandSpec.name()))
+ .concat(endOfOptionsText).concat(" ")
+ .concat(positionalParamText).concat(commandText);
+ Ansi.Text padding = Ansi.OFF.new Text(leadingSpaces(mainCommandText.plainString().length()), colorScheme);
+ List alignedOptions = alignByWidth(optionsList,
+ width - columnIndent - textTable.indentWrappedLines - synopsisPrefix.length(),
+ colorScheme);
+ // Align options by width
+ for (int i = 0; i < alignedOptions.size(); i++)
+ {
+ Ansi.Text option = alignedOptions.get(i);
+ if (i == 0)
+ option = colorScheme.text(synopsisPrefix).concat(synopsisPrefix.isEmpty() ? "" : " ")
+ .concat(mainCommandText).concat(" ")
+ .concat(option);
+ else
+ option = padding.concat(option);
+
+ if (i == alignedOptions.size() - 1)
+ textTable.addRowValues(option.concat(text));
+ else
+ textTable.addRowValues(option);
+ }
+
+ textTable.addEmptyRow();
+ return textTable.toString();
+ }
+
+ private static List alignByWidth(List optionsList, int width, ColorScheme colorScheme)
+ {
+ List result = new ArrayList<>();
+ Ansi.Text current = Ansi.OFF.new Text("", colorScheme);
+ for (Ansi.Text option : optionsList)
+ {
+ if (current.plainString().length() + option.plainString().length() >= width)
+ {
+ result.add(current);
+ current = Ansi.OFF.new Text("", colorScheme);
+ }
+ current = current.plainString().isEmpty() ? option : current.concat(" ").concat(option);
+ }
+ if (!current.plainString().isEmpty())
+ result.add(current);
+ return result;
+ }
+
+ private Ansi.Text createCassandraSynopsisPositionalsText(Collection done)
+ {
+ List positionals = cassandraPositionals(commandSpec());
+ positionals.removeAll(done);
+
+ IParamLabelRenderer parameterLabelRenderer = createMinimalSpacedParamLabelRenderer();
+ Ansi.Text text = colorScheme().text("");
+ for (CommandLine.Model.PositionalParamSpec positionalParam : positionals)
+ {
+ Ansi.Text label = parameterLabelRenderer.renderParameterLabel(positionalParam, colorScheme().ansi(), colorScheme().parameterStyles());
+ text = text.plainString().isEmpty() ? label : text.concat(" ").concat(label);
+ }
+ return text;
+ }
+
+ private List createCassandraSynopsisOptionsText(Collection done)
+ {
+ // Cassandra uses alphabetical order for options, ordered by short name.
+ List optionList = new ArrayList<>(commandSpec().options());
+ optionList.sort(createShortOptionNameComparator());
+ List result = new ArrayList<>();
+ optionList.removeAll(done);
+
+ ColorScheme colorScheme = colorScheme();
+ IParamLabelRenderer parameterLabelRenderer = createMinimalSpacedParamLabelRenderer();
+
+ for (CommandLine.Model.OptionSpec option : optionList)
+ {
+ if (option.hidden())
+ continue;
+
+ Ansi.Text text = ansi().new Text(0);
+ String nameString = option.shortestName();
+ Ansi.Text name = colorScheme.optionText(nameString);
+ Ansi.Text nameFull = colorScheme.optionText(option.longestName());
+ text = text.concat("[(")
+ .concat(name)
+ .concat(spacedParamLabel(option, parameterLabelRenderer, colorScheme))
+ .concat(" | ")
+ .concat(nameFull)
+ .concat(spacedParamLabel(option, parameterLabelRenderer, colorScheme))
+ .concat(")]");
+
+ result.add(text);
+ }
+ return result;
+ }
+
+ public static IParamLabelRenderer createMinimalSpacedParamLabelRenderer()
+ {
+ return new IParamLabelRenderer()
+ {
+ public Ansi.Text renderParameterLabel(CommandLine.Model.ArgSpec argSpec, Ansi ansi, List styles)
+ {
+ ColorScheme colorScheme = CommandLine.Help.defaultColorScheme(ansi);
+ if (argSpec.equals(CASSANDRA_END_OF_OPTIONS_OPTION))
+ return colorScheme.text("");
+ if (argSpec instanceof CommandLine.Model.OptionSpec && argSpec.typeInfo().isBoolean())
+ return colorScheme.text("");
+ return argSpec.isOption() ? colorScheme.optionText(argSpec.paramLabel()) :
+ colorScheme.parameterText(argSpec.paramLabel());
+ }
+
+ public String separator()
+ {
+ return "";
+ }
+ };
+ }
+
+ @Override
+ public String optionListHeading(Object... params)
+ {
+ return createHeading(OPTIONS_HEADING, params);
+ }
+
+ @Override
+ public String optionList()
+ {
+ Comparator comparator = createShortOptionNameComparator();
+ List optionList = commandSpec().options();
+
+ List options = new ArrayList<>(optionList);
+ options.sort(comparator);
+
+ Layout layout = cassandraSingleColumnOptionsParametersLayout();
+ layout.addAllOptions(options, createMinimalSpacedParamLabelRenderer());
+ return layout.toString();
+ }
+
+ @Override
+ public String endOfOptionsList() {
+ Layout layout = cassandraSingleColumnOptionsParametersLayout();
+ layout.addOption(CASSANDRA_END_OF_OPTIONS_OPTION, createMinimalSpacedParamLabelRenderer());
+ return layout.toString();
+ }
+
+ private Layout cassandraSingleColumnOptionsParametersLayout()
+ {
+ TextTable table = TextTable.forColumns(colorScheme(), new Column(commandSpec().usageMessage().width() - COLUMN_INDENT,
+ COLUMN_INDENT, Column.Overflow.WRAP));
+ table.setAdjustLineBreaksForWideCJKCharacters(commandSpec().usageMessage().adjustLineBreaksForWideCJKCharacters());
+ table.indentWrappedLines = DESCRIPTION_INDENT;
+ return new Layout(colorScheme(), table, new CassandraStyleOptionRenderer(), new CassandraStyleParameterRenderer());
+ }
+
+ @Override
+ public String parameterList()
+ {
+ List positionalParams = cassandraPositionals(commandSpec());
+ Layout layout = cassandraSingleColumnOptionsParametersLayout();
+ layout.addAllPositionalParameters(positionalParams, createMinimalSpacedParamLabelRenderer());
+ return layout.toString();
+ }
+
+ @Override
+ public String commandList(Map subcommands)
+ {
+ if (subcommands.isEmpty())
+ return "";
+ int width = commandSpec().usageMessage().width();
+ int commandLength = Math.min(CommandUtils.maxLength(subcommands.keySet()), width / 2);
+ int leadinColumnWidth = commandLength + SUBCOMMANDS_INDENT;
+ CommandLine.Help.TextTable table = TextTable.forColumns(colorScheme(),
+ new CommandLine.Help.Column(leadinColumnWidth, SUBCOMMANDS_INDENT,
+ CommandLine.Help.Column.Overflow.SPAN),
+ new CommandLine.Help.Column(width - leadinColumnWidth, SUBCOMMANDS_INDENT,
+ CommandLine.Help.Column.Overflow.WRAP));
+ table.setAdjustLineBreaksForWideCJKCharacters(commandSpec().usageMessage().adjustLineBreaksForWideCJKCharacters());
+
+ for (Map.Entry entry : subcommands.entrySet())
+ {
+ CommandLine.Help help = entry.getValue();
+ CommandLine.Model.UsageMessageSpec usage = help.commandSpec().usageMessage();
+ String header = isEmpty(usage.header()) ? (isEmpty(usage.description()) ? "" : usage.description()[0]) : usage.header()[0];
+ Ansi.Text[] lines = colorScheme().text(header).splitLines();
+ for (int i = 0; i < lines.length; i++)
+ table.addRowValues(i == 0 ? help.commandNamesText(", ") : Ansi.OFF.new Text(0), lines[i]);
+ }
+ return table.toString();
+ }
+
+ @Override
+ public String footerHeading(Object... params)
+ {
+ return createHeading("%n", params);
+ }
+
+ @Override
+ public String footer(Object... params)
+ {
+
+ String[] footer = isEmpty(
+ commandSpec().usageMessage().footer()) ? new String[]{ "See 'nodetool help ' for more information on a specific command." } :
+ commandSpec().usageMessage().footer();
+ return join(ansi(),
+ commandSpec().usageMessage().width(),
+ commandSpec().usageMessage().adjustLineBreaksForWideCJKCharacters(),
+ footer,
+ new StringBuilder(),
+ params).toString();
+ }
+
+ public String topLevelCommandListHeading(Object... params) {
+ return createHeading(TOP_LEVEL_COMMAND_HEADING, params);
+ }
+
+ public String topLevelSynopsis(Object... params)
+ {
+ return printDetailedSynopsis(TOP_LEVEL_SYNOPSIS_LIST_PREFIX, 0, false);
+ }
+
+ private static List cassandraPositionals(CommandLine.Model.CommandSpec commandSpec)
+ {
+ List positionals = new ArrayList<>(commandSpec.positionalParameters());
+ for (CommandLine.Model.PositionalParamSpec param : positionals)
+ {
+ if (param.hidden())
+ {
+ if (param.description()[0].equals(CommandUtils.CASSANDRA_BACKWARD_COMPATIBLE_MARKER))
+ {
+ positionals.clear();
+ positionals.add(param);
+ break;
+ }
+ else
+ positionals.remove(param);
+ }
+ }
+ return positionals;
+ }
+
+ /**
+ * Layout for cassandra help CLI output.
+ * @return List of keys for the help sections.
+ */
+ public static List cassandraHelpSectionKeys()
+ {
+ List result = new LinkedList<>();
+ result.add(SECTION_KEY_HEADER_HEADING);
+ result.add(SECTION_KEY_HEADER);
+ result.add(SECTION_KEY_DESCRIPTION_HEADING);
+ result.add(SECTION_KEY_DESCRIPTION);
+ result.add(SECTION_KEY_SYNOPSIS_HEADING);
+ result.add(SECTION_KEY_SYNOPSIS);
+ result.add(SECTION_KEY_OPTION_LIST_HEADING);
+ result.add(SECTION_KEY_OPTION_LIST);
+ result.add(SECTION_KEY_END_OF_OPTIONS);
+ result.add(SECTION_KEY_PARAMETER_LIST_HEADING);
+ result.add(SECTION_KEY_PARAMETER_LIST);
+ result.add(SECTION_KEY_COMMAND_LIST_HEADING);
+ result.add(SECTION_KEY_COMMAND_LIST);
+ result.add(SECTION_KEY_EXIT_CODE_LIST_HEADING);
+ result.add(SECTION_KEY_EXIT_CODE_LIST);
+ result.add(SECTION_KEY_FOOTER_HEADING);
+ result.add(SECTION_KEY_FOOTER);
+ return result;
+ }
+
+ /**
+ * 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 cassandraTopLevelHelpSectionKeys(CassandraHelpLayout layout)
+ {
+ Map 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, CommandLine.Help::commandList);
+ 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;
+ }
+
+ private static Ansi.Text spacedParamLabel(CommandLine.Model.OptionSpec optionSpec,
+ IParamLabelRenderer parameterLabelRenderer,
+ ColorScheme scheme)
+ {
+ return optionSpec.typeInfo().isBoolean() ? scheme.text("") :
+ scheme.text(" ").concat(parameterLabelRenderer.renderParameterLabel(optionSpec, scheme.ansi(), scheme.optionParamStyles()));
+ }
+
+ private static class CassandraStyleOptionRenderer implements IOptionRenderer
+ {
+ public Ansi.Text[][] render(CommandLine.Model.OptionSpec option, IParamLabelRenderer parameterLabelRenderer, ColorScheme scheme)
+ {
+ Ansi.Text optionText = scheme.optionText("");
+ for (int i = 0; i < option.names().length; i++)
+ {
+ String name = option.names()[i];
+ optionText = optionText.concat(scheme.optionText(name))
+ .concat(spacedParamLabel(option, parameterLabelRenderer, scheme))
+ .concat(i == option.names().length - 1 ? "" : ", ");
+ }
+
+ Ansi.Text descPadding = Ansi.OFF.new Text(leadingSpaces(DESCRIPTION_INDENT), scheme);
+ Ansi.Text desc = scheme.optionText(option.description().length == 0 ? "" : option.description()[0]);
+
+ Ansi.Text[][] result = new Ansi.Text[3][];
+ result[0] = new Ansi.Text[]{ optionText };
+ result[1] = new Ansi.Text[]{ descPadding.concat(desc) };
+ result[2] = new Ansi.Text[]{ Ansi.OFF.new Text("", scheme) };
+ return result;
+ }
+ }
+
+ private static class CassandraStyleParameterRenderer implements IParameterRenderer
+ {
+ @Override
+ public Ansi.Text[][] render(CommandLine.Model.PositionalParamSpec param, IParamLabelRenderer parameterLabelRenderer, ColorScheme scheme)
+ {
+ String descriptionString = param.description()[0].equals(CommandUtils.CASSANDRA_BACKWARD_COMPATIBLE_MARKER) ?
+ param.description()[1] : param.description()[0];
+
+ Ansi.Text descPadding = Ansi.OFF.new Text(leadingSpaces(DESCRIPTION_INDENT), scheme);
+ Ansi.Text[][] result = new Ansi.Text[3][];
+ result[0] = new Ansi.Text[]{ parameterLabelRenderer.renderParameterLabel(param, scheme.ansi(), scheme.parameterStyles()) };
+ result[1] = new Ansi.Text[]{ descPadding.concat(scheme.parameterText(descriptionString)) };
+ result[2] = new Ansi.Text[]{ Ansi.OFF.new Text("", scheme) };
+ return result;
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/CommandUtils.java b/src/java/org/apache/cassandra/management/CommandUtils.java
new file mode 100644
index 000000000000..58706f21e162
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/CommandUtils.java
@@ -0,0 +1,61 @@
+/*
+ * 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.management;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.apache.cassandra.db.compaction.CompactionManagerMBean;
+import org.apache.cassandra.service.StorageServiceMBean;
+
+public final class CommandUtils
+{
+ public static final String CASSANDRA_BACKWARD_COMPATIBLE_MARKER = "cassandra-backward-compatible";
+
+ /**
+ * Returns a string with the given number of leading spaces.
+ *
+ * @param num the number of leading spaces
+ * @return the string with the given number of leading spaces
+ */
+ public static String leadingSpaces(int num)
+ {
+ char[] buff = new char[num];
+ Arrays.fill(buff, ' ');
+ return new String(buff);
+ }
+
+ 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 StorageServiceMBean ssProxy(ServiceBridge bridge)
+ {
+ return bridge.mBean(StorageServiceMBean.class);
+ }
+
+ public static CompactionManagerMBean cmProxy(ServiceBridge bridge)
+ {
+ return bridge.mBean(CompactionManagerMBean.class);
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/ServiceBridge.java b/src/java/org/apache/cassandra/management/ServiceBridge.java
new file mode 100644
index 000000000000..80edce06824a
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/ServiceBridge.java
@@ -0,0 +1,27 @@
+/*
+ * 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.management;
+
+/**
+ * Management context for nodetool commands to access management services like StorageServiceMBean etc.
+ */
+public interface ServiceBridge
+{
+ T mBean(Class clazz);
+}
diff --git a/src/java/org/apache/cassandra/management/api/AbortBootstrap.java b/src/java/org/apache/cassandra/management/api/AbortBootstrap.java
new file mode 100644
index 000000000000..cbea96efedc5
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/AbortBootstrap.java
@@ -0,0 +1,47 @@
+/*
+ * 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.management.api;
+
+import org.apache.cassandra.management.BaseCommand;
+import org.apache.cassandra.management.ServiceBridge;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Option;
+
+import static org.apache.cassandra.management.CommandUtils.ssProxy;
+import static org.apache.commons.lang3.StringUtils.EMPTY;
+import static org.apache.commons.lang3.StringUtils.isEmpty;
+
+@Command(name = "abortbootstrap", description = "Abort a failed bootstrap")
+public class AbortBootstrap extends BaseCommand
+{
+ @Option(names = "--node", description = "Node ID of the node that failed bootstrap")
+ public String nodeId = EMPTY;
+
+ @Option(names = "--ip", description = "IP of the node that failed bootstrap")
+ public String endpoint = EMPTY;
+
+ @Override
+ public void execute(ServiceBridge probe)
+ {
+ if (isEmpty(nodeId) && isEmpty(endpoint))
+ throw new IllegalArgumentException("Either --node or --ip needs to be set");
+ if (!isEmpty(nodeId) && !isEmpty(endpoint))
+ throw new IllegalArgumentException("Only one of --node or --ip need to be set");
+ ssProxy(probe).abortBootstrap(nodeId, endpoint);
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/Assassinate.java b/src/java/org/apache/cassandra/management/api/Assassinate.java
new file mode 100644
index 000000000000..49a7914dd126
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/Assassinate.java
@@ -0,0 +1,38 @@
+/*
+ * 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.management.api;
+
+import org.apache.cassandra.management.BaseCommand;
+import org.apache.cassandra.management.ServiceBridge;
+import picocli.CommandLine;
+
+import static org.apache.cassandra.management.CommandUtils.ssProxy;
+import static org.apache.commons.lang3.StringUtils.EMPTY;
+
+@CommandLine.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 BaseCommand
+{
+ @CommandLine.Parameters(description = "IP address of the endpoint to assassinate", arity = "1")
+ public String ip_address = EMPTY;
+
+ @Override
+ public void execute(ServiceBridge probe)
+ {
+ ssProxy(probe).assassinateEndpoint(ip_address);
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/BootstrapResume.java b/src/java/org/apache/cassandra/management/api/BootstrapResume.java
new file mode 100644
index 000000000000..7cae29586aa2
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/BootstrapResume.java
@@ -0,0 +1,52 @@
+/*
+ * 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.management.api;
+
+import java.io.IOError;
+import java.io.IOException;
+
+import io.airlift.airline.Command;
+import io.airlift.airline.Option;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+import static org.apache.cassandra.config.CassandraRelevantProperties.RESET_BOOTSTRAP_PROGRESS;
+
+@Command(name = "resume", description = "Resume bootstrap streaming")
+public class BootstrapResume extends NodeToolCmd
+{
+ @Option(title = "force",
+ name = { "-f", "--force"},
+ description = "Use --force to resume bootstrap regardless of cassandra.reset_bootstrap_progress environment variable. WARNING: This is potentially dangerous, see CASSANDRA-17679")
+ boolean force = false;
+
+ @Override
+ protected void execute(NodeProbe probe)
+ {
+ try
+ {
+ if ((!RESET_BOOTSTRAP_PROGRESS.isPresent() || RESET_BOOTSTRAP_PROGRESS.getBoolean()) && !force)
+ throw new RuntimeException("'nodetool bootstrap resume' is disabled.");
+ probe.resumeBootstrap(probe.output().out);
+ }
+ catch (IOException e)
+ {
+ throw new IOError(e);
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/CIDRFilteringStats.java b/src/java/org/apache/cassandra/management/api/CIDRFilteringStats.java
new file mode 100644
index 000000000000..156814acf811
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/CIDRFilteringStats.java
@@ -0,0 +1,93 @@
+/*
+ * 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.management.api;
+
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.db.virtual.CIDRFilteringMetricsTable.CIDRFilteringMetricsCountsTable;
+import org.apache.cassandra.db.virtual.CIDRFilteringMetricsTable.CIDRFilteringMetricsLatenciesTable;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+import org.apache.cassandra.tools.nodetool.formatter.TableBuilder;
+
+/**
+ * Nodetool command to view stats related to CIDR filtering
+ */
+@Command(name = "cidrfilteringstats", description = "Print statistics on CIDR filtering")
+public class CIDRFilteringStats extends NodeToolCmd
+{
+ private void printCountsMetrics(NodeProbe probe, PrintStream out)
+ {
+ out.println(CIDRFilteringMetricsCountsTable.TABLE_NAME + ": ");
+ TableBuilder outputTable = new TableBuilder();
+ outputTable.add(CIDRFilteringMetricsCountsTable.NAME_COL,
+ CIDRFilteringMetricsCountsTable.VALUE_COL);
+
+ Map metricRows = probe.getCountsMetricsFromVtable();
+
+ for (Map.Entry row : metricRows.entrySet())
+ {
+ outputTable.add(row.getKey(), Long.toString(row.getValue()));
+ }
+
+ outputTable.printTo(out);
+ out.println();
+ }
+
+ private void printLatenciesMetrics(NodeProbe probe, PrintStream out)
+ {
+ out.println(CIDRFilteringMetricsLatenciesTable.TABLE_NAME + ": ");
+ TableBuilder outputTable = new TableBuilder();
+ outputTable.add(CIDRFilteringMetricsLatenciesTable.NAME_COL,
+ CIDRFilteringMetricsLatenciesTable.P50_COL,
+ CIDRFilteringMetricsLatenciesTable.P95_COL,
+ CIDRFilteringMetricsLatenciesTable.P99_COL,
+ CIDRFilteringMetricsLatenciesTable.P999_COL,
+ CIDRFilteringMetricsLatenciesTable.MAX_COL);
+
+ Map> metricRows = probe.getLatenciesMetricsFromVtable();
+
+ for (Map.Entry> row : metricRows.entrySet())
+ {
+ List values = new ArrayList<>();
+ values.add(row.getKey());
+ for (Double col : row.getValue())
+ {
+ values.add(Double.toString(col));
+ }
+ outputTable.add(values);
+ }
+
+ outputTable.printTo(out);
+ out.println();
+ }
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ PrintStream out = probe.output().out;
+
+ // Read rows from corresponding virtual tables and print
+ printCountsMetrics(probe, out);
+ printLatenciesMetrics(probe, out);
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/CMSAdmin.java b/src/java/org/apache/cassandra/management/api/CMSAdmin.java
new file mode 100644
index 000000000000..3aa637703f4b
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/CMSAdmin.java
@@ -0,0 +1,186 @@
+/*
+ * 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.management.api;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+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.NodeTool;
+
+public abstract class CMSAdmin extends NodeTool.NodeToolCmd
+{
+ @Command(name = "describe", description = "Describe the current Cluster Metadata Service")
+ public static class DescribeCMS extends NodeTool.NodeToolCmd
+ {
+ @Override
+ protected void execute(NodeProbe probe)
+ {
+ Map info = probe.getCMSOperationsProxy().describeCMS();
+ output.out.printf("Cluster Metadata Service:%n");
+ output.out.printf("Members: %s%n", info.get("MEMBERS"));
+ output.out.printf("Is Member: %s%n", info.get("IS_MEMBER"));
+ output.out.printf("Service State: %s%n", info.get("SERVICE_STATE"));
+ output.out.printf("Is Migrating: %s%n", info.get("IS_MIGRATING"));
+ output.out.printf("Epoch: %s%n", info.get("EPOCH"));
+ output.out.printf("Local Pending Count: %s%n", info.get("LOCAL_PENDING"));
+ output.out.printf("Commits Paused: %s%n", info.get("COMMITS_PAUSED"));
+ output.out.printf("Replication factor: %s%n", info.get("REPLICATION_FACTOR"));
+ }
+ }
+
+ @Command(name = "initialize", description = "Upgrade from gossip and initialize CMS")
+ public static class InitializeCMS extends NodeTool.NodeToolCmd
+ {
+ @Option(title = "ignored endpoints", name = { "-i", "--ignore"}, description = "Hosts to ignore due to them being down")
+ private List endpoint = new ArrayList<>();
+
+ @Override
+ protected void execute(NodeProbe probe)
+ {
+ probe.getCMSOperationsProxy().initializeCMS(endpoint);
+ }
+ }
+
+ @Command(name = "reconfigure", description = "Reconfigure replication factor of CMS")
+ public static class ReconfigureCMS extends NodeTool.NodeToolCmd
+ {
+ @Option(title = "status",
+ name = {"--status"},
+ description = "Poll status of the reconfigure command. All other flags and arguments are ignored when this one is used.")
+ private boolean status = false;
+
+ @Option(title = "resume",
+ name = {"-r", "--resume"},
+ description = "Whether or not a previously interrupted sequence should be resumed")
+ private boolean resume = false;
+
+ @Option(title = "cancel",
+ name = {"-c", "--cancel"},
+ description = "Cancels any in progress CMS reconfiguration")
+ private boolean cancel = false;
+
+ @Arguments(usage = "[] or : ... ", description = "Replication factor of new CMS")
+ private List args = new ArrayList<>();
+
+ @Override
+ protected void execute(NodeProbe probe)
+ {
+ if (status)
+ {
+ Map> status = probe.getCMSOperationsProxy().reconfigureCMSStatus();
+ if (status == null)
+ {
+ output.out.println("No active reconfiguration");
+ }
+ else
+ {
+ for (Map.Entry> e : status.entrySet())
+ output.out.printf("%s: %s%n", e.getKey(), e.getValue());
+ }
+ return;
+ }
+ if (resume)
+ {
+ if (!args.isEmpty())
+ throw new IllegalArgumentException("Replication factor should not be set if previous operation is resumed");
+
+ probe.getCMSOperationsProxy().resumeReconfigureCms();
+ return;
+ }
+
+ if (cancel)
+ {
+ probe.getCMSOperationsProxy().cancelReconfigureCms();
+ return;
+ }
+
+ if (args.isEmpty())
+ throw new IllegalArgumentException("Replication factor is empty");
+
+ Map parsedRfs = new HashMap<>(args.size());
+ for (String rf : args)
+ {
+ if (!rf.contains(":"))
+ {
+ if (args.size() > 1)
+ throw new IllegalArgumentException("Simple placement can only specify a single replication factor accross all data centers");
+ int parsedRf;
+ try
+ {
+ parsedRf = Integer.parseInt(args.get(0));
+ }
+ catch (Throwable t)
+ {
+ throw new IllegalArgumentException(String.format("Can not parse replication factor from %s", args.get(0)));
+ }
+ probe.getCMSOperationsProxy().reconfigureCMS(parsedRf);
+ return;
+ }
+ else
+ {
+ String[] splits = rf.split(":");
+ if (splits.length > 2)
+ throw new IllegalArgumentException(String.format("Can not parse replication factor %s", rf));
+ String dc = splits[0];
+ int parsedRf;
+ try
+ {
+ parsedRf = Integer.parseInt(splits[1]);
+ }
+ catch (Throwable t)
+ {
+ throw new IllegalArgumentException(String.format("Can not parse replication factor from %s", args.get(0)));
+ }
+ parsedRfs.put(dc, parsedRf);
+ }
+ }
+
+ probe.getCMSOperationsProxy().reconfigureCMS(parsedRfs);
+ }
+ }
+
+ @Command(name = "snapshot", description = "Request a checkpointing snapshot of cluster metadata")
+ public static class Snapshot extends NodeTool.NodeToolCmd
+ {
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ probe.getCMSOperationsProxy().snapshotClusterMetadata();
+ }
+ }
+
+ @Command(name = "unregister", description = "Unregister nodes in LEFT state")
+ public static class Unregister extends NodeTool.NodeToolCmd
+ {
+ @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 = "+")
+ public List nodeIds;
+
+ @Override
+ protected void execute(NodeProbe probe)
+ {
+ probe.getCMSOperationsProxy().unregisterLeftNodes(nodeIds);
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/Cleanup.java b/src/java/org/apache/cassandra/management/api/Cleanup.java
new file mode 100644
index 000000000000..444d0f3fb2cb
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/Cleanup.java
@@ -0,0 +1,62 @@
+/*
+ * 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.management.api;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import io.airlift.airline.Arguments;
+import io.airlift.airline.Command;
+import io.airlift.airline.Option;
+import org.apache.cassandra.schema.SchemaConstants;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+@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
+{
+ @Arguments(usage = "[ ...]", description = "The keyspace followed by one or many tables")
+ private List args = new ArrayList<>();
+
+ @Option(title = "jobs",
+ name = {"-j", "--jobs"},
+ description = "Number of sstables to cleanup simultanously, set to 0 to use all available compaction threads")
+ private int jobs = 2;
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ List keyspaces = parseOptionalKeyspace(args, probe, KeyspaceSet.NON_LOCAL_STRATEGY);
+ String[] tableNames = parseOptionalTables(args);
+
+ for (String keyspace : keyspaces)
+ {
+ if (SchemaConstants.isLocalSystemKeyspace(keyspace))
+ continue;
+
+ try
+ {
+ probe.forceKeyspaceCleanup(probe.output().out, jobs, keyspace, tableNames);
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException("Error occurred during cleanup", e);
+ }
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/ClearSnapshot.java b/src/java/org/apache/cassandra/management/api/ClearSnapshot.java
new file mode 100644
index 000000000000..6b387dd45872
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/ClearSnapshot.java
@@ -0,0 +1,129 @@
+/*
+ * 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.management.api;
+
+import java.io.IOException;
+import java.time.Instant;
+import java.time.format.DateTimeParseException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import io.airlift.airline.Arguments;
+import io.airlift.airline.Command;
+import io.airlift.airline.Option;
+import org.apache.cassandra.config.DurationSpec;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+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")
+public class ClearSnapshot extends NodeToolCmd
+{
+ @Arguments(usage = "[...] ", description = "Remove snapshots from the given keyspaces")
+ private List keyspaces = new ArrayList<>();
+
+ @Option(title = "snapshot_name", name = "-t", description = "Remove the snapshot with a given name")
+ private String snapshotName = EMPTY;
+
+ @Option(title = "clear_all_snapshots", name = "--all", description = "Removes all snapshots")
+ private boolean clearAllSnapshots = false;
+
+ @Option(title = "older_than", name = "--older-than", description = "Clear snapshots older than specified time period.")
+ private String olderThan;
+
+ @Option(title = "older_than_timestamp", name = "--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'")
+ private String olderThanTimestamp;
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ if (snapshotName.isEmpty() && !clearAllSnapshots)
+ throw new IllegalArgumentException("Specify snapshot name or --all");
+
+ if (!snapshotName.isEmpty() && clearAllSnapshots)
+ throw new IllegalArgumentException("Specify only one of snapshot name or --all");
+
+ if (olderThan != null && olderThanTimestamp != null)
+ throw new IllegalArgumentException("Specify only one of --older-than or --older-than-timestamp");
+
+ if (!snapshotName.isEmpty() && olderThan != null)
+ throw new IllegalArgumentException("Specifying snapshot name together with --older-than flag is not allowed");
+
+ if (!snapshotName.isEmpty() && olderThanTimestamp != null)
+ throw new IllegalArgumentException("Specifying snapshot name together with --older-than-timestamp flag is not allowed");
+
+ if (olderThanTimestamp != null)
+ try
+ {
+ Instant.parse(olderThanTimestamp);
+ }
+ catch (DateTimeParseException ex)
+ {
+ throw new IllegalArgumentException("Parameter --older-than-timestamp has to be a valid instant in ISO format.");
+ }
+
+ Long olderThanInSeconds = null;
+ if (olderThan != null)
+ // fail fast when it is not valid
+ olderThanInSeconds = new DurationSpec.LongSecondsBound(olderThan).toSeconds();
+
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("Requested clearing snapshot(s) for ");
+
+ if (keyspaces.isEmpty())
+ sb.append("[all keyspaces]");
+ else
+ sb.append('[').append(join(keyspaces, ", ")).append(']');
+
+ if (snapshotName.isEmpty())
+ sb.append(" with [all snapshots]");
+ else
+ sb.append(" with snapshot name [").append(snapshotName).append(']');
+
+ if (olderThanInSeconds != null)
+ sb.append(" older than ")
+ .append(olderThanInSeconds)
+ .append(" seconds.");
+
+ if (olderThanTimestamp != null)
+ sb.append(" older than timestamp ").append(olderThanTimestamp);
+
+ probe.output().out.println(sb);
+
+ try
+ {
+ Map parameters = new HashMap<>();
+ if (olderThan != null)
+ parameters.put("older_than", olderThan);
+ if (olderThanTimestamp != null)
+ parameters.put("older_than_timestamp", olderThanTimestamp);
+
+ probe.clearSnapshot(parameters, snapshotName, toArray(keyspaces, String.class));
+ } catch (IOException e)
+ {
+ throw new RuntimeException("Error during clearing snapshots", e);
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/ClientStats.java b/src/java/org/apache/cassandra/management/api/ClientStats.java
new file mode 100644
index 000000000000..fc95b70c2e40
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/ClientStats.java
@@ -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.management.api;
+
+import java.io.PrintStream;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.stream.Collectors;
+
+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.NodeTool.NodeToolCmd;
+import org.apache.cassandra.tools.nodetool.formatter.TableBuilder;
+import org.apache.cassandra.transport.ClientStat;
+import org.apache.cassandra.transport.ConnectedClient;
+
+@Command(name = "clientstats", description = "Print information about connected clients")
+public class ClientStats extends NodeToolCmd
+{
+ @Option(title = "list_connections", name = "--all", description = "Lists all connections")
+ private boolean listConnections = false;
+
+ @Option(title = "by_protocol", name = "--by-protocol", description = "Lists most recent client connections by protocol version")
+ private boolean connectionsByProtocolVersion = false;
+
+ @Option(title = "clear_history", name = "--clear-history", description = "Clear the history of connected clients")
+ private boolean clearConnectionHistory = false;
+
+ @Option(title = "list_connections_with_client_options", name = "--client-options", description = "Lists all connections and the client options")
+ private boolean clientOptions = false;
+
+ @Option(title = "verbose", name = "--verbose", description = "Lists all connections with additional details (client options, authenticator-specific metadata and more)")
+ private boolean verbose = false;
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ PrintStream out = probe.output().out;
+ if (clearConnectionHistory)
+ {
+ out.println("Clearing connection history");
+ probe.clearConnectionHistory();
+ return;
+ }
+
+ if (connectionsByProtocolVersion)
+ {
+ SimpleDateFormat sdf = new SimpleDateFormat("MMM dd, yyyy HH:mm:ss");
+
+ out.println("Clients by protocol version");
+ out.println();
+
+ List> clients = (List>) probe.getClientMetric("clientsByProtocolVersion");
+
+ if (!clients.isEmpty())
+ {
+ TableBuilder table = new TableBuilder();
+ table.add("Protocol-Version", "IP-Address", "Last-Seen");
+
+ for (Map client : clients)
+ {
+ table.add(client.get(ClientStat.PROTOCOL_VERSION),
+ client.get(ClientStat.INET_ADDRESS),
+ sdf.format(new Date(Long.valueOf(client.get(ClientStat.LAST_SEEN_TIME)))));
+ }
+
+ table.printTo(out);
+ out.println();
+ }
+
+ return;
+ }
+
+ // Note: for compatbility with existing implementation if someone passes --all (listConnections),
+ // --client-options, and --metadata all three will be printed.
+ List> clients = (List>) probe.getClientMetric("connections");
+ if (!clients.isEmpty() && (listConnections || clientOptions || verbose))
+ {
+ ImmutableList.Builder tableHeaderBuilder = ImmutableList.builder()
+ .add("Address", "SSL", "Cipher", "Protocol", "Version",
+ "User", "Keyspace", "Requests", "Driver-Name",
+ "Driver-Version");
+ ImmutableList.Builder tableFieldsBuilder = ImmutableList.builder()
+ .add(ConnectedClient.ADDRESS, ConnectedClient.SSL,
+ ConnectedClient.CIPHER, ConnectedClient.PROTOCOL,
+ ConnectedClient.VERSION, ConnectedClient.USER,
+ ConnectedClient.KEYSPACE, ConnectedClient.REQUESTS,
+ ConnectedClient.DRIVER_NAME, ConnectedClient.DRIVER_VERSION);
+ if (clientOptions || verbose)
+ {
+ tableHeaderBuilder.add("Client-Options");
+ tableFieldsBuilder.add(ConnectedClient.CLIENT_OPTIONS);
+ }
+
+ if (verbose)
+ {
+ tableHeaderBuilder.add("Auth-Mode", "Auth-Metadata");
+ tableFieldsBuilder.add(ConnectedClient.AUTHENTICATION_MODE, ConnectedClient.AUTHENTICATION_METADATA);
+ }
+
+ List tableHeader = tableHeaderBuilder.build();
+ List tableFields = tableFieldsBuilder.build();
+ printTable(out, tableHeader, tableFields, clients);
+ }
+
+ Map connectionsByUser = (Map) probe.getClientMetric("connectedNativeClientsByUser");
+ int total = connectionsByUser.values().stream().reduce(0, Integer::sum);
+ out.println("Total connected clients: " + total);
+ out.println();
+ TableBuilder table = new TableBuilder();
+ table.add("User", "Connections");
+ for (Entry entry : connectionsByUser.entrySet())
+ {
+ table.add(entry.getKey(), entry.getValue().toString());
+ }
+ table.printTo(out);
+ }
+
+ /**
+ * Convenience function to print a table with the given header and the resolved fields for each connection.
+ *
+ * @param out print stream to print to.
+ * @param headers headers for the table
+ * @param tableFields the fields from {@link ConnectedClient} to retrieve from each client connection.
+ * @param clients the clients to print, each client being a row inthe table.
+ */
+ private void printTable(PrintStream out, List headers, List tableFields, List> clients)
+ {
+ TableBuilder table = new TableBuilder();
+ table.add(headers);
+ for (Map conn : clients)
+ {
+ List connectionFieldValues = tableFields.stream()
+ .map(conn::get)
+ .collect(Collectors.toList());
+ table.add(connectionFieldValues);
+ }
+ table.printTo(out);
+ out.println();
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/Compact.java b/src/java/org/apache/cassandra/management/api/Compact.java
new file mode 100644
index 000000000000..c50ed999d78c
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/Compact.java
@@ -0,0 +1,107 @@
+/*
+ * 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.management.api;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.cassandra.management.BaseCommand;
+import org.apache.cassandra.management.ServiceBridge;
+import picocli.CommandLine;
+
+import static org.apache.cassandra.management.CommandUtils.cmProxy;
+import static org.apache.cassandra.management.CommandUtils.ssProxy;
+import static org.apache.cassandra.tools.NodeTool.NodeToolCmd.parseOptionalKeyspace;
+import static org.apache.cassandra.tools.NodeTool.NodeToolCmd.parseOptionalTables;
+import static org.apache.commons.lang3.StringUtils.EMPTY;
+
+@CommandLine.Command(name = "compact", description = "Force a (major) compaction on one or more tables or user-defined compaction on given SSTables")
+public class Compact extends BaseCommand
+{
+ @CommandLine.Parameters(paramLabel = " ...] or [",
+ description = "The keyspace followed by one or many tables or list of SSTable data files when using --user-defined")
+ public List args = new ArrayList<>();
+
+ @CommandLine.Option(names = { "-s", "--split-output"}, description = "Use -s to not create a single big file")
+ public boolean splitOutput = false;
+
+ @CommandLine.Option(names = { "--user-defined"}, description = "Use --user-defined to submit listed files for user-defined compaction")
+ public boolean userDefined = false;
+
+ @CommandLine.Option(names = { "-st", "--start-token"}, description = "Use -st to specify a token at which the compaction range starts (inclusive)")
+ public String startToken = EMPTY;
+
+ @CommandLine.Option(names = { "-et", "--end-token"}, description = "Use -et to specify a token at which compaction range ends (inclusive)")
+ public String endToken = EMPTY;
+
+ @CommandLine.Option(names = { "--partition"}, description = "String representation of the partition key")
+ public String partitionKey = EMPTY;
+
+ @Override
+ public void execute(ServiceBridge probe)
+ {
+ final boolean startEndTokenProvided = !(startToken.isEmpty() && endToken.isEmpty());
+ final boolean partitionKeyProvided = !partitionKey.isEmpty();
+ final boolean tokenProvided = startEndTokenProvided || partitionKeyProvided;
+ if (splitOutput && (userDefined || tokenProvided))
+ {
+ throw new RuntimeException("Invalid option combination: Can not use split-output here");
+ }
+ if (userDefined && tokenProvided)
+ {
+ throw new RuntimeException("Invalid option combination: Can not provide tokens when using user-defined");
+ }
+
+ if (userDefined)
+ {
+ try
+ {
+ String userDefinedFiles = String.join(",", args);
+ cmProxy(probe).forceUserDefinedCompaction(userDefinedFiles);
+ } catch (Exception e) {
+ throw new RuntimeException("Error occurred during user defined compaction", e);
+ }
+ return;
+ }
+
+ List keyspaces = parseOptionalKeyspace(args, probe);
+ String[] tableNames = parseOptionalTables(args);
+
+ for (String keyspace : keyspaces)
+ {
+ try
+ {
+ if (startEndTokenProvided)
+ {
+ ssProxy(probe).forceKeyspaceCompactionForTokenRange(keyspace, startToken, endToken, tableNames);
+ }
+ else if (partitionKeyProvided)
+ {
+ ssProxy(probe).forceKeyspaceCompactionForPartitionKey(keyspace, partitionKey, tableNames);
+ }
+ else
+ {
+ ssProxy(probe).forceKeyspaceCompaction(splitOutput, keyspace, tableNames);
+ }
+ } catch (Exception e)
+ {
+ throw new RuntimeException("Error occurred during compaction", e);
+ }
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/CompactionHistory.java b/src/java/org/apache/cassandra/management/api/CompactionHistory.java
new file mode 100644
index 000000000000..bfe0de71e2b8
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/CompactionHistory.java
@@ -0,0 +1,48 @@
+/*
+ * 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.management.api;
+
+import io.airlift.airline.Command;
+import io.airlift.airline.Option;
+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.CompactionHistoryPrinter;
+import org.apache.cassandra.tools.nodetool.stats.StatsHolder;
+import org.apache.cassandra.tools.nodetool.stats.StatsPrinter;
+
+@Command(name = "compactionhistory", description = "Print history of compaction")
+public class CompactionHistory extends NodeToolCmd
+{
+ @Option(title = "format",
+ name = {"-F", "--format"},
+ description = "Output format (json, yaml)")
+ private String outputFormat = "";
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ if (!outputFormat.isEmpty() && !"json".equals(outputFormat) && !"yaml".equals(outputFormat))
+ {
+ throw new IllegalArgumentException("arguments for -F are json,yaml only.");
+ }
+ StatsHolder data = new CompactionHistoryHolder(probe);
+ StatsPrinter printer = CompactionHistoryPrinter.from(outputFormat);
+ printer.print(data, probe.output().out);
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/CompactionStats.java b/src/java/org/apache/cassandra/management/api/CompactionStats.java
new file mode 100644
index 000000000000..b1ea4651df82
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/CompactionStats.java
@@ -0,0 +1,171 @@
+/*
+ * 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.management.api;
+
+import java.io.PrintStream;
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.util.List;
+import java.util.Map;
+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.Unit;
+import org.apache.cassandra.io.util.FileUtils;
+import org.apache.cassandra.metrics.CassandraMetricsRegistry;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+import org.apache.cassandra.tools.nodetool.formatter.TableBuilder;
+
+import static java.lang.String.format;
+
+@Command(name = "compactionstats", description = "Print statistics on compactions")
+public class CompactionStats extends NodeToolCmd
+{
+ @Option(title = "human_readable",
+ name = {"-H", "--human-readable"},
+ description = "Display bytes in human readable form, i.e. KiB, MiB, GiB, TiB")
+ private boolean humanReadable = false;
+
+ @Option(title = "vtable_output",
+ name = {"-V", "--vtable"},
+ description = "Display fields matching vtable output")
+ private boolean vtableOutput = false;
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ PrintStream out = probe.output().out;
+ TableBuilder tableBuilder = new TableBuilder();
+ pendingTasksAndConcurrentCompactorsStats(probe, tableBuilder);
+ compactionsStats(probe, tableBuilder);
+ reportCompactionTable(probe.getCompactionManagerProxy().getCompactions(), probe.getCompactionThroughputBytes(), humanReadable, vtableOutput, out, tableBuilder);
+ }
+
+ private void pendingTasksAndConcurrentCompactorsStats(NodeProbe probe, TableBuilder tableBuilder)
+ {
+ Map> pendingTaskNumberByTable =
+ (Map>) probe.getCompactionMetric("PendingTasksByTableName");
+
+ tableBuilder.add("concurrent compactors", Integer.toString(probe.getConcurrentCompactors()));
+ tableBuilder.add("pending tasks", Integer.toString(numPendingTasks(pendingTaskNumberByTable)));
+
+ for (Entry> ksEntry : pendingTaskNumberByTable.entrySet())
+ for (Entry tableEntry : ksEntry.getValue().entrySet())
+ tableBuilder.add(ksEntry.getKey(), tableEntry.getKey(), tableEntry.getValue().toString());
+ }
+
+ private int numPendingTasks(Map> pendingTaskNumberByTable)
+ {
+ int numTotalPendingTasks = 0;
+ for (Entry> ksEntry : pendingTaskNumberByTable.entrySet())
+ for (Entry tableEntry : ksEntry.getValue().entrySet())
+ numTotalPendingTasks += tableEntry.getValue();
+
+ return numTotalPendingTasks;
+ }
+
+ private void compactionsStats(NodeProbe probe, TableBuilder tableBuilder)
+ {
+ CassandraMetricsRegistry.JmxMeterMBean totalCompactionsCompletedMetrics =
+ (CassandraMetricsRegistry.JmxMeterMBean) probe.getCompactionMetric("TotalCompactionsCompleted");
+ tableBuilder.add("compactions completed", String.valueOf(totalCompactionsCompletedMetrics.getCount()));
+
+ CassandraMetricsRegistry.JmxCounterMBean bytesCompacted = (CassandraMetricsRegistry.JmxCounterMBean) probe.getCompactionMetric("BytesCompacted");
+ if (humanReadable)
+ tableBuilder.add("data compacted", FileUtils.stringifyFileSize(Double.parseDouble(Long.toString(bytesCompacted.getCount()))));
+ else
+ tableBuilder.add("data compacted", Long.toString(bytesCompacted.getCount()));
+
+ CassandraMetricsRegistry.JmxCounterMBean compactionsAborted = (CassandraMetricsRegistry.JmxCounterMBean) probe.getCompactionMetric("CompactionsAborted");
+ tableBuilder.add("compactions aborted", Long.toString(compactionsAborted.getCount()));
+
+ CassandraMetricsRegistry.JmxCounterMBean compactionsReduced = (CassandraMetricsRegistry.JmxCounterMBean) probe.getCompactionMetric("CompactionsReduced");
+ tableBuilder.add("compactions reduced", Long.toString(compactionsReduced.getCount()));
+
+ CassandraMetricsRegistry.JmxCounterMBean sstablesDroppedFromCompaction = (CassandraMetricsRegistry.JmxCounterMBean) probe.getCompactionMetric("SSTablesDroppedFromCompaction");
+ tableBuilder.add("sstables dropped from compaction", Long.toString(sstablesDroppedFromCompaction.getCount()));
+
+ NumberFormat formatter = new DecimalFormat("0.00");
+
+ tableBuilder.add("15 minute rate", String.format("%s/minute", formatter.format(totalCompactionsCompletedMetrics.getFifteenMinuteRate() * 60)));
+ tableBuilder.add("mean rate", String.format("%s/hour", formatter.format(totalCompactionsCompletedMetrics.getMeanRate() * 60 * 60)));
+
+ double configured = probe.getStorageService().getCompactionThroughtputMibPerSecAsDouble();
+ tableBuilder.add("compaction throughput (MiB/s)", configured == 0 ? "throttling disabled (0)" : Double.toString(configured));
+ }
+
+ public static void reportCompactionTable(List> compactions, long compactionThroughputInBytes, boolean humanReadable, PrintStream out, TableBuilder table)
+ {
+ reportCompactionTable(compactions, compactionThroughputInBytes, humanReadable, false, out, table);
+ }
+
+ public static void reportCompactionTable(List> compactions, long compactionThroughputInBytes, boolean humanReadable, boolean vtableOutput, PrintStream out, TableBuilder table)
+ {
+ if (compactions.isEmpty())
+ {
+ table.printTo(out);
+ return;
+ }
+
+ long remainingBytes = 0;
+
+ if (vtableOutput)
+ table.add("keyspace", "table", "task id", "completion ratio", "kind", "progress", "sstables", "total", "unit", "target directory");
+ else
+ table.add("id", "compaction type", "keyspace", "table", "completed", "total", "unit", "progress");
+
+ for (Map c : compactions)
+ {
+ long total = Long.parseLong(c.get(CompactionInfo.TOTAL));
+ long completed = Long.parseLong(c.get(CompactionInfo.COMPLETED));
+ String taskType = c.get(CompactionInfo.TASK_TYPE);
+ String keyspace = c.get(CompactionInfo.KEYSPACE);
+ String columnFamily = c.get(CompactionInfo.COLUMNFAMILY);
+ String unit = c.get(CompactionInfo.UNIT);
+ boolean toFileSize = humanReadable && Unit.isFileSize(unit);
+ String[] tables = c.get(CompactionInfo.SSTABLES).split(",");
+ String progressStr = toFileSize ? FileUtils.stringifyFileSize(completed) : Long.toString(completed);
+ String totalStr = toFileSize ? FileUtils.stringifyFileSize(total) : Long.toString(total);
+ String percentComplete = total == 0 ? "n/a" : new DecimalFormat("0.00").format((double) completed / total * 100) + '%';
+ String id = c.get(CompactionInfo.COMPACTION_ID);
+ if (vtableOutput)
+ {
+ String targetDirectory = c.get(CompactionInfo.TARGET_DIRECTORY);
+ table.add(keyspace, columnFamily, id, percentComplete, taskType, progressStr, String.valueOf(tables.length), totalStr, unit, targetDirectory);
+ }
+ else
+ table.add(id, taskType, keyspace, columnFamily, progressStr, totalStr, unit, percentComplete);
+
+ remainingBytes += total - completed;
+ }
+
+ String remainingTime = "n/a";
+ if (compactionThroughputInBytes != 0)
+ {
+ long remainingTimeInSecs = remainingBytes / compactionThroughputInBytes;
+ remainingTime = format("%dh%02dm%02ds", remainingTimeInSecs / 3600, (remainingTimeInSecs % 3600) / 60, (remainingTimeInSecs % 60));
+ }
+
+ table.add("active compaction remaining time", remainingTime);
+ table.printTo(out);
+ }
+
+}
\ No newline at end of file
diff --git a/src/java/org/apache/cassandra/management/api/DataPaths.java b/src/java/org/apache/cassandra/management/api/DataPaths.java
new file mode 100644
index 000000000000..30ebd22a8da0
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/DataPaths.java
@@ -0,0 +1,53 @@
+/*
+ * 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.management.api;
+
+import java.util.ArrayList;
+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.NodeTool.NodeToolCmd;
+import org.apache.cassandra.tools.nodetool.stats.DataPathsHolder;
+import org.apache.cassandra.tools.nodetool.stats.DataPathsPrinter;
+import org.apache.cassandra.tools.nodetool.stats.StatsPrinter;
+
+@Command(name = "datapaths", description = "Print all directories where data of tables are stored")
+public class DataPaths extends NodeToolCmd
+{
+ @Arguments(usage = "[...]", description = "List of table (or keyspace) names")
+ private List tableNames = new ArrayList<>();
+
+ @Option(title = "format", name = {"-F", "--format"}, description = "Output format (json, yaml)")
+ private String outputFormat = "";
+
+ @Override
+ protected void execute(NodeProbe probe)
+ {
+ if (!outputFormat.isEmpty() && !"json".equals(outputFormat) && !"yaml".equals(outputFormat))
+ {
+ throw new IllegalArgumentException("arguments for -F are yaml and json only.");
+ }
+
+ DataPathsHolder holder = new DataPathsHolder(probe, tableNames);
+ StatsPrinter printer = DataPathsPrinter.from(outputFormat);
+ printer.print(holder, probe.output().out);
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/Decommission.java b/src/java/org/apache/cassandra/management/api/Decommission.java
new file mode 100644
index 000000000000..7b5b9d66b405
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/Decommission.java
@@ -0,0 +1,58 @@
+/*
+ * 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.management.api;
+
+import io.airlift.airline.Command;
+import io.airlift.airline.Option;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+@Command(name = "decommission", description = "Decommission the *node I am connecting to*")
+public class Decommission extends NodeToolCmd
+{
+
+ @Option(title = "force",
+ name = {"-f", "--force"},
+ description = "Force decommission of this node even when it reduces the number of replicas to below configured RF")
+ private boolean force = false;
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ try
+ {
+ if (probe.getStorageService().isDecommissioning())
+ {
+ probe.output().out.println("This node is still decommissioning.");
+ return;
+ }
+ if ("DECOMMISSIONED".equals(probe.getStorageService().getBootstrapState()))
+ {
+ probe.output().out.println("Node was already decommissioned.");
+ return;
+ }
+ probe.decommission(force);
+ } catch (InterruptedException e)
+ {
+ throw new RuntimeException("Error decommissioning node", e);
+ } catch (UnsupportedOperationException e)
+ {
+ throw new IllegalStateException("Unsupported operation: " + e.getMessage(), e);
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/DescribeCluster.java b/src/java/org/apache/cassandra/management/api/DescribeCluster.java
new file mode 100644
index 000000000000..0f60b2a96bde
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/DescribeCluster.java
@@ -0,0 +1,158 @@
+/*
+ * 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.management.api;
+
+import java.io.PrintStream;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+
+import com.google.common.collect.ArrayListMultimap;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.locator.DynamicEndpointSnitch;
+import org.apache.cassandra.locator.InetAddressAndPort;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+@Command(name = "describecluster", description = "Print the name, snitch, partitioner and schema version of a cluster")
+public class DescribeCluster extends NodeToolCmd
+{
+ private boolean resolveIp = false;
+ private String keyspace = null;
+ private Collection joiningNodes, leavingNodes, movingNodes, liveNodes, unreachableNodes;
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ PrintStream out = probe.output().out;
+ // display cluster name, snitch and partitioner
+ out.println("Cluster Information:");
+ out.println("\tName: " + probe.getClusterName());
+ String snitch = probe.getEndpointSnitchInfoProxy().getSnitchName();
+ boolean dynamicSnitchEnabled = false;
+ if (snitch.equals(DynamicEndpointSnitch.class.getName()))
+ {
+ snitch = probe.getDynamicEndpointSnitchInfoProxy().getSubsnitchClassName();
+ dynamicSnitchEnabled = true;
+ }
+ out.println("\tSnitch: " + snitch);
+ out.println("\tDynamicEndPointSnitch: " + (dynamicSnitchEnabled ? "enabled" : "disabled"));
+ out.println("\tPartitioner: " + probe.getPartitioner());
+
+ // display schema version for each node
+ out.println("\tSchema versions:");
+ Map> schemaVersions = printPort ? probe.getSpProxy().getSchemaVersionsWithPort() : probe.getSpProxy().getSchemaVersions();
+ for (Map.Entry> entry : schemaVersions.entrySet())
+ {
+ out.printf("\t\t%s: %s%n%n", entry.getKey(), entry.getValue());
+ }
+
+ // Collect status information of all nodes
+ boolean withPort = true;
+ joiningNodes = probe.getJoiningNodes(withPort);
+ leavingNodes = probe.getLeavingNodes(withPort);
+ movingNodes = probe.getMovingNodes(withPort);
+ liveNodes = probe.getLiveNodes(withPort);
+ unreachableNodes = probe.getUnreachableNodes(withPort);
+
+ // Get the list of all keyspaces
+ List keyspaces = probe.getKeyspaces();
+
+ out.println("Stats for all nodes:");
+ out.println("\tLive: " + liveNodes.size());
+ out.println("\tJoining: " + joiningNodes.size());
+ out.println("\tMoving: " + movingNodes.size());
+ out.println("\tLeaving: " + leavingNodes.size());
+ out.println("\tUnreachable: " + unreachableNodes.size());
+
+ Map tokensToEndpoints = probe.getTokenToEndpointMap(withPort);
+ StringBuilder errors = new StringBuilder();
+ Map ownerships = null;
+ try
+ {
+ ownerships = probe.effectiveOwnershipWithPort(keyspace);
+ }
+ catch (IllegalStateException ex)
+ {
+ try
+ {
+ ownerships = probe.getOwnershipWithPort();
+ errors.append("Note: ").append(ex.getMessage()).append("%n");
+ }
+ catch (Exception e)
+ {
+ out.printf("%nError: %s%n", e.getMessage());
+ System.exit(1);
+ }
+ }
+ catch (IllegalArgumentException ex)
+ {
+ out.printf("%nError: %s%n", ex.getMessage());
+ System.exit(1);
+ }
+
+ SortedMap dcs = NodeTool.getOwnershipByDcWithPort(probe, resolveIp, tokensToEndpoints, ownerships);
+
+ out.println("\nData Centers: ");
+ for (Map.Entry dc : dcs.entrySet())
+ {
+ out.print('\t' + dc.getKey());
+
+ ArrayListMultimap hostToTokens = ArrayListMultimap.create();
+ for (HostStatWithPort stat : dc.getValue())
+ hostToTokens.put(stat.endpointWithPort, stat);
+
+ int totalNodes = 0; // total number of nodes in a datacenter
+ int downNodes = 0; // number of down nodes in a datacenter
+
+ for (InetAddressAndPort endpoint : hostToTokens.keySet())
+ {
+ totalNodes++;
+ if (unreachableNodes.contains(endpoint.getHostAddressAndPort()))
+ downNodes++;
+ }
+ out.print(" #Nodes: " + totalNodes);
+ out.println(" #Down: " + downNodes);
+ }
+
+ // display database version for each node
+ out.println("\nDatabase versions:");
+ Map> databaseVersions = probe.getGossProxy().getReleaseVersionsWithPort();
+ for (Map.Entry> entry : databaseVersions.entrySet())
+ {
+ out.printf("\t%s: %s%n%n", entry.getKey(), entry.getValue());
+ }
+
+ out.println("Keyspaces:");
+ for (String keyspaceName : keyspaces)
+ {
+ String replicationInfo = probe.getKeyspaceReplicationInfo(keyspaceName);
+ if (replicationInfo == null)
+ {
+ out.println("something went wrong for keyspace: " + keyspaceName);
+ }
+ out.printf("\t%s -> Replication class: %s%n", keyspaceName, replicationInfo);
+ }
+
+ if (errors.length() != 0)
+ out.printf("%n" + errors);
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/DescribeRing.java b/src/java/org/apache/cassandra/management/api/DescribeRing.java
new file mode 100644
index 000000000000..4ae4d3d87bda
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/DescribeRing.java
@@ -0,0 +1,53 @@
+/*
+ * 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.management.api;
+
+import java.io.IOException;
+import java.io.PrintStream;
+
+import io.airlift.airline.Arguments;
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+import static org.apache.commons.lang3.StringUtils.EMPTY;
+
+@Command(name = "describering", description = "Shows the token ranges info of a given keyspace")
+public class DescribeRing extends NodeToolCmd
+{
+ @Arguments(description = "The keyspace name", required = true)
+ String keyspace = EMPTY;
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ PrintStream out = probe.output().out;
+ out.println("Schema Version:" + probe.getSchemaVersion());
+ out.println("TokenRange: ");
+ try
+ {
+ for (String tokenRangeString : probe.describeRing(keyspace, printPort))
+ {
+ out.println("\t" + tokenRangeString);
+ }
+ } catch (IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/DisableAuditLog.java b/src/java/org/apache/cassandra/management/api/DisableAuditLog.java
new file mode 100644
index 000000000000..45a2271cbf5e
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/DisableAuditLog.java
@@ -0,0 +1,33 @@
+/*
+ * 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.management.api;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+@Command(name = "disableauditlog", description = "Disable the audit log")
+public class DisableAuditLog extends NodeToolCmd
+{
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ probe.disableAuditLog();
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/DisableAutoCompaction.java b/src/java/org/apache/cassandra/management/api/DisableAutoCompaction.java
new file mode 100644
index 000000000000..a1ab39e34a45
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/DisableAutoCompaction.java
@@ -0,0 +1,52 @@
+/*
+ * 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.management.api;
+
+import java.io.IOException;
+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.NodeTool.NodeToolCmd;
+
+@Command(name = "disableautocompaction", description = "Disable autocompaction for the given keyspace and table")
+public class DisableAutoCompaction extends NodeToolCmd
+{
+ @Arguments(usage = "[ ...]", description = "The keyspace followed by one or many tables")
+ private List args = new ArrayList<>();
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ List keyspaces = parseOptionalKeyspace(args, probe);
+ String[] tablenames = parseOptionalTables(args);
+
+ for (String keyspace : keyspaces)
+ {
+ try
+ {
+ probe.disableAutoCompaction(keyspace, tablenames);
+ } catch (IOException e)
+ {
+ throw new RuntimeException("Error occurred during disabling auto-compaction", e);
+ }
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/DisableBackup.java b/src/java/org/apache/cassandra/management/api/DisableBackup.java
new file mode 100644
index 000000000000..d311b0a6a6af
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/DisableBackup.java
@@ -0,0 +1,32 @@
+/*
+ * 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.management.api;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+@Command(name = "disablebackup", description = "Disable incremental backup")
+public class DisableBackup extends NodeToolCmd
+{
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ probe.setIncrementalBackupsEnabled(false);
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/DisableBinary.java b/src/java/org/apache/cassandra/management/api/DisableBinary.java
new file mode 100644
index 000000000000..341c7b91f093
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/DisableBinary.java
@@ -0,0 +1,36 @@
+/*
+ * 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.management.api;
+
+import io.airlift.airline.Command;
+import io.airlift.airline.Option;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+@Command(name = "disablebinary", description = "Disable native transport (binary protocol)")
+public class DisableBinary extends NodeToolCmd
+{
+ @Option(title = "force", name = { "-f", "--force"}, description = "Use -f to interrupt client requests that have already started")
+ private boolean force = false;
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ probe.stopNativeTransport(force);
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/DisableFullQueryLog.java b/src/java/org/apache/cassandra/management/api/DisableFullQueryLog.java
new file mode 100644
index 000000000000..4439126120e2
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/DisableFullQueryLog.java
@@ -0,0 +1,33 @@
+/*
+ * 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.management.api;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+@Command(name = "disablefullquerylog", description = "Disable the full query log")
+public class DisableFullQueryLog extends NodeToolCmd
+{
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ probe.stopFullQueryLogger();
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/DisableGossip.java b/src/java/org/apache/cassandra/management/api/DisableGossip.java
new file mode 100644
index 000000000000..27833b361dbf
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/DisableGossip.java
@@ -0,0 +1,32 @@
+/*
+ * 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.management.api;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+@Command(name = "disablegossip", description = "Disable gossip (effectively marking the node down)")
+public class DisableGossip extends NodeToolCmd
+{
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ probe.stopGossiping();
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/DisableHandoff.java b/src/java/org/apache/cassandra/management/api/DisableHandoff.java
new file mode 100644
index 000000000000..a6d51843f99c
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/DisableHandoff.java
@@ -0,0 +1,32 @@
+/*
+ * 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.management.api;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+@Command(name = "disablehandoff", description = "Disable storing hinted handoffs")
+public class DisableHandoff extends NodeToolCmd
+{
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ probe.disableHintedHandoff();
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/DisableHintsForDC.java b/src/java/org/apache/cassandra/management/api/DisableHintsForDC.java
new file mode 100644
index 000000000000..a4da3dc7d0ff
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/DisableHintsForDC.java
@@ -0,0 +1,42 @@
+/*
+ * 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.management.api;
+
+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.NodeTool;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+@Command(name = "disablehintsfordc", description = "Disable hints for a data center")
+public class DisableHintsForDC extends NodeTool.NodeToolCmd
+{
+ @Arguments(usage = "", description = "The data center to disable")
+ private List args = new ArrayList<>();
+
+ public void execute(NodeProbe probe)
+ {
+ checkArgument(args.size() == 1, "disablehintsfordc requires exactly one data center");
+
+ probe.disableHintsForDC(args.get(0));
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/DisableOldProtocolVersions.java b/src/java/org/apache/cassandra/management/api/DisableOldProtocolVersions.java
new file mode 100644
index 000000000000..cc68dda47f43
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/DisableOldProtocolVersions.java
@@ -0,0 +1,33 @@
+/*
+ * 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.management.api;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool;
+
+@Command(name = "disableoldprotocolversions", description = "Disable old protocol versions")
+public class DisableOldProtocolVersions extends NodeTool.NodeToolCmd
+{
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ probe.disableOldProtocolVersions();
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/Drain.java b/src/java/org/apache/cassandra/management/api/Drain.java
new file mode 100644
index 000000000000..06f0f632d8f3
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/Drain.java
@@ -0,0 +1,41 @@
+/*
+ * 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.management.api;
+
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+@Command(name = "drain", description = "Drain the node (stop accepting writes and flush all tables)")
+public class Drain extends NodeToolCmd
+{
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ try
+ {
+ probe.drain();
+ } catch (IOException | InterruptedException | ExecutionException e)
+ {
+ throw new RuntimeException("Error occurred during flushing", e);
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/DropCIDRGroup.java b/src/java/org/apache/cassandra/management/api/DropCIDRGroup.java
new file mode 100644
index 000000000000..641a2eab7246
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/DropCIDRGroup.java
@@ -0,0 +1,50 @@
+/*
+ * 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.management.api;
+
+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.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * 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")
+public class DropCIDRGroup extends NodeToolCmd
+{
+ @Arguments(usage = "", description = "Requires a cidr group name")
+ private List args = new ArrayList<>();
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ checkArgument(args.size() == 1, "dropcidrgroup command requires a cidr group name");
+
+ String cidrGroupName = args.get(0);
+ probe.dropCidrGroup(cidrGroupName);
+
+ probe.output().out.println("Deleted CIDR group " + cidrGroupName);
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/EnableAuditLog.java b/src/java/org/apache/cassandra/management/api/EnableAuditLog.java
new file mode 100644
index 000000000000..49c8fe14cdf3
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/EnableAuditLog.java
@@ -0,0 +1,87 @@
+/*
+ * 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.management.api;
+
+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.NodeTool.NodeToolCmd;
+
+@Command(name = "enableauditlog", description = "Enable the audit log")
+public class EnableAuditLog extends NodeToolCmd
+{
+ @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")
+ 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")
+ 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")
+ 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")
+ 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")
+ 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")
+ 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")
+ private String excluded_users = null;
+
+ @Option(title = "roll_cycle", name = {"--roll-cycle"}, description = "How often to roll the log file (MINUTELY, HOURLY, DAILY).")
+ private String rollCycle = null;
+
+ @Option(title = "blocking", name = {"--blocking"}, description = "If the queue is full whether to block producers or drop samples [true|false].")
+ 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.")
+ 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.")
+ private long maxLogSize = Long.MIN_VALUE;
+
+ @Option(title = "archive_command", name = {"--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" +
+ " Enable this by setting the audit_logging_options.allow_nodetool_archive_command: true in the config.")
+
+ private String archiveCommand = null;
+
+ @Option(title = "archive_retries", name = {"--max-archive-retries"}, description = "Max number of archive retries.")
+ private int archiveRetries = Integer.MIN_VALUE;
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ Boolean bblocking = null;
+ if (blocking != null)
+ {
+ if (!blocking.equalsIgnoreCase("TRUE") && !blocking.equalsIgnoreCase("FALSE"))
+ throw new IllegalArgumentException("Invalid [" + blocking + "]. Blocking only accepts 'true' or 'false'.");
+ else
+ bblocking = Boolean.parseBoolean(blocking);
+ }
+ probe.enableAuditLog(logger, Collections.EMPTY_MAP, included_keyspaces, excluded_keyspaces, included_categories, excluded_categories, included_users, excluded_users,
+ archiveRetries, bblocking, rollCycle, maxLogSize, maxQueueWeight, archiveCommand);
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/EnableAutoCompaction.java b/src/java/org/apache/cassandra/management/api/EnableAutoCompaction.java
new file mode 100644
index 000000000000..9821b7269b77
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/EnableAutoCompaction.java
@@ -0,0 +1,52 @@
+/*
+ * 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.management.api;
+
+import java.io.IOException;
+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.NodeTool.NodeToolCmd;
+
+@Command(name = "enableautocompaction", description = "Enable autocompaction for the given keyspace and table")
+public class EnableAutoCompaction extends NodeToolCmd
+{
+ @Arguments(usage = "[ ...]", description = "The keyspace followed by one or many tables")
+ private List args = new ArrayList<>();
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ List keyspaces = parseOptionalKeyspace(args, probe);
+ String[] tableNames = parseOptionalTables(args);
+
+ for (String keyspace : keyspaces)
+ {
+ try
+ {
+ probe.enableAutoCompaction(keyspace, tableNames);
+ } catch (IOException e)
+ {
+ throw new RuntimeException("Error occurred during enabling auto-compaction", e);
+ }
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/EnableBackup.java b/src/java/org/apache/cassandra/management/api/EnableBackup.java
new file mode 100644
index 000000000000..aefa8ef830e5
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/EnableBackup.java
@@ -0,0 +1,32 @@
+/*
+ * 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.management.api;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+@Command(name = "enablebackup", description = "Enable incremental backup")
+public class EnableBackup extends NodeToolCmd
+{
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ probe.setIncrementalBackupsEnabled(true);
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/EnableBinary.java b/src/java/org/apache/cassandra/management/api/EnableBinary.java
new file mode 100644
index 000000000000..9e794b5dd91f
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/EnableBinary.java
@@ -0,0 +1,32 @@
+/*
+ * 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.management.api;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+@Command(name = "enablebinary", description = "Reenable native transport (binary protocol)")
+public class EnableBinary extends NodeToolCmd
+{
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ probe.startNativeTransport();
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/EnableFullQueryLog.java b/src/java/org/apache/cassandra/management/api/EnableFullQueryLog.java
new file mode 100644
index 000000000000..b89018387955
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/EnableFullQueryLog.java
@@ -0,0 +1,65 @@
+/*
+ * 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.management.api;
+
+import io.airlift.airline.Command;
+import io.airlift.airline.Option;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+@Command(name = "enablefullquerylog", description = "Enable full query logging, defaults for the options are configured in cassandra.yaml")
+public class EnableFullQueryLog extends NodeToolCmd
+{
+ @Option(title = "roll_cycle", name = {"--roll-cycle"}, description = "How often to roll the log file (MINUTELY, HOURLY, DAILY).")
+ private String rollCycle = null;
+
+ @Option(title = "blocking", name = {"--blocking"}, description = "If the queue is full whether to block producers or drop samples [true|false].")
+ 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.")
+ 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.")
+ 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.")
+ private String path = null;
+
+ @Option(title = "archive_command", name = {"--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" +
+ " Enable this by setting the full_query_logging_options.allow_nodetool_archive_command: true in the config.")
+ private String archiveCommand = null;
+
+ @Option(title = "archive_retries", name = {"--max-archive-retries"}, description = "Max number of archive retries.")
+ private int archiveRetries = Integer.MIN_VALUE;
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ Boolean bblocking = null;
+ if (blocking != null)
+ {
+ if (!blocking.equalsIgnoreCase("TRUE") && !blocking.equalsIgnoreCase("FALSE"))
+ throw new IllegalArgumentException("Invalid [" + blocking + "]. Blocking only accepts 'true' or 'false'.");
+ else
+ bblocking = Boolean.parseBoolean(blocking);
+ }
+ probe.enableFullQueryLogger(path, rollCycle, bblocking, maxQueueWeight, maxLogSize, archiveCommand, archiveRetries);
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/EnableGossip.java b/src/java/org/apache/cassandra/management/api/EnableGossip.java
new file mode 100644
index 000000000000..25d64f45b41f
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/EnableGossip.java
@@ -0,0 +1,32 @@
+/*
+ * 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.management.api;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+@Command(name = "enablegossip", description = "Reenable gossip")
+public class EnableGossip extends NodeToolCmd
+{
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ probe.startGossiping();
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/EnableHandoff.java b/src/java/org/apache/cassandra/management/api/EnableHandoff.java
new file mode 100644
index 000000000000..42c288183945
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/EnableHandoff.java
@@ -0,0 +1,32 @@
+/*
+ * 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.management.api;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+@Command(name = "enablehandoff", description = "Reenable future hints storing on the current node")
+public class EnableHandoff extends NodeToolCmd
+{
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ probe.enableHintedHandoff();
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/EnableHintsForDC.java b/src/java/org/apache/cassandra/management/api/EnableHintsForDC.java
new file mode 100644
index 000000000000..6bf0a2774385
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/EnableHintsForDC.java
@@ -0,0 +1,42 @@
+/*
+ * 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.management.api;
+
+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.NodeTool;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+@Command(name = "enablehintsfordc", description = "Enable hints for a data center that was previsouly disabled")
+public class EnableHintsForDC extends NodeTool.NodeToolCmd
+{
+ @Arguments(usage = "", description = "The data center to enable")
+ private List args = new ArrayList<>();
+
+ public void execute(NodeProbe probe)
+ {
+ checkArgument(args.size() == 1, "enablehintsfordc requires exactly one data center");
+
+ probe.enableHintsForDC(args.get(0));
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/EnableOldProtocolVersions.java b/src/java/org/apache/cassandra/management/api/EnableOldProtocolVersions.java
new file mode 100644
index 000000000000..6c65e652ee60
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/EnableOldProtocolVersions.java
@@ -0,0 +1,34 @@
+/*
+ * 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.management.api;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool;
+
+
+@Command(name = "enableoldprotocolversions", description = "Enable old protocol versions")
+public class EnableOldProtocolVersions extends NodeTool.NodeToolCmd
+{
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ probe.enableOldProtocolVersions();
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/FailureDetectorInfo.java b/src/java/org/apache/cassandra/management/api/FailureDetectorInfo.java
new file mode 100644
index 000000000000..419d1df13962
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/FailureDetectorInfo.java
@@ -0,0 +1,44 @@
+/*
+ * 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.management.api;
+
+import java.util.List;
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.TabularData;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+@Command(name = "failuredetector", description = "Shows the failure detector information for the cluster")
+public class FailureDetectorInfo extends NodeToolCmd
+{
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ TabularData data = probe.getFailureDetectorPhilValues(printPort);
+ probe.output().out.printf("%10s,%16s%n", "Endpoint", "Phi");
+ for (Object o : data.keySet())
+ {
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ CompositeData datum = data.get(((List) o).toArray(new Object[((List) o).size()]));
+ probe.output().out.printf("%10s,%16.8f%n", datum.get("Endpoint"), datum.get("PHI"));
+ }
+ }
+}
+
diff --git a/src/java/org/apache/cassandra/management/api/Flush.java b/src/java/org/apache/cassandra/management/api/Flush.java
new file mode 100644
index 000000000000..107411a06582
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/Flush.java
@@ -0,0 +1,51 @@
+/*
+ * 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.management.api;
+
+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.NodeTool.NodeToolCmd;
+
+@Command(name = "flush", description = "Flush one or more tables")
+public class Flush extends NodeToolCmd
+{
+ @Arguments(usage = "[ ...]", description = "The keyspace followed by one or many tables")
+ private List args = new ArrayList<>();
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ List keyspaces = parseOptionalKeyspace(args, probe);
+ String[] tableNames = parseOptionalTables(args);
+
+ for (String keyspace : keyspaces)
+ {
+ try
+ {
+ probe.forceKeyspaceFlush(keyspace, tableNames);
+ } catch (Exception e)
+ {
+ throw new RuntimeException("Error occurred during flushing", e);
+ }
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/ForceCompact.java b/src/java/org/apache/cassandra/management/api/ForceCompact.java
new file mode 100644
index 000000000000..25d18d0e597c
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/ForceCompact.java
@@ -0,0 +1,72 @@
+/*
+ * 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.management.api;
+
+import java.util.List;
+
+import com.google.common.collect.Lists;
+
+import org.apache.cassandra.management.BaseCommand;
+import org.apache.cassandra.management.CommandUtils;
+import org.apache.cassandra.management.ServiceBridge;
+import picocli.CommandLine;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static org.apache.cassandra.management.CommandUtils.ssProxy;
+import static org.apache.cassandra.tools.NodeTool.NodeToolCmd.parsePartitionKeys;
+
+@CommandLine.Command(name = "forcecompact", description = "Force a (major) compaction on a table")
+public class ForceCompact extends BaseCommand
+{
+ @CommandLine.Parameters(hidden = true, arity = "0", paramLabel = "[ ]",
+ description = { CommandUtils.CASSANDRA_BACKWARD_COMPATIBLE_MARKER,
+ "The keyspace, table, and a list of partition keys ignoring the gc_grace_seconds" })
+ public List args;
+
+ @CommandLine.Parameters(index = "0", arity = "1", description = "The keyspace name to compact")
+ public String keyspace;
+
+ @CommandLine.Parameters(index = "1", arity = "1", description = "The table name to compact")
+ public String table;
+
+ @CommandLine.Parameters(index = "2..*", arity = "1", description = "The partition keys to compact")
+ public String[] keys;
+
+ @Override
+ public void execute(ServiceBridge probe)
+ {
+ args = Lists.asList(keyspace, table, keys);
+ // Check if the input has valid size
+ checkArgument(args.size() >= 3, "forcecompact requires keyspace, table and keys args");
+
+ // We rely on lower-level APIs to check and throw exceptions if the input keyspace or table name are invalid
+ String keyspaceName = args.get(0);
+ String tableName = args.get(1);
+ String[] partitionKeysIgnoreGcGrace = parsePartitionKeys(args);
+
+ try
+ {
+ ssProxy(probe).forceCompactionKeysIgnoringGcGrace(keyspaceName, tableName, partitionKeysIgnoreGcGrace);
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException("Error occurred during compaction keys", e);
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/GarbageCollect.java b/src/java/org/apache/cassandra/management/api/GarbageCollect.java
new file mode 100644
index 000000000000..1ae34b6b7bda
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/GarbageCollect.java
@@ -0,0 +1,65 @@
+/*
+ * 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.management.api;
+
+import java.util.ArrayList;
+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.NodeTool.NodeToolCmd;
+
+@Command(name = "garbagecollect", description = "Remove deleted data from one or more tables")
+public class GarbageCollect extends NodeToolCmd
+{
+ @Arguments(usage = "[ ...]", description = "The keyspace followed by one or many tables")
+ private List args = new ArrayList<>();
+
+ @Option(title = "granularity",
+ name = {"-g", "--granularity"},
+ 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",
+ name = {"-j", "--jobs"},
+ 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 " +
+ "and also remove tombstones.")
+ private int jobs = 1;
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ List keyspaces = parseOptionalKeyspace(args, probe);
+ String[] tableNames = parseOptionalTables(args);
+
+ for (String keyspace : keyspaces)
+ {
+ try
+ {
+ probe.garbageCollect(probe.output().out, tombstoneOption, jobs, keyspace, tableNames);
+ } catch (Exception e)
+ {
+ throw new RuntimeException("Error occurred during garbage collection", e);
+ }
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/GcStats.java b/src/java/org/apache/cassandra/management/api/GcStats.java
new file mode 100644
index 000000000000..9a1458d8e913
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/GcStats.java
@@ -0,0 +1,36 @@
+/*
+ * 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.management.api;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+@Command(name = "gcstats", description = "Print GC Statistics")
+public class GcStats extends NodeToolCmd
+{
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ double[] stats = probe.getAndResetGCStats();
+ double mean = stats[2] / stats[5];
+ double stdev = Math.sqrt((stats[3] / stats[5]) - (mean * mean));
+ probe.output().out.printf("%20s%20s%20s%20s%20s%20s%25s%n", "Interval (ms)", "Max GC Elapsed (ms)", "Total GC Elapsed (ms)", "Stdev GC Elapsed (ms)", "GC Reclaimed (MB)", "Collections", "Direct Memory Bytes");
+ probe.output().out.printf("%20.0f%20.0f%20.0f%20.0f%20.0f%20.0f%25d%n", stats[0], stats[1], stats[2], stdev, stats[4], stats[5], (long)stats[6]);
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/GetAuditLog.java b/src/java/org/apache/cassandra/management/api/GetAuditLog.java
new file mode 100644
index 000000000000..6a64b5304c53
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/GetAuditLog.java
@@ -0,0 +1,56 @@
+/*
+ * 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.management.api;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.audit.AuditLogOptions;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool;
+import org.apache.cassandra.tools.nodetool.formatter.TableBuilder;
+
+@Command(name = "getauditlog", description = "Print configuration of audit log if enabled, otherwise the configuration reflected in cassandra.yaml")
+public class GetAuditLog extends NodeTool.NodeToolCmd
+{
+ @Override
+ protected void execute(NodeProbe probe)
+ {
+ final TableBuilder tableBuilder = new TableBuilder();
+
+ tableBuilder.add("enabled", Boolean.toString(probe.getStorageService().isAuditLogEnabled()));
+
+ final AuditLogOptions options = probe.getAuditLogOptions();
+
+ tableBuilder.add("logger", options.logger.class_name);
+ tableBuilder.add("audit_logs_dir", options.audit_logs_dir);
+ tableBuilder.add("archive_command", options.archive_command);
+ tableBuilder.add("roll_cycle", options.roll_cycle);
+ tableBuilder.add("block", Boolean.toString(options.block));
+ tableBuilder.add("max_log_size", Long.toString(options.max_log_size));
+ tableBuilder.add("max_queue_weight", Integer.toString(options.max_queue_weight));
+ tableBuilder.add("max_archive_retries", Long.toString(options.max_archive_retries));
+ tableBuilder.add("included_keyspaces", options.included_keyspaces);
+ tableBuilder.add("excluded_keyspaces", options.excluded_keyspaces);
+ tableBuilder.add("included_categories", options.included_categories);
+ tableBuilder.add("excluded_categories", options.excluded_categories);
+ tableBuilder.add("included_users", options.included_users);
+ tableBuilder.add("excluded_users", options.excluded_users);
+
+ tableBuilder.printTo(probe.output().out);
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/GetAuthCacheConfig.java b/src/java/org/apache/cassandra/management/api/GetAuthCacheConfig.java
new file mode 100644
index 000000000000..7aaaaabebf3b
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/GetAuthCacheConfig.java
@@ -0,0 +1,47 @@
+/*
+ * 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.management.api;
+
+import io.airlift.airline.Command;
+import io.airlift.airline.Option;
+import org.apache.cassandra.auth.AuthCacheMBean;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool;
+
+@Command(name = "getauthcacheconfig", description = "Get configuration of Auth cache")
+public class GetAuthCacheConfig extends NodeTool.NodeToolCmd
+{
+ @SuppressWarnings("unused")
+ @Option(title = "cache-name",
+ name = {"--cache-name"},
+ description = "Name of Auth cache (required)",
+ required = true)
+ private String cacheName;
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ AuthCacheMBean authCacheMBean = probe.getAuthCacheMBean(cacheName);
+
+ probe.output().out.println("Validity Period: " + authCacheMBean.getValidity());
+ probe.output().out.println("Update Interval: " + authCacheMBean.getUpdateInterval());
+ probe.output().out.println("Max Entries: " + authCacheMBean.getMaxEntries());
+ probe.output().out.println("Active Update: " + authCacheMBean.getActiveUpdate());
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/GetBatchlogReplayTrottle.java b/src/java/org/apache/cassandra/management/api/GetBatchlogReplayTrottle.java
new file mode 100644
index 000000000000..3525bca0b951
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/GetBatchlogReplayTrottle.java
@@ -0,0 +1,33 @@
+/*
+ * 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.management.api;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+@Command(name = "getbatchlogreplaythrottle", description = "Print batchlog replay throttle in KB/s. " +
+ "This is reduced proportionally to the number of nodes in the cluster.")
+public class GetBatchlogReplayTrottle extends NodeToolCmd
+{
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ probe.output().out.println("Batchlog replay throttle: " + probe.getBatchlogReplayThrottle() + " KB/s");
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/GetCIDRGroupsOfIP.java b/src/java/org/apache/cassandra/management/api/GetCIDRGroupsOfIP.java
new file mode 100644
index 000000000000..56b6710f3114
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/GetCIDRGroupsOfIP.java
@@ -0,0 +1,50 @@
+/*
+ * 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.management.api;
+
+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.NodeTool.NodeToolCmd;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * Nodetool command to get CIDR groups(s) of given IP
+ */
+@Command(name = "getcidrgroupsofip", description = "Print CIDR groups associated with given IP")
+public class GetCIDRGroupsOfIP extends NodeToolCmd
+{
+ @Arguments(usage = "", description = "Requires IP address as a string")
+ private List args = new ArrayList<>();
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ PrintStream out = probe.output().out;
+
+ checkArgument(args.size() == 1, "Requires IP address as input");
+
+ String ipStr = args.get(0);
+ probe.printSet(out, "CIDR Groups", probe.getCidrGroupsOfIp(ipStr));
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/GetColumnIndexSize.java b/src/java/org/apache/cassandra/management/api/GetColumnIndexSize.java
new file mode 100644
index 000000000000..ea00974cf533
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/GetColumnIndexSize.java
@@ -0,0 +1,33 @@
+/*
+ * 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.management.api;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+@Command(name = "getcolumnindexsize", description = "Print the granularity of the collation index of rows within a partition in KiB")
+public class GetColumnIndexSize extends NodeToolCmd
+{
+ @Override
+ protected void execute(NodeProbe probe)
+ {
+ probe.output().out.println("Current value for column_index_size: " + probe.getColumnIndexSizeInKB() + " KiB");
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/GetCompactionThreshold.java b/src/java/org/apache/cassandra/management/api/GetCompactionThreshold.java
new file mode 100644
index 000000000000..545dfe6da3f6
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/GetCompactionThreshold.java
@@ -0,0 +1,49 @@
+/*
+ * 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.management.api;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import io.airlift.airline.Arguments;
+import io.airlift.airline.Command;
+import org.apache.cassandra.db.ColumnFamilyStoreMBean;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+@Command(name = "getcompactionthreshold", description = "Print min and max compaction thresholds for a given table")
+public class GetCompactionThreshold extends NodeToolCmd
+{
+ @Arguments(usage = " ", description = "The keyspace with a table")
+ private List args = new ArrayList<>();
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ checkArgument(args.size() == 2, "getcompactionthreshold requires ks and cf args");
+ String ks = args.get(0);
+ String cf = args.get(1);
+
+ ColumnFamilyStoreMBean cfsProxy = probe.getCfsProxy(ks, cf);
+ probe.output().out.println("Current compaction thresholds for " + ks + "/" + cf + ": \n" +
+ " min = " + cfsProxy.getMinimumCompactionThreshold() + ", " +
+ " max = " + cfsProxy.getMaximumCompactionThreshold());
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/GetCompactionThroughput.java b/src/java/org/apache/cassandra/management/api/GetCompactionThroughput.java
new file mode 100644
index 000000000000..ad87bddceda5
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/GetCompactionThroughput.java
@@ -0,0 +1,49 @@
+/*
+ * 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.management.api;
+
+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.NodeTool.NodeToolCmd;
+
+@Command(name = "getcompactionthroughput", description = "Print the MiB/s throughput cap for compaction in the system as a rounded number")
+public class GetCompactionThroughput extends NodeToolCmd
+{
+ @SuppressWarnings("UnusedDeclaration")
+ @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;
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ double throughput = probe.getCompactionThroughputMebibytesAsDouble();
+
+ if (compactionThroughputAsDouble)
+ probe.output().out.println("Current compaction throughput: " + throughput + " MiB/s");
+ else
+ {
+ if (!DoubleMath.isMathematicalInteger(throughput))
+ throw new RuntimeException("Use the -d flag to quiet this error and get the exact throughput in MiB/s");
+
+ probe.output().out.println("Current compaction throughput: " + probe.getCompactionThroughput() + " MB/s");
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/GetConcurrency.java b/src/java/org/apache/cassandra/management/api/GetConcurrency.java
new file mode 100644
index 000000000000..5c50493a6eab
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/GetConcurrency.java
@@ -0,0 +1,50 @@
+/*
+ * 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.management.api;
+
+import java.util.ArrayList;
+import java.util.List;
+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.NodeTool.NodeToolCmd;
+
+@Command(name = "getconcurrency", description = "Get maximum concurrency for processing stages")
+public class GetConcurrency extends NodeToolCmd
+{
+ @Arguments(title = "[stage-names]",
+ usage = "[stage-names]",
+ description = "optional list of stage names, otherwise display all stages")
+ private List args = new ArrayList<>();
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ probe.output().out.printf("%-25s%16s%16s%n", "Stage", "CorePoolSize", "MaximumPoolSize");
+ probe.getMaximumPoolSizes(args).entrySet().stream()
+ .sorted(Map.Entry.comparingByKey())
+ .forEach(entry ->
+ probe.output().out.printf("%-25s%16d%16d%n",
+ entry.getKey(),
+ entry.getValue().get(0),
+ entry.getValue().get(1)));
+
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/GetConcurrentCompactors.java b/src/java/org/apache/cassandra/management/api/GetConcurrentCompactors.java
new file mode 100644
index 000000000000..129799c81a6f
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/GetConcurrentCompactors.java
@@ -0,0 +1,33 @@
+/*
+ * 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.management.api;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+@Command(name = "getconcurrentcompactors", description = "Get the number of concurrent compactors in the system.")
+public class GetConcurrentCompactors extends NodeToolCmd
+{
+ protected void execute(NodeProbe probe)
+ {
+ probe.output().out.println("Current concurrent compactors in the system is: \n" +
+ probe.getConcurrentCompactors());
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/GetConcurrentViewBuilders.java b/src/java/org/apache/cassandra/management/api/GetConcurrentViewBuilders.java
new file mode 100644
index 000000000000..92587131f6cc
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/GetConcurrentViewBuilders.java
@@ -0,0 +1,33 @@
+/*
+ * 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.management.api;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+@Command(name = "getconcurrentviewbuilders", description = "Get the number of concurrent view builders in the system")
+public class GetConcurrentViewBuilders extends NodeToolCmd
+{
+ protected void execute(NodeProbe probe)
+ {
+ probe.output().out.println("Current number of concurrent view builders in the system is: \n" +
+ probe.getConcurrentViewBuilders());
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/GetDefaultKeyspaceRF.java b/src/java/org/apache/cassandra/management/api/GetDefaultKeyspaceRF.java
new file mode 100644
index 000000000000..1ff4c4f3ff34
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/GetDefaultKeyspaceRF.java
@@ -0,0 +1,32 @@
+/*
+ * 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.management.api;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool;
+
+@Command(name = "getdefaultrf", description = "Gets default keyspace replication factor.")
+public class GetDefaultKeyspaceRF extends NodeTool.NodeToolCmd
+{
+ protected void execute(NodeProbe probe)
+ {
+ probe.output().out.println(probe.getDefaultKeyspaceReplicationFactor());
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/GetEndpoints.java b/src/java/org/apache/cassandra/management/api/GetEndpoints.java
new file mode 100644
index 000000000000..c6cb3e09b5d3
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/GetEndpoints.java
@@ -0,0 +1,61 @@
+/*
+ * 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.management.api;
+
+import java.net.InetAddress;
+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.NodeTool.NodeToolCmd;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+@Command(name = "getendpoints", description = "Print the end points that owns the key")
+public class GetEndpoints extends NodeToolCmd
+{
+ @Arguments(usage = " ", description = "The keyspace, the table, and the partition key for which we need to find the endpoint")
+ private List args = new ArrayList<>();
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ checkArgument(args.size() == 3, "getendpoints requires keyspace, table and partition key arguments");
+ String ks = args.get(0);
+ String table = args.get(1);
+ String key = args.get(2);
+
+ if (printPort)
+ {
+ for (String endpoint : probe.getEndpointsWithPort(ks, table, key))
+ {
+ probe.output().out.println(endpoint);
+ }
+ }
+ else
+ {
+ List endpoints = probe.getEndpoints(ks, table, key);
+ for (InetAddress endpoint : endpoints)
+ {
+ probe.output().out.println(endpoint.getHostAddress());
+ }
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/GetFullQueryLog.java b/src/java/org/apache/cassandra/management/api/GetFullQueryLog.java
new file mode 100644
index 000000000000..c349bd2692ca
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/GetFullQueryLog.java
@@ -0,0 +1,48 @@
+/*
+ * 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.management.api;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.fql.FullQueryLoggerOptions;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+import org.apache.cassandra.tools.nodetool.formatter.TableBuilder;
+
+@Command(name = "getfullquerylog", description = "Print configuration of fql if enabled, otherwise the configuration reflected in cassandra.yaml")
+public class GetFullQueryLog extends NodeToolCmd
+{
+ protected void execute(NodeProbe probe)
+ {
+ final TableBuilder tableBuilder = new TableBuilder();
+
+ tableBuilder.add("enabled", Boolean.toString(probe.getStorageService().isFullQueryLogEnabled()));
+
+ final FullQueryLoggerOptions options = probe.getFullQueryLoggerOptions();
+
+ tableBuilder.add("log_dir", options.log_dir);
+ tableBuilder.add("archive_command", options.archive_command);
+ tableBuilder.add("roll_cycle", options.roll_cycle);
+ tableBuilder.add("block", Boolean.toString(options.block));
+ tableBuilder.add("max_log_size", Long.toString(options.max_log_size));
+ tableBuilder.add("max_queue_weight", Integer.toString(options.max_queue_weight));
+ tableBuilder.add("max_archive_retries", Long.toString(options.max_archive_retries));
+
+ tableBuilder.printTo(probe.output().out);
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/GetInterDCStreamThroughput.java b/src/java/org/apache/cassandra/management/api/GetInterDCStreamThroughput.java
new file mode 100644
index 000000000000..3c646c762bb4
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/GetInterDCStreamThroughput.java
@@ -0,0 +1,87 @@
+/*
+ * 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.management.api;
+
+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.NodeTool.NodeToolCmd;
+
+@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")
+public class GetInterDCStreamThroughput extends NodeToolCmd
+{
+ @SuppressWarnings("UnusedDeclaration")
+ @Option(name = { "-e", "--entire-sstable-throughput" }, description = "Print entire SSTable streaming throughput in MiB/s")
+ private boolean entireSSTableThroughput;
+
+ @SuppressWarnings("UnusedDeclaration")
+ @Option(name = { "-m", "--mib" }, description = "Print the throughput cap for inter-datacenter streaming in MiB/s")
+ private boolean interDCStreamThroughputMiB;
+
+ @SuppressWarnings("UnusedDeclaration")
+ @Option(name = { "-d", "--precise-mbit" }, description = "Print the throughput cap for inter-datacenter streaming in precise Mbits (double)")
+ private boolean interDCStreamThroughputDoubleMbit;
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ int throughput;
+ double throughputInDouble;
+
+ if (entireSSTableThroughput)
+ {
+ if (interDCStreamThroughputDoubleMbit || interDCStreamThroughputMiB)
+ throw new IllegalArgumentException("You cannot use more than one flag with this command");
+
+ throughputInDouble = probe.getEntireSSTableInterDCStreamThroughput();
+ probe.output().out.printf("Current entire SSTable inter-datacenter stream throughput: %s%n",
+ throughputInDouble > 0 ? throughputInDouble + " MiB/s" : "unlimited");
+ }
+ else if (interDCStreamThroughputMiB)
+ {
+ if (interDCStreamThroughputDoubleMbit)
+ throw new IllegalArgumentException("You cannot use more than one flag with this command");
+
+ throughputInDouble = probe.getInterDCStreamThroughputMibAsDouble();
+ probe.output().out.printf("Current inter-datacenter stream throughput: %s%n",
+ throughputInDouble > 0 ? throughputInDouble + " MiB/s" : "unlimited");
+
+ }
+ else if (interDCStreamThroughputDoubleMbit)
+ {
+ throughputInDouble = probe.getInterDCStreamThroughputAsDouble();
+ probe.output().out.printf("Current stream throughput: %s%n",
+ throughputInDouble > 0 ? throughputInDouble + " Mb/s" : "unlimited");
+ }
+ else
+ {
+ throughputInDouble = probe.getInterDCStreamThroughputAsDouble();
+ throughput = probe.getInterDCStreamThroughput();
+
+ if (throughput <= 0)
+ probe.output().out.printf("Current inter-datacenter stream throughput: unlimited%n");
+ else if (DoubleMath.isMathematicalInteger(throughputInDouble))
+ probe.output().out.printf(throughputInDouble + "Current inter-datacenter stream throughput: %s%n", throughput + " Mb/s");
+ else
+ throw new RuntimeException("Use the -d flag to quiet this error and get the exact throughput in megabits/s");
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/GetLoggingLevels.java b/src/java/org/apache/cassandra/management/api/GetLoggingLevels.java
new file mode 100644
index 000000000000..feb2d5c4b8ee
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/GetLoggingLevels.java
@@ -0,0 +1,37 @@
+/*
+ * 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.management.api;
+
+import java.util.Map;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+@Command(name = "getlogginglevels", description = "Get the runtime logging levels")
+public class GetLoggingLevels extends NodeToolCmd
+{
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ // what if some one set a very long logger name? 50 space may not be enough...
+ probe.output().out.printf("%n%-50s%10s%n", "Logger Name", "Log Level");
+ for (Map.Entry entry : probe.getLoggingLevels().entrySet())
+ probe.output().out.printf("%-50s%10s%n", entry.getKey(), entry.getValue());
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/GetMaxHintWindow.java b/src/java/org/apache/cassandra/management/api/GetMaxHintWindow.java
new file mode 100644
index 000000000000..ce8165c2c0ad
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/GetMaxHintWindow.java
@@ -0,0 +1,33 @@
+/*
+ * 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.management.api;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool;
+
+@Command(name = "getmaxhintwindow", description = "Print the max hint window in ms")
+public class GetMaxHintWindow extends NodeTool.NodeToolCmd
+{
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ probe.output().out.println("Current max hint window: " + probe.getMaxHintWindow() + " ms");
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/GetSSTables.java b/src/java/org/apache/cassandra/management/api/GetSSTables.java
new file mode 100644
index 000000000000..bb6a99dacb90
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/GetSSTables.java
@@ -0,0 +1,70 @@
+/*
+ * 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.management.api;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+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.NodeTool.NodeToolCmd;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+@Command(name = "getsstables", description = "Print the sstable filenames that own the key")
+public class GetSSTables extends NodeToolCmd
+{
+ @Option(title = "hex_format",
+ name = {"-hf", "--hex-format"},
+ description = "Specify the key in hexadecimal string format")
+ 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)")
+ private boolean showLevels = false;
+
+ @Arguments(usage = " ", description = "The keyspace, the column family, and the key")
+ private List args = new ArrayList<>();
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ checkArgument(args.size() == 3, "getsstables requires ks, cf and key args");
+ String ks = args.get(0);
+ String cf = args.get(1);
+ String key = args.get(2);
+
+ if (showLevels && probe.isLeveledCompaction(ks, cf))
+ {
+ Map> sstables = probe.getSSTablesWithLevel(ks, cf, key, hexFormat);
+ for (Integer level : sstables.keySet())
+ for (String sstable : sstables.get(level))
+ probe.output().out.println(level + ": " + sstable);
+ } else
+ {
+ List sstables = probe.getSSTables(ks, cf, key, hexFormat);
+ for (String sstable : sstables)
+ {
+ probe.output().out.println(sstable);
+ }
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/GetSeeds.java b/src/java/org/apache/cassandra/management/api/GetSeeds.java
new file mode 100644
index 000000000000..675834df6d5f
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/GetSeeds.java
@@ -0,0 +1,43 @@
+/*
+ * 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.management.api;
+
+import java.util.List;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+@Command(name = "getseeds", description = "Get the currently in use seed node IP list excluding the node IP")
+public class GetSeeds extends NodeToolCmd
+{
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ List seedList = probe.getSeeds();
+ if (seedList.isEmpty())
+ {
+ probe.output().out.println("Seed node list does not contain any remote node IPs");
+ }
+ else
+ {
+ probe.output().out.println("Current list of seed node IPs, excluding the current node's IP: " + String.join(" ", seedList));
+ }
+
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/GetSnapshotThrottle.java b/src/java/org/apache/cassandra/management/api/GetSnapshotThrottle.java
new file mode 100644
index 000000000000..d55113edb5c6
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/GetSnapshotThrottle.java
@@ -0,0 +1,36 @@
+/*
+ * 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.management.api;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+@Command(name = "getsnapshotthrottle", description = "Print the snapshot_links_per_second throttle for snapshot/clearsnapshot")
+public class GetSnapshotThrottle extends NodeToolCmd
+{
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ long throttle = probe.getSnapshotLinksPerSecond();
+ if (throttle > 0)
+ System.out.println("Current snapshot throttle: " + throttle + " links/s");
+ else
+ System.out.println("Snapshot throttle is disabled");
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/GetStreamThroughput.java b/src/java/org/apache/cassandra/management/api/GetStreamThroughput.java
new file mode 100644
index 000000000000..0727ba4fce80
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/GetStreamThroughput.java
@@ -0,0 +1,86 @@
+/*
+ * 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.management.api;
+
+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.NodeTool.NodeToolCmd;
+
+@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")
+public class GetStreamThroughput extends NodeToolCmd
+{
+ @SuppressWarnings("UnusedDeclaration")
+ @Option(name = { "-e", "--entire-sstable-throughput" }, description = "Print entire SSTable streaming throughput in MiB/s")
+ private boolean entireSSTableThroughput;
+
+ @SuppressWarnings("UnusedDeclaration")
+ @Option(name = { "-m", "--mib" }, description = "Print the throughput cap for streaming in MiB/s")
+ private boolean streamThroughputMiB;
+
+ @SuppressWarnings("UnusedDeclaration")
+ @Option(name = { "-d", "--precise-mbit" }, description = "Print the throughput cap for streaming in precise Mbits (double)")
+ private boolean streamThroughputDoubleMbit;
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ int throughput;
+ double throughputInDouble;
+
+ if (entireSSTableThroughput)
+ {
+ if (streamThroughputDoubleMbit || streamThroughputMiB)
+ throw new IllegalArgumentException("You cannot use more than one flag with this command");
+
+ throughputInDouble = probe.getEntireSSTableStreamThroughput();
+ probe.output().out.printf("Current entire SSTable stream throughput: %s%n",
+ throughputInDouble > 0 ? throughputInDouble + " MiB/s" : "unlimited");
+ }
+ else if (streamThroughputMiB)
+ {
+ if (streamThroughputDoubleMbit)
+ throw new IllegalArgumentException("You cannot use more than one flag with this command");
+
+ throughputInDouble = probe.getStreamThroughputMibAsDouble();
+ probe.output().out.printf("Current stream throughput: %s%n",
+ throughputInDouble > 0 ? throughputInDouble + " MiB/s" : "unlimited");
+ }
+ else if (streamThroughputDoubleMbit)
+ {
+ throughputInDouble = probe.getStreamThroughputAsDouble();
+ probe.output().out.printf("Current stream throughput: %s%n",
+ throughputInDouble > 0 ? throughputInDouble + " Mb/s" : "unlimited");
+ }
+ else
+ {
+ throughputInDouble = probe.getStreamThroughputAsDouble();
+ throughput = probe.getStreamThroughput();
+
+ if (throughput <= 0)
+ probe.output().out.printf("Current stream throughput: unlimited%n");
+ else if (DoubleMath.isMathematicalInteger(throughputInDouble))
+ probe.output().out.printf("Current stream throughput: %s%n", throughput + " Mb/s");
+ else
+ throw new RuntimeException("Use the -d flag to quiet this error and get the exact throughput in megabits/s");
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/GetTimeout.java b/src/java/org/apache/cassandra/management/api/GetTimeout.java
new file mode 100644
index 000000000000..101c774930ba
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/GetTimeout.java
@@ -0,0 +1,51 @@
+/*
+ * 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.management.api;
+
+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.NodeTool.NodeToolCmd;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+@Command(name = "gettimeout", description = "Print the timeout of the given type in ms")
+public class GetTimeout extends NodeToolCmd
+{
+ public static final String TIMEOUT_TYPES = "read, range, write, counterwrite, cascontention, truncate, internodeconnect, internodeuser, internodestreaminguser, misc (general rpc_timeout_in_ms)";
+
+ @Arguments(usage = "", description = "The timeout type, one of (" + TIMEOUT_TYPES + ")")
+ private List args = new ArrayList<>();
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ checkArgument(args.size() == 1, "gettimeout requires a timeout type, one of (" + TIMEOUT_TYPES + ")");
+ try
+ {
+ probe.output().out.println("Current timeout for type " + args.get(0) + ": " + probe.getTimeout(args.get(0)) + " ms");
+ } catch (Exception e)
+ {
+ throw new IllegalArgumentException(e.getMessage());
+ }
+
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/GetTraceProbability.java b/src/java/org/apache/cassandra/management/api/GetTraceProbability.java
new file mode 100644
index 000000000000..7f808d240b43
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/GetTraceProbability.java
@@ -0,0 +1,32 @@
+/*
+ * 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.management.api;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+@Command(name = "gettraceprobability", description = "Print the current trace probability value")
+public class GetTraceProbability extends NodeToolCmd
+{
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ probe.output().out.println("Current trace probability: " + probe.getTraceProbability());
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/GossipInfo.java b/src/java/org/apache/cassandra/management/api/GossipInfo.java
new file mode 100644
index 000000000000..2122af8ae4dd
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/GossipInfo.java
@@ -0,0 +1,36 @@
+/*
+ * 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.management.api;
+
+import io.airlift.airline.Command;
+import io.airlift.airline.Option;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+@Command(name = "gossipinfo", description = "Shows the gossip information for the cluster")
+public class GossipInfo extends NodeToolCmd
+{
+ @Option(title = "resolve_ip", name = {"-r", "--resolve-ip"}, description = "Show node domain names instead of IPs")
+ private boolean resolveIp = false;
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ probe.output().out.println(probe.getGossipInfo(printPort, resolveIp));
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/HostStat.java b/src/java/org/apache/cassandra/management/api/HostStat.java
new file mode 100644
index 000000000000..5f96f50de281
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/HostStat.java
@@ -0,0 +1,41 @@
+/*
+ * 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.management.api;
+
+import java.net.InetAddress;
+
+public class HostStat
+{
+ public final InetAddress endpoint;
+ public final boolean resolveIp;
+ public final Float owns;
+ public final String token;
+
+ public HostStat(String token, InetAddress endpoint, boolean resolveIp, Float owns)
+ {
+ this.token = token;
+ this.endpoint = endpoint;
+ this.resolveIp = resolveIp;
+ this.owns = owns;
+ }
+
+ public String ipOrDns()
+ {
+ return resolveIp ? endpoint.getHostName() : endpoint.getHostAddress();
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/HostStatWithPort.java b/src/java/org/apache/cassandra/management/api/HostStatWithPort.java
new file mode 100644
index 000000000000..02ef377415e3
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/HostStatWithPort.java
@@ -0,0 +1,47 @@
+/*
+ * 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.management.api;
+
+import org.apache.cassandra.locator.InetAddressAndPort;
+
+public class HostStatWithPort extends HostStat
+{
+ public final InetAddressAndPort endpointWithPort;
+
+ public HostStatWithPort(String token, InetAddressAndPort endpoint, boolean resolveIp, Float owns)
+ {
+ super(token, endpoint.getAddress(), resolveIp, owns);
+ this.endpointWithPort = endpoint;
+ }
+
+ public String ipOrDns()
+ {
+ return ipOrDns(true);
+ }
+
+ public String ipOrDns(boolean withPort)
+ {
+ if (!withPort)
+ return super.ipOrDns();
+
+ return resolveIp ?
+ endpointWithPort.getAddress().getHostName() + ':' + endpointWithPort.getPort() :
+ endpointWithPort.getHostAddressAndPort();
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/Import.java b/src/java/org/apache/cassandra/management/api/Import.java
new file mode 100644
index 000000000000..f8a70659b472
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/Import.java
@@ -0,0 +1,119 @@
+/*
+ * 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.management.api;
+
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+import com.google.common.collect.Lists;
+
+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.NodeTool.NodeToolCmd;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+@Command(name = "import", description = "Import new SSTables to the system")
+public class Import extends NodeToolCmd
+{
+ @Arguments(usage = " ...", description = "The keyspace, table name and directories to import sstables from")
+ private List args = new ArrayList<>();
+
+ @Option(title = "keep_level",
+ name = {"-l", "--keep-level"},
+ description = "Keep the level on the new sstables")
+ private boolean keepLevel = false;
+
+ @Option(title = "keep_repaired",
+ name = {"-r", "--keep-repaired"},
+ description = "Keep any repaired information from the sstables")
+ private boolean keepRepaired = false;
+
+ @Option(title = "no_verify_sstables",
+ name = {"-v", "--no-verify"},
+ description = "Don't verify new sstables")
+ private boolean noVerify = false;
+
+ @Option(title = "no_verify_tokens",
+ name = {"-t", "--no-tokens"},
+ description = "Don't verify that all tokens in the new sstable are owned by the current node")
+ private boolean noVerifyTokens = false;
+
+ @Option(title = "no_invalidate_caches",
+ name = {"-c", "--no-invalidate-caches"},
+ description = "Don't invalidate the row cache when importing")
+ private boolean noInvalidateCaches = false;
+
+ @Option(title = "quick",
+ name = {"-q", "--quick"},
+ 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;
+
+ @Option(title = "extended_verify",
+ name = {"-e", "--extended-verify"},
+ description = "Run an extended verify, verifying all values in the new sstables")
+ private boolean extendedVerify = false;
+
+ @Option(title = "copy_data",
+ name = {"-p", "--copy-data"},
+ description = "Copy data from source directories instead of moving them")
+ private boolean copyData = false;
+
+ @Option(title = "require_index_components",
+ name = {"-ri", "--require-index-components"},
+ description = "Require existing index components for SSTables with attached indexes")
+ private boolean failOnMissingIndex = false;
+
+ @Option(title = "no_index_validation",
+ name = {"-niv", "--no-index-validation"},
+ description = "Skip SSTable-attached index checksum validation")
+ private boolean noIndexValidation = false;
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ checkArgument(args.size() >= 3, "import requires keyspace, table name and directories");
+
+ if (quick)
+ {
+ probe.output().out.println("Doing a quick import - skipping sstable verification and row cache invalidation");
+ noVerifyTokens = true;
+ noInvalidateCaches = true;
+ noVerify = true;
+ extendedVerify = false;
+ noIndexValidation = true;
+ }
+ List srcPaths = Lists.newArrayList(args.subList(2, args.size()));
+ List failedDirs = probe.importNewSSTables(args.get(0), args.get(1), new HashSet<>(srcPaths), !keepLevel,
+ !keepRepaired, !noVerify, !noVerifyTokens, !noInvalidateCaches,
+ extendedVerify, copyData, failOnMissingIndex, !noIndexValidation);
+ if (!failedDirs.isEmpty())
+ {
+ PrintStream err = probe.output().err;
+ err.println("Some directories failed to import, check server logs for details:");
+ for (String directory : failedDirs)
+ err.println(directory);
+ System.exit(1);
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/Info.java b/src/java/org/apache/cassandra/management/api/Info.java
new file mode 100644
index 000000000000..5035ce523e19
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/Info.java
@@ -0,0 +1,208 @@
+/*
+ * 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.management.api;
+
+import java.io.PrintStream;
+import java.lang.management.MemoryUsage;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map.Entry;
+import javax.management.InstanceNotFoundException;
+
+import io.airlift.airline.Command;
+import io.airlift.airline.Option;
+import org.apache.cassandra.db.ColumnFamilyStoreMBean;
+import org.apache.cassandra.io.util.FileUtils;
+import org.apache.cassandra.service.CacheServiceMBean;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+@Command(name = "info", description = "Print node information (uptime, load, ...)")
+public class Info extends NodeToolCmd
+{
+ @Option(name = {"-T", "--tokens"}, description = "Display all tokens")
+ private boolean tokens = false;
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ boolean gossipInitialized = probe.isGossipRunning();
+
+ PrintStream out = probe.output().out;
+ out.printf("%-23s: %s%n", "ID", probe.getLocalHostId());
+ out.printf("%-23s: %s%n", "Gossip active", gossipInitialized);
+ out.printf("%-23s: %s%n", "Native Transport active", probe.isNativeTransportRunning());
+ out.printf("%-23s: %s%n", "Load", probe.getLoadString());
+ out.printf("%-23s: %s%n", "Uncompressed load", probe.getUncompressedLoadString());
+
+ if (gossipInitialized)
+ out.printf("%-23s: %s%n", "Generation No", probe.getCurrentGenerationNumber());
+ else
+ out.printf("%-23s: %s%n", "Generation No", 0);
+
+ // Uptime
+ long secondsUp = probe.getUptime() / 1000;
+ out.printf("%-23s: %d%n", "Uptime (seconds)", secondsUp);
+
+ // Memory usage
+ MemoryUsage heapUsage = probe.getHeapMemoryUsage();
+ double memUsed = (double) heapUsage.getUsed() / (1024 * 1024);
+ double memMax = (double) heapUsage.getMax() / (1024 * 1024);
+ out.printf("%-23s: %.2f / %.2f%n", "Heap Memory (MB)", memUsed, memMax);
+ try
+ {
+ out.printf("%-23s: %.2f%n", "Off Heap Memory (MB)", getOffHeapMemoryUsed(probe));
+ }
+ catch (RuntimeException e)
+ {
+ // offheap-metrics introduced in 2.1.3 - older versions do not have the appropriate mbeans
+ if (!(e.getCause() instanceof InstanceNotFoundException))
+ throw e;
+ }
+
+ // Data Center/Rack
+ out.printf("%-23s: %s%n", "Data Center", probe.getDataCenter());
+ out.printf("%-23s: %s%n", "Rack", probe.getRack());
+
+ // Exceptions
+ out.printf("%-23s: %s%n", "Exceptions", probe.getStorageMetric("Exceptions"));
+
+ CacheServiceMBean cacheService = probe.getCacheServiceMBean();
+
+ // Key Cache: Hits, Requests, RecentHitRate, SavePeriodInSeconds
+ out.printf("%-23s: entries %d, size %s, capacity %s, %d hits, %d requests, %.3f recent hit rate, %d save period in seconds%n",
+ "Key Cache",
+ probe.getCacheMetric("KeyCache", "Entries"),
+ FileUtils.stringifyFileSize((long) probe.getCacheMetric("KeyCache", "Size")),
+ FileUtils.stringifyFileSize((long) probe.getCacheMetric("KeyCache", "Capacity")),
+ probe.getCacheMetric("KeyCache", "Hits"),
+ probe.getCacheMetric("KeyCache", "Requests"),
+ probe.getCacheMetric("KeyCache", "HitRate"),
+ cacheService.getKeyCacheSavePeriodInSeconds());
+
+ // Row Cache: Hits, Requests, RecentHitRate, SavePeriodInSeconds
+ out.printf("%-23s: entries %d, size %s, capacity %s, %d hits, %d requests, %.3f recent hit rate, %d save period in seconds%n",
+ "Row Cache",
+ probe.getCacheMetric("RowCache", "Entries"),
+ FileUtils.stringifyFileSize((long) probe.getCacheMetric("RowCache", "Size")),
+ FileUtils.stringifyFileSize((long) probe.getCacheMetric("RowCache", "Capacity")),
+ probe.getCacheMetric("RowCache", "Hits"),
+ probe.getCacheMetric("RowCache", "Requests"),
+ probe.getCacheMetric("RowCache", "HitRate"),
+ cacheService.getRowCacheSavePeriodInSeconds());
+
+ // Counter Cache: Hits, Requests, RecentHitRate, SavePeriodInSeconds
+ out.printf("%-23s: entries %d, size %s, capacity %s, %d hits, %d requests, %.3f recent hit rate, %d save period in seconds%n",
+ "Counter Cache",
+ probe.getCacheMetric("CounterCache", "Entries"),
+ FileUtils.stringifyFileSize((long) probe.getCacheMetric("CounterCache", "Size")),
+ FileUtils.stringifyFileSize((long) probe.getCacheMetric("CounterCache", "Capacity")),
+ probe.getCacheMetric("CounterCache", "Hits"),
+ probe.getCacheMetric("CounterCache", "Requests"),
+ probe.getCacheMetric("CounterCache", "HitRate"),
+ cacheService.getCounterCacheSavePeriodInSeconds());
+
+ // Chunk Cache: Hits, Requests, RecentHitRate, SavePeriodInSeconds
+ try
+ {
+ out.printf("%-23s: entries %d, size %s, capacity %s, %d misses, %d requests, %.3f recent hit rate, %.3f %s miss latency%n",
+ "Chunk Cache",
+ probe.getCacheMetric("ChunkCache", "Entries"),
+ FileUtils.stringifyFileSize((long) probe.getCacheMetric("ChunkCache", "Size")),
+ FileUtils.stringifyFileSize((long) probe.getCacheMetric("ChunkCache", "Capacity")),
+ probe.getCacheMetric("ChunkCache", "Misses"),
+ probe.getCacheMetric("ChunkCache", "Requests"),
+ probe.getCacheMetric("ChunkCache", "HitRate"),
+ probe.getCacheMetric("ChunkCache", "MissLatency"),
+ probe.getCacheMetric("ChunkCache", "MissLatencyUnit"));
+ }
+ catch (RuntimeException e)
+ {
+ if (!(e.getCause() instanceof InstanceNotFoundException))
+ throw e;
+
+ // Chunk cache is not on.
+ }
+
+ // network Cache: capacity, size
+ try
+ {
+ out.printf("%-23s: size %s, overflow size: %s, capacity %s%n", "Network Cache",
+ FileUtils.stringifyFileSize((long) probe.getBufferPoolMetric("networking", "Size")),
+ FileUtils.stringifyFileSize((long) probe.getBufferPoolMetric("networking", "OverflowSize")),
+ FileUtils.stringifyFileSize((long) probe.getBufferPoolMetric("networking", "Capacity")));
+ }
+ catch (RuntimeException e)
+ {
+ if (!(e.getCause() instanceof InstanceNotFoundException))
+ throw e;
+
+ // network cache is not on.
+ }
+
+ // Global table stats
+ out.printf("%-23s: %s%%%n", "Percent Repaired", probe.getColumnFamilyMetric(null, null, "PercentRepaired"));
+
+ // check if node is already joined, before getting tokens, since it throws exception if not.
+ if (probe.isJoined())
+ {
+ // Tokens
+ List tokens = probe.getTokens();
+ if (tokens.size() == 1 || this.tokens)
+ for (String token : tokens)
+ out.printf("%-23s: %s%n", "Token", token);
+ else
+ out.printf("%-23s: (invoke with -T/--tokens to see all %d tokens)%n", "Token",
+ tokens.size());
+ }
+ else
+ {
+ out.printf("%-23s: (node is not joined to the cluster)%n", "Token");
+ }
+
+ out.printf("%-23s: %s%n", "Bootstrap state", probe.getStorageService().getBootstrapState());
+ out.printf("%-23s: %s%n", "Bootstrap failed", probe.getStorageService().isBootstrapFailed());
+ out.printf("%-23s: %s%n", "Decommissioning", probe.getStorageService().isDecommissioning());
+ out.printf("%-23s: %s%n", "Decommission failed", probe.getStorageService().isDecommissionFailed());
+ }
+
+ /**
+ * Returns the total off heap memory used in MiB.
+ * @return the total off heap memory used in MiB.
+ */
+ private static double getOffHeapMemoryUsed(NodeProbe probe)
+ {
+ long offHeapMemUsedInBytes = 0;
+ // get a list of column family stores
+ Iterator> cfamilies = probe.getColumnFamilyStoreMBeanProxies();
+
+ while (cfamilies.hasNext())
+ {
+ Entry entry = cfamilies.next();
+ String keyspaceName = entry.getKey();
+ String cfName = entry.getValue().getTableName();
+
+ offHeapMemUsedInBytes += (Long) probe.getColumnFamilyMetric(keyspaceName, cfName, "MemtableOffHeapSize");
+ offHeapMemUsedInBytes += (Long) probe.getColumnFamilyMetric(keyspaceName, cfName, "BloomFilterOffHeapMemoryUsed");
+ offHeapMemUsedInBytes += (Long) probe.getColumnFamilyMetric(keyspaceName, cfName, "IndexSummaryOffHeapMemoryUsed");
+ offHeapMemUsedInBytes += (Long) probe.getColumnFamilyMetric(keyspaceName, cfName, "CompressionMetadataOffHeapMemoryUsed");
+ }
+
+ return offHeapMemUsedInBytes / (1024d * 1024);
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/InvalidateCIDRPermissionsCache.java b/src/java/org/apache/cassandra/management/api/InvalidateCIDRPermissionsCache.java
new file mode 100644
index 000000000000..9b8bd5bbf150
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/InvalidateCIDRPermissionsCache.java
@@ -0,0 +1,56 @@
+/*
+ * 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.management.api;
+
+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.NodeTool.NodeToolCmd;
+
+/**
+ * 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")
+public class InvalidateCIDRPermissionsCache extends NodeToolCmd
+{
+ @Arguments(usage = "[...]", description = "List of roles to invalidate. By default, all roles")
+ private List args = new ArrayList<>();
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ if (args.isEmpty())
+ {
+ probe.invalidateCidrPermissionsCache("");
+ probe.output().out.println("Invalidated CIDR permissions cache");
+ }
+ else
+ {
+ for (String roleName : args)
+ {
+ if (probe.invalidateCidrPermissionsCache(roleName))
+ probe.output().out.println("Invalidated the role " + roleName + " from CIDR permissions cache");
+ else
+ probe.output().out.println("Not found role " + roleName + " in CIDR permissions cache, nothing to invalidate");
+ }
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/InvalidateCounterCache.java b/src/java/org/apache/cassandra/management/api/InvalidateCounterCache.java
new file mode 100644
index 000000000000..0335de8b9ddd
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/InvalidateCounterCache.java
@@ -0,0 +1,32 @@
+/*
+ * 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.management.api;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+@Command(name = "invalidatecountercache", description = "Invalidate the counter cache")
+public class InvalidateCounterCache extends NodeToolCmd
+{
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ probe.invalidateCounterCache();
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/InvalidateCredentialsCache.java b/src/java/org/apache/cassandra/management/api/InvalidateCredentialsCache.java
new file mode 100644
index 000000000000..9908892530ab
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/InvalidateCredentialsCache.java
@@ -0,0 +1,49 @@
+/*
+ * 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.management.api;
+
+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.NodeTool.NodeToolCmd;
+
+@Command(name = "invalidatecredentialscache", description = "Invalidate the credentials cache")
+public class InvalidateCredentialsCache extends NodeToolCmd
+{
+ @Arguments(usage = "[...]", description = "List of roles to invalidate. By default, all roles")
+ private List args = new ArrayList<>();
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ if (args.isEmpty())
+ {
+ probe.invalidateCredentialsCache();
+ }
+ else
+ {
+ for (String roleName : args)
+ {
+ probe.invalidateCredentialsCache(roleName);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/java/org/apache/cassandra/management/api/InvalidateJmxPermissionsCache.java b/src/java/org/apache/cassandra/management/api/InvalidateJmxPermissionsCache.java
new file mode 100644
index 000000000000..934567bada75
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/InvalidateJmxPermissionsCache.java
@@ -0,0 +1,48 @@
+/*
+ * 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.management.api;
+
+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.NodeTool.NodeToolCmd;
+
+@Command(name = "invalidatejmxpermissionscache", description = "Invalidate the JMX permissions cache")
+public class InvalidateJmxPermissionsCache extends NodeToolCmd
+{
+ @Arguments(usage = "[...]", description = "List of roles to invalidate. By default, all roles")
+ private List args = new ArrayList<>();
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ if (args.isEmpty())
+ {
+ probe.invalidateJmxPermissionsCache();
+ } else
+ {
+ for (String roleName : args)
+ {
+ probe.invalidateJmxPermissionsCache(roleName);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/java/org/apache/cassandra/management/api/InvalidateKeyCache.java b/src/java/org/apache/cassandra/management/api/InvalidateKeyCache.java
new file mode 100644
index 000000000000..d7323fb854c8
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/InvalidateKeyCache.java
@@ -0,0 +1,32 @@
+/*
+ * 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.management.api;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+@Command(name = "invalidatekeycache", description = "Invalidate the key cache")
+public class InvalidateKeyCache extends NodeToolCmd
+{
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ probe.invalidateKeyCache();
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/InvalidateNetworkPermissionsCache.java b/src/java/org/apache/cassandra/management/api/InvalidateNetworkPermissionsCache.java
new file mode 100644
index 000000000000..4da168e72d1d
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/InvalidateNetworkPermissionsCache.java
@@ -0,0 +1,49 @@
+/*
+ * 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.management.api;
+
+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.NodeTool.NodeToolCmd;
+
+@Command(name = "invalidatenetworkpermissionscache", description = "Invalidate the network permissions cache")
+public class InvalidateNetworkPermissionsCache extends NodeToolCmd
+{
+ @Arguments(usage = "[...]", description = "List of roles to invalidate. By default, all roles")
+ private List args = new ArrayList<>();
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ if (args.isEmpty())
+ {
+ probe.invalidateNetworkPermissionsCache();
+ }
+ else
+ {
+ for (String roleName : args)
+ {
+ probe.invalidateNetworkPermissionsCache(roleName);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/java/org/apache/cassandra/management/api/InvalidatePermissionsCache.java b/src/java/org/apache/cassandra/management/api/InvalidatePermissionsCache.java
new file mode 100644
index 000000000000..0529742669a6
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/InvalidatePermissionsCache.java
@@ -0,0 +1,188 @@
+/*
+ * 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.management.api;
+
+import java.util.ArrayList;
+import java.util.List;
+
+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.FunctionResource;
+import org.apache.cassandra.auth.JMXResource;
+import org.apache.cassandra.auth.RoleResource;
+import org.apache.cassandra.exceptions.ConfigurationException;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+@Command(name = "invalidatepermissionscache", description = "Invalidate the permissions cache")
+public class InvalidatePermissionsCache extends NodeToolCmd
+{
+ @Arguments(usage = "[]", description = "A role for which permissions to specified resources need to be invalidated")
+ private List args = new ArrayList<>();
+
+ // Data Resources
+ @Option(title = "all-keyspaces",
+ name = {"--all-keyspaces"},
+ description = "Invalidate permissions for 'ALL KEYSPACES'")
+ private boolean allKeyspaces;
+
+ @Option(title = "keyspace",
+ name = {"--keyspace"},
+ description = "Keyspace to invalidate permissions for")
+ private String keyspace;
+
+ @Option(title = "all-tables",
+ name = {"--all-tables"},
+ description = "Invalidate permissions for 'ALL TABLES'")
+ private boolean allTables;
+
+ @Option(title = "table",
+ name = {"--table"},
+ description = "Table to invalidate permissions for (you must specify --keyspace for using this option)")
+ private String table;
+
+ // Roles Resources
+ @Option(title = "all-roles",
+ name = {"--all-roles"},
+ description = "Invalidate permissions for 'ALL ROLES'")
+ private boolean allRoles;
+
+ @Option(title = "role",
+ name = {"--role"},
+ description = "Role to invalidate permissions for")
+ private String role;
+
+ // Functions Resources
+ @Option(title = "all-functions",
+ name = {"--all-functions"},
+ description = "Invalidate permissions for 'ALL FUNCTIONS'")
+ private boolean allFunctions;
+
+ @Option(title = "functions-in-keyspace",
+ name = {"--functions-in-keyspace"},
+ description = "Keyspace to invalidate permissions for")
+ private String functionsInKeyspace;
+
+ @Option(title = "function",
+ name = {"--function"},
+ 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])")
+ private String function;
+
+ // MBeans Resources
+ @Option(title = "all-mbeans",
+ name = {"--all-mbeans"},
+ description = "Invalidate permissions for 'ALL MBEANS'")
+ private boolean allMBeans;
+
+ @Option(title = "mbean",
+ name = {"--mbean"},
+ description = "MBean to invalidate permissions for")
+ private String mBean;
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ if (args.isEmpty())
+ {
+ checkArgument(!allKeyspaces && StringUtils.isEmpty(keyspace) && StringUtils.isEmpty(table)
+ && !allRoles && StringUtils.isEmpty(role)
+ && !allFunctions && StringUtils.isEmpty(functionsInKeyspace) && StringUtils.isEmpty(function)
+ && !allMBeans && StringUtils.isEmpty(mBean),
+ "No resource options allowed without a being specified");
+
+ probe.invalidatePermissionsCache();
+ }
+ else
+ {
+ checkArgument(args.size() == 1,
+ "A single is only supported / you have a typo in the resource options spelling");
+ List resourceNames = new ArrayList<>();
+
+ // Data Resources
+ if (allKeyspaces)
+ resourceNames.add(DataResource.root().getName());
+
+ if (allTables)
+ if (StringUtils.isNotEmpty(keyspace))
+ resourceNames.add(DataResource.allTables(keyspace).getName());
+ else
+ throw new IllegalArgumentException("--all-tables option should be passed along with --keyspace option");
+
+ if (StringUtils.isNotEmpty(table))
+ if (StringUtils.isNotEmpty(keyspace))
+ resourceNames.add(DataResource.table(keyspace, table).getName());
+ else
+ throw new IllegalArgumentException("--table option should be passed along with --keyspace option");
+
+ if (StringUtils.isNotEmpty(keyspace) && !allTables && StringUtils.isEmpty(table))
+ resourceNames.add(DataResource.keyspace(keyspace).getName());
+
+ // Roles Resources
+ if (allRoles)
+ resourceNames.add(RoleResource.root().getName());
+
+ if (StringUtils.isNotEmpty(role))
+ resourceNames.add(RoleResource.role(role).getName());
+
+ // Function Resources
+ if (allFunctions)
+ resourceNames.add(FunctionResource.root().getName());
+
+ if (StringUtils.isNotEmpty(function))
+ if (StringUtils.isNotEmpty(functionsInKeyspace))
+ resourceNames.add(constructFunctionResource(functionsInKeyspace, function));
+ else
+ throw new IllegalArgumentException("--function option should be passed along with --functions-in-keyspace option");
+ else
+ if (StringUtils.isNotEmpty(functionsInKeyspace))
+ resourceNames.add(FunctionResource.keyspace(functionsInKeyspace).getName());
+
+ // MBeans Resources
+ if (allMBeans)
+ resourceNames.add(JMXResource.root().getName());
+
+ if (StringUtils.isNotEmpty(mBean))
+ resourceNames.add(JMXResource.mbean(mBean).getName());
+
+ String roleName = args.get(0);
+
+ if (resourceNames.isEmpty())
+ throw new IllegalArgumentException("No resource options specified");
+
+ for (String resourceName : resourceNames)
+ probe.invalidatePermissionsCache(roleName, resourceName);
+ }
+ }
+
+ private String constructFunctionResource(String functionsInKeyspace, String function) {
+ try
+ {
+ return FunctionResource.fromName("functions/" + functionsInKeyspace + '/' + function).getName();
+ } catch (ConfigurationException e)
+ {
+ throw new IllegalArgumentException("An error was encountered when looking up function definition: " + e.getMessage());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/java/org/apache/cassandra/management/api/InvalidateRolesCache.java b/src/java/org/apache/cassandra/management/api/InvalidateRolesCache.java
new file mode 100644
index 000000000000..17b7c5a30565
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/InvalidateRolesCache.java
@@ -0,0 +1,50 @@
+/*
+ * 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.management.api;
+
+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.NodeTool.NodeToolCmd;
+
+@Command(name = "invalidaterolescache", description = "Invalidate the roles cache")
+public class InvalidateRolesCache extends NodeToolCmd
+{
+
+ @Arguments(usage = "[...]", description = "List of roles to invalidate. By default, all roles")
+ private List args = new ArrayList<>();
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ if (args.isEmpty())
+ {
+ probe.invalidateRolesCache();
+ }
+ else
+ {
+ for (String roleName : args)
+ {
+ probe.invalidateRolesCache(roleName);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/java/org/apache/cassandra/management/api/InvalidateRowCache.java b/src/java/org/apache/cassandra/management/api/InvalidateRowCache.java
new file mode 100644
index 000000000000..911ac29a4524
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/InvalidateRowCache.java
@@ -0,0 +1,32 @@
+/*
+ * 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.management.api;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+@Command(name = "invalidaterowcache", description = "Invalidate the row cache")
+public class InvalidateRowCache extends NodeToolCmd
+{
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ probe.invalidateRowCache();
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/JmxConnectionMixin.java b/src/java/org/apache/cassandra/management/api/JmxConnectionMixin.java
new file mode 100644
index 000000000000..16c59f895179
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/JmxConnectionMixin.java
@@ -0,0 +1,130 @@
+/*
+ * 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.management.api;
+
+import java.io.IOException;
+import java.util.List;
+import javax.inject.Inject;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Throwables;
+
+import org.apache.cassandra.management.BaseCommand;
+import org.apache.cassandra.management.ServiceBridge;
+import org.apache.cassandra.tools.INodeProbeFactory;
+import org.apache.cassandra.tools.Output;
+import picocli.CommandLine;
+
+import static java.lang.Integer.parseInt;
+import static org.apache.cassandra.tools.NodeTool.NodeToolCmd.promptAndReadPassword;
+import static org.apache.cassandra.tools.NodeTool.NodeToolCmd.readUserPasswordFromFile;
+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.
+ */
+public class JmxConnectionMixin
+{
+ public static final String MIXIN_KEY = "jmx";
+
+ @CommandLine.Option(names = { "-h", "--host" }, description = "Node hostname or ip address")
+ public String host = "127.0.0.1";
+
+ @CommandLine.Option(names = { "-p", "--port" }, description = "Remote jmx agent port number")
+ public String port = "7199";
+
+ @CommandLine.Option(names = { "-u", "--username" }, description = "Remote jmx agent username")
+ public String username = EMPTY;
+
+ @CommandLine.Option(names = { "-pw", "--password" }, description = "Remote jmx agent password")
+ public String password = EMPTY;
+
+ @CommandLine.Option(names = { "-pwf", "--password-file" }, description = "Path to the JMX password file")
+ public String passwordFilePath = EMPTY;
+
+ @CommandLine.Option(names = { "-pp", "--print-port" }, description = "Operate in 4.0 mode with hosts disambiguated by port number")
+ public boolean printPort = false;
+
+ @Inject
+ private INodeProbeFactory nodeProbeFactory;
+ @Inject
+ private Output output;
+
+ /**
+ * 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(CommandLine.ParseResult parseResult)
+ {
+ List parsedCommands = parseResult.asCommandLineList();
+ int start = indexOfLastSubcommandWithSameParent(parsedCommands);
+ CommandLine.Model.CommandSpec lastParent = parsedCommands.get(start).getCommandSpec();
+ CommandLine.Model.CommandSpec jmx = lastParent.mixins().get(MIXIN_KEY);
+ if (jmx == null)
+ throw new CommandLine.InitializationException("No JmxConnect mixin found in the command hierarchy");
+
+ if (lastParent.userObject() instanceof BaseCommand)
+ ((BaseCommand) lastParent.userObject()).setBridge(((JmxConnectionMixin) jmx.userObject()).init(lastParent));
+ return new CommandLine.RunLast().execute(parseResult);
+ }
+
+ private static int indexOfLastSubcommandWithSameParent(List 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 start;
+ }
+
+ /**
+ * Initialize the JMX connection to the Cassandra node.
+ * @param spec The command specification to be executed after the initialization.
+ * @return The ServiceBridge instance to interact with the Cassandra node.
+ */
+ private ServiceBridge init(CommandLine.Model.CommandSpec spec)
+ {
+ try
+ {
+ if (isNotEmpty(username)) {
+ if (isNotEmpty(passwordFilePath))
+ password = readUserPasswordFromFile(username, passwordFilePath);
+
+ if (isEmpty(password))
+ password = promptAndReadPassword();
+ }
+
+ return username.isEmpty() ? nodeProbeFactory.create(host, parseInt(port))
+ : nodeProbeFactory.create(host, parseInt(port), username, password);
+ }
+ catch (IOException | SecurityException e)
+ {
+ Throwable rootCause = Throwables.getRootCause(e);
+ output.err.printf("nodetool: Failed to connect to '%s:%s' - %s: '%s'.%n", host, port,
+ rootCause.getClass().getSimpleName(), rootCause.getMessage());
+ throw new CommandLine.ExecutionException(spec.commandLine(), "Failed to connect to JMX", e);
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/Join.java b/src/java/org/apache/cassandra/management/api/Join.java
new file mode 100644
index 000000000000..7e91e1255138
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/Join.java
@@ -0,0 +1,43 @@
+/*
+ * 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.management.api;
+
+import java.io.IOException;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+import static com.google.common.base.Preconditions.checkState;
+
+@Command(name = "join", description = "Join the ring")
+public class Join extends NodeToolCmd
+{
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ checkState(!probe.isJoined(), "This node has already joined the ring.");
+ try
+ {
+ probe.joinRing();
+ } catch (IOException e)
+ {
+ throw new RuntimeException("Error during joining the ring", e);
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/ListPendingHints.java b/src/java/org/apache/cassandra/management/api/ListPendingHints.java
new file mode 100644
index 000000000000..9d3e25b7d242
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/ListPendingHints.java
@@ -0,0 +1,98 @@
+/*
+ * 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.management.api;
+
+import java.net.UnknownHostException;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+import java.util.Map;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.hints.PendingHintsInfo;
+import org.apache.cassandra.locator.EndpointSnitchInfoMBean;
+import org.apache.cassandra.locator.InetAddressAndPort;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool;
+import org.apache.cassandra.tools.nodetool.formatter.TableBuilder;
+
+@Command(name = "listpendinghints", description = "Print all pending hints that this node has")
+public class ListPendingHints extends NodeTool.NodeToolCmd
+{
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ List> pendingHints = probe.listPendingHints();
+ if(pendingHints.isEmpty())
+ {
+ probe.output().out.println("This node does not have any pending hints");
+ }
+ else
+ {
+ Map endpointMap = probe.getHostIdToEndpointWithPort();
+ Map simpleStates = probe.getSimpleStatesWithPort();
+ EndpointSnitchInfoMBean epSnitchInfo = probe.getEndpointSnitchInfoProxy();
+
+ DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss,SSS");
+ TableBuilder tableBuilder = new TableBuilder();
+
+ tableBuilder.add("Host ID", "Address", "Rack", "DC", "Status", "Total files", "Newest", "Oldest");
+ for (Map hintInfo : pendingHints)
+ {
+ String endpoint = hintInfo.get(PendingHintsInfo.HOST_ID);
+ String totalFiles = hintInfo.get(PendingHintsInfo.TOTAL_FILES);
+ LocalDateTime newest = Instant.ofEpochMilli(Long.parseLong(hintInfo.get(PendingHintsInfo.NEWEST_TIMESTAMP)))
+ .atZone(ZoneId.of("UTC"))
+ .toLocalDateTime();
+ LocalDateTime oldest = Instant.ofEpochMilli(Long.parseLong(hintInfo.get(PendingHintsInfo.OLDEST_TIMESTAMP)))
+ .atZone(ZoneId.of("UTC"))
+ .toLocalDateTime();
+ String address = endpointMap.get(endpoint);
+ String rack = null;
+ String dc = null;
+ String status = null;
+ try
+ {
+ rack = epSnitchInfo.getRack(address);
+ dc = epSnitchInfo.getDatacenter(address);
+ status = simpleStates.getOrDefault(InetAddressAndPort.getByName(address).toString(),
+ "Unknown");
+ }
+ catch (UnknownHostException e)
+ {
+ rack = rack != null ? rack : "Unknown";
+ dc = dc != null ? dc : "Unknown";
+ status = "Unknown";
+ }
+
+ tableBuilder.add(endpoint,
+ address,
+ rack,
+ dc,
+ status,
+ String.valueOf(totalFiles),
+ dtf.format(newest),
+ dtf.format(oldest));
+ }
+ tableBuilder.printTo(probe.output().out);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/java/org/apache/cassandra/management/api/ListSnapshots.java b/src/java/org/apache/cassandra/management/api/ListSnapshots.java
new file mode 100644
index 000000000000..99db1a5b83f5
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/ListSnapshots.java
@@ -0,0 +1,97 @@
+/*
+ * 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.management.api;
+
+import java.io.PrintStream;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.management.openmbean.TabularData;
+
+import io.airlift.airline.Command;
+import io.airlift.airline.Option;
+import org.apache.cassandra.io.util.FileUtils;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+import org.apache.cassandra.tools.nodetool.formatter.TableBuilder;
+
+@Command(name = "listsnapshots", description = "Lists all the snapshots along with the size on disk and true size. True size is the total size of all SSTables which are not backed up to disk. Size on disk is total size of the snapshot on disk. Total TrueDiskSpaceUsed does not make any SSTable deduplication.")
+public class ListSnapshots extends NodeToolCmd
+{
+ @Option(title = "no_ttl",
+ name = { "-nt", "--no-ttl" },
+ description = "Skip snapshots with TTL")
+ private boolean noTTL = false;
+
+ @Option(title = "ephemeral",
+ name = { "-e", "--ephemeral" },
+ description = "Include ephememeral snapshots")
+ private boolean includeEphemeral = false;
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ PrintStream out = probe.output().out;
+ try
+ {
+ out.println("Snapshot Details: ");
+
+ Map options = new HashMap<>();
+ options.put("no_ttl", Boolean.toString(noTTL));
+ options.put("include_ephemeral", Boolean.toString(includeEphemeral));
+
+ final Map snapshotDetails = probe.getSnapshotDetails(options);
+ if (snapshotDetails.isEmpty())
+ {
+ out.println("There are no snapshots");
+ return;
+ }
+
+ final long trueSnapshotsSize = probe.trueSnapshotsSize();
+ TableBuilder table = new TableBuilder();
+ // display column names only once
+ final List indexNames = snapshotDetails.entrySet().iterator().next().getValue().getTabularType().getIndexNames();
+
+ if (includeEphemeral)
+ table.add(indexNames.toArray(new String[indexNames.size()]));
+ else
+ table.add(indexNames.subList(0, indexNames.size() - 1).toArray(new String[indexNames.size() - 1]));
+
+ for (final Map.Entry snapshotDetail : snapshotDetails.entrySet())
+ {
+ Set> values = snapshotDetail.getValue().keySet();
+ for (Object eachValue : values)
+ {
+ final List> value = (List>) eachValue;
+ if (includeEphemeral)
+ table.add(value.toArray(new String[value.size()]));
+ else
+ table.add(value.subList(0, value.size() - 1).toArray(new String[value.size() - 1]));
+ }
+ }
+ table.printTo(out);
+
+ out.println("\nTotal TrueDiskSpaceUsed: " + FileUtils.stringifyFileSize(trueSnapshotsSize) + '\n');
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException("Error during list snapshot", e);
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/Move.java b/src/java/org/apache/cassandra/management/api/Move.java
new file mode 100644
index 000000000000..9f73c8b2a12b
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/Move.java
@@ -0,0 +1,46 @@
+/*
+ * 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.management.api;
+
+import java.io.IOException;
+
+import io.airlift.airline.Arguments;
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+import static org.apache.commons.lang3.StringUtils.EMPTY;
+
+@Command(name = "move", description = "Move node on the token ring to a new token")
+public class Move extends NodeToolCmd
+{
+ @Arguments(usage = "", description = "The new token.", required = true)
+ private String newToken = EMPTY;
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ try
+ {
+ probe.move(newToken);
+ } catch (IOException e)
+ {
+ throw new RuntimeException("Error during moving node", e);
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/NetStats.java b/src/java/org/apache/cassandra/management/api/NetStats.java
new file mode 100644
index 000000000000..af6c679be753
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/NetStats.java
@@ -0,0 +1,172 @@
+/*
+ * 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.management.api;
+
+import java.io.PrintStream;
+import java.util.Set;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import io.airlift.airline.Command;
+import io.airlift.airline.Option;
+import org.apache.cassandra.io.util.FileUtils;
+import org.apache.cassandra.locator.InetAddressAndPort;
+import org.apache.cassandra.net.MessagingServiceMBean;
+import org.apache.cassandra.streaming.ProgressInfo;
+import org.apache.cassandra.streaming.SessionInfo;
+import org.apache.cassandra.streaming.StreamState;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+@Command(name = "netstats", description = "Print network information on provided host (connecting node by default)")
+public class NetStats extends NodeToolCmd
+{
+ @Option(title = "human_readable",
+ name = {"-H", "--human-readable"},
+ description = "Display bytes in human readable form, i.e. KiB, MiB, GiB, TiB")
+ private boolean humanReadable = false;
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ PrintStream out = probe.output().out;
+ out.printf("Mode: %s%n", probe.getOperationMode());
+ Set statuses = probe.getStreamStatus();
+ if (statuses.isEmpty())
+ out.println("Not sending any streams.");
+ for (StreamState status : statuses)
+ {
+ out.printf("%s %s%n", status.streamOperation.getDescription(), status.planId.toString());
+ for (SessionInfo info : status.sessions)
+ {
+ out.printf(" %s", InetAddressAndPort.toString(info.peer, printPort));
+ // print private IP when it is used
+ if (!info.peer.equals(info.connecting))
+ {
+ out.printf(" (using %s)", InetAddressAndPort.toString(info.connecting, printPort));
+ }
+ out.printf("%n");
+ if (!info.receivingSummaries.isEmpty())
+ {
+ printReceivingSummaries(out, info, humanReadable);
+ }
+ if (!info.sendingSummaries.isEmpty())
+ {
+ printSendingSummaries(out, info, humanReadable);
+ }
+ }
+ }
+
+ if (!probe.isStarting())
+ {
+ out.printf("Read Repair Statistics:%nAttempted: %d%nMismatch (Blocking): %d%nMismatch (Background): %d%n", probe.getReadRepairAttempted(), probe.getReadRepairRepairedBlocking(), probe.getReadRepairRepairedBackground());
+
+ MessagingServiceMBean ms = probe.getMessagingServiceProxy();
+ out.printf("%-25s", "Pool Name");
+ out.printf("%10s", "Active");
+ out.printf("%10s", "Pending");
+ out.printf("%15s", "Completed");
+ out.printf("%10s%n", "Dropped");
+
+ int pending;
+ long completed;
+ long dropped;
+
+ pending = 0;
+ for (int n : ms.getLargeMessagePendingTasksWithPort().values())
+ pending += n;
+ completed = 0;
+ for (long n : ms.getLargeMessageCompletedTasksWithPort().values())
+ completed += n;
+ dropped = 0;
+ for (long n : ms.getLargeMessageDroppedTasksWithPort().values())
+ dropped += n;
+ out.printf("%-25s%10s%10s%15s%10s%n", "Large messages", "n/a", pending, completed, dropped);
+
+ pending = 0;
+ for (int n : ms.getSmallMessagePendingTasksWithPort().values())
+ pending += n;
+ completed = 0;
+ for (long n : ms.getSmallMessageCompletedTasksWithPort().values())
+ completed += n;
+ dropped = 0;
+ for (long n : ms.getSmallMessageDroppedTasksWithPort().values())
+ dropped += n;
+ out.printf("%-25s%10s%10s%15s%10s%n", "Small messages", "n/a", pending, completed, dropped);
+
+ pending = 0;
+ for (int n : ms.getGossipMessagePendingTasksWithPort().values())
+ pending += n;
+ completed = 0;
+ for (long n : ms.getGossipMessageCompletedTasksWithPort().values())
+ completed += n;
+ dropped = 0;
+ for (long n : ms.getGossipMessageDroppedTasksWithPort().values())
+ dropped += n;
+ out.printf("%-25s%10s%10s%15s%10s%n", "Gossip messages", "n/a", pending, completed, dropped);
+ }
+ }
+
+ @VisibleForTesting
+ public void printReceivingSummaries(PrintStream out, SessionInfo info, boolean printHumanReadable)
+ {
+ long totalFilesToReceive = info.getTotalFilesToReceive();
+ long totalBytesToReceive = info.getTotalSizeToReceive();
+ long totalFilesReceived = info.getTotalFilesReceived();
+ long totalSizeReceived = info.getTotalSizeReceived();
+ double percentageFilesReceived = ((double) totalFilesReceived / totalFilesToReceive) * 100;
+ double percentageSizesReceived = ((double) totalSizeReceived / totalBytesToReceive) * 100;
+
+ out.printf(" Receiving %d files, %s total. Already received %d files (%.2f%%), %s total (%.2f%%)%n",
+ totalFilesToReceive,
+ printHumanReadable ? FileUtils.stringifyFileSize(totalBytesToReceive) : Long.toString(totalBytesToReceive) + " bytes",
+ totalFilesReceived,
+ percentageFilesReceived,
+ printHumanReadable ? FileUtils.stringifyFileSize(totalSizeReceived) : Long.toString(totalSizeReceived) + " bytes",
+ percentageSizesReceived);
+
+ for (ProgressInfo progress : info.getReceivingFiles())
+ {
+ out.printf(" %s%n", progress.toString(printPort));
+ }
+ }
+
+ @VisibleForTesting
+ public void printSendingSummaries(PrintStream out, SessionInfo info, boolean printHumanReadable)
+ {
+ long totalFilesToSend = info.getTotalFilesToSend();
+ long totalSizeToSend = info.getTotalSizeToSend();
+ long totalFilesSent = info.getTotalFilesSent();
+ long totalSizeSent = info.getTotalSizeSent();
+ double percentageFilesSent = ((double) totalFilesSent / totalFilesToSend) * 100;
+ double percentageSizeSent = ((double) totalSizeSent / totalSizeToSend) * 100;
+
+ out.printf(" Sending %d files, %s total. Already sent %d files (%.2f%%), %s total (%.2f%%)%n",
+ totalFilesToSend,
+ printHumanReadable ? FileUtils.stringifyFileSize(totalSizeToSend) : Long.toString(totalSizeToSend) + " bytes",
+ totalFilesSent,
+ percentageFilesSent,
+ printHumanReadable ? FileUtils.stringifyFileSize(totalSizeSent) : Long.toString(totalSizeSent) + " bytes",
+ percentageSizeSent);
+
+ for (ProgressInfo progress : info.getSendingFiles())
+ {
+ out.printf(" %s%n", progress.toString(printPort));
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/PauseHandoff.java b/src/java/org/apache/cassandra/management/api/PauseHandoff.java
new file mode 100644
index 000000000000..8380063655eb
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/PauseHandoff.java
@@ -0,0 +1,32 @@
+/*
+ * 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.management.api;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+@Command(name = "pausehandoff", description = "Pause hints delivery process")
+public class PauseHandoff extends NodeToolCmd
+{
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ probe.pauseHintsDelivery();
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/ProfileLoad.java b/src/java/org/apache/cassandra/management/api/ProfileLoad.java
new file mode 100644
index 000000000000..8ac783d57af6
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/ProfileLoad.java
@@ -0,0 +1,187 @@
+/*
+ * 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.management.api;
+
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Collectors;
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.OpenDataException;
+
+import com.google.common.collect.Lists;
+
+import io.airlift.airline.Arguments;
+import io.airlift.airline.Command;
+import io.airlift.airline.Option;
+import org.apache.cassandra.metrics.Sampler.SamplerType;
+import org.apache.cassandra.metrics.SamplingManager;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+import org.apache.cassandra.utils.Pair;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static org.apache.commons.lang3.StringUtils.join;
+
+@Command(name = "profileload", description = "Low footprint profiling of activity for a period of time")
+public class ProfileLoad extends NodeToolCmd
+{
+ @Arguments(usage = " ", description = "The keyspace, column family name, and duration in milliseconds (Default: 10000)")
+ private List args = new ArrayList<>();
+
+ @Option(name = "-s", description = "Capacity of the sampler, higher for more accuracy (Default: 256)")
+ private int capacity = 256;
+
+ @Option(name = "-k", description = "Number of the top samples to list (Default: 10)")
+ private int topCount = 10;
+
+ @Option(name = "-a", description = "Comma separated list of samplers to use (Default: all)")
+ private String samplers = join(SamplerType.values(), ',');
+
+ @Option(name = {"-i", "--interval"}, description = "Schedule a new job that samples every interval milliseconds (Default: disabled) in the background")
+ private int intervalMillis = -1; // -1 for disabled.
+
+ @Option(name = {"-t", "--stop"}, description = "Stop the scheduled sampling job identified by and . Jobs are stopped until the last schedules complete.")
+ private boolean shouldStop = false;
+
+ @Option(name = {"-l", "--list"}, description = "List the scheduled sampling jobs")
+ private boolean shouldList = false;
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ checkArgument(args.size() == 3 || args.size() == 2 || args.size() == 1 || args.size() == 0,
+ "Invalid arguments, either [keyspace table/* duration] or [keyspace table/*] or [duration] or no args.\n" +
+ "Optionally, use * to represent all tables under the keyspace.");
+ checkArgument(topCount > 0, "TopK count (-k) option must have positive value");
+ checkArgument(topCount < capacity,
+ "TopK count (-k) option must be smaller then the summary capacity (-s)");
+ checkArgument(capacity <= 1024, "Capacity (-s) cannot exceed 1024.");
+ String keyspace = null;
+ String table = null;
+ int durationMillis = 10000;
+ /* There are 3 possible outcomes after processing the args.
+ * - keyspace == null && table == null. We need to sample all tables
+ * - keyspace == KEYSPACE && table == *. We need to sample all tables under the specified KEYSPACE
+ * - keyspace = KEYSPACE && table == TABLE. Sample the specific KEYSPACE.table combination
+ */
+ if (args.size() == 3)
+ {
+ keyspace = args.get(0);
+ table = args.get(1);
+ durationMillis = Integer.parseInt(args.get(2));
+ }
+ else if (args.size() == 2)
+ {
+ keyspace = args.get(0);
+ table = args.get(1);
+ }
+ else if (args.size() == 1)
+ {
+ durationMillis = Integer.parseInt(args.get(0));
+ }
+ keyspace = nullifyWildcard(keyspace);
+ table = nullifyWildcard(table);
+
+ checkArgument(durationMillis > 0, "Duration: %s must be positive", durationMillis);
+
+ checkArgument(!hasInterval() || intervalMillis >= durationMillis,
+ "Invalid scheduled sampling interval. Expecting interval >= duration, but interval: %s ms; duration: %s ms",
+ intervalMillis, durationMillis);
+ // generate the list of samplers
+ List targets = Lists.newArrayList();
+ Set available = Arrays.stream(SamplerType.values()).map(Enum::toString).collect(Collectors.toSet());
+ for (String s : samplers.split(","))
+ {
+ String sampler = s.trim().toUpperCase();
+ checkArgument(available.contains(sampler), String.format("'%s' sampler is not available from: %s", s, Arrays.toString(SamplerType.values())));
+ targets.add(sampler);
+ }
+
+ PrintStream out = probe.output().out;
+
+ Map> results;
+ try
+ {
+ // handle scheduled samplings, i.e. start or stop
+ if (hasInterval() || shouldStop)
+ {
+ // keyspace and table are nullable
+ boolean opSuccess = probe.handleScheduledSampling(keyspace, table, capacity, topCount, durationMillis, intervalMillis, targets, shouldStop);
+ if (!opSuccess)
+ {
+ if (shouldStop)
+ out.printf("Unable to stop the non-existent scheduled sampling for keyspace: %s, table: %s%n", keyspace, table);
+ else
+ out.printf("Unable to schedule sampling for keyspace: %s, table: %s due to existing samplings. " +
+ "Stop the existing sampling jobs first.%n", keyspace, table);
+ }
+ return;
+ }
+ else if (shouldList)
+ {
+ List> sampleTasks = new ArrayList<>();
+ int maxKsLength = "KEYSPACE".length();
+ int maxTblLength = "TABLE".length();
+ for (String fullTableName : probe.getSampleTasks())
+ {
+ String[] parts = fullTableName.split("\\.");
+ checkState(parts.length == 2, "Unable to parse the full table name: %s", fullTableName);
+ sampleTasks.add(Pair.create(parts[0], parts[1]));
+ maxKsLength = Math.max(maxKsLength, parts[0].length());
+ }
+ // print the header line and put enough space between KEYSPACE AND TABLE.
+ String lineFormat = "%" + maxKsLength + "s %" + maxTblLength + "s%n";
+ out.printf(lineFormat, "KEYSPACE", "TABLE");
+ sampleTasks.forEach(pair -> out.printf(lineFormat, pair.left, pair.right));
+ return;
+ }
+ else
+ {
+ // blocking sample all the tables or all the tables under a keyspace
+ if (keyspace == null || table == null)
+ results = probe.getPartitionSample(keyspace, capacity, durationMillis, topCount, targets);
+ else // blocking sample the specific table
+ results = probe.getPartitionSample(keyspace, table, capacity, durationMillis, topCount, targets);
+ }
+ }
+ catch (OpenDataException e)
+ {
+ throw new RuntimeException(e);
+ }
+
+ AtomicBoolean first = new AtomicBoolean(true);
+ SamplingManager.ResultBuilder rb = new SamplingManager.ResultBuilder(first, results, targets);
+ out.println(SamplingManager.formatResult(rb));
+ }
+
+ private boolean hasInterval()
+ {
+ return intervalMillis != -1;
+ }
+
+ private String nullifyWildcard(String input)
+ {
+ return input != null && input.equals("*") ? null : input;
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/ProxyHistograms.java b/src/java/org/apache/cassandra/management/api/ProxyHistograms.java
new file mode 100644
index 000000000000..e3aaaa04db79
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/ProxyHistograms.java
@@ -0,0 +1,61 @@
+/*
+ * 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.management.api;
+
+import java.io.PrintStream;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+import static java.lang.String.format;
+
+@Command(name = "proxyhistograms", description = "Print statistic histograms for network operations")
+public class ProxyHistograms extends NodeToolCmd
+{
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ PrintStream out = probe.output().out;
+ String[] percentiles = {"50%", "75%", "95%", "98%", "99%", "Min", "Max"};
+ Double[] readLatency = probe.metricPercentilesAsArray(probe.getProxyMetric("Read"));
+ Double[] writeLatency = probe.metricPercentilesAsArray(probe.getProxyMetric("Write"));
+ Double[] rangeLatency = probe.metricPercentilesAsArray(probe.getProxyMetric("RangeSlice"));
+ Double[] casReadLatency = probe.metricPercentilesAsArray(probe.getProxyMetric("CASRead"));
+ Double[] casWriteLatency = probe.metricPercentilesAsArray(probe.getProxyMetric("CASWrite"));
+ Double[] viewWriteLatency = probe.metricPercentilesAsArray(probe.getProxyMetric("ViewWrite"));
+
+ out.println("proxy histograms");
+ out.println(format("%-10s%19s%19s%19s%19s%19s%19s",
+ "Percentile", "Read Latency", "Write Latency", "Range Latency", "CAS Read Latency", "CAS Write Latency", "View Write Latency"));
+ out.println(format("%-10s%19s%19s%19s%19s%19s%19s",
+ "", "(micros)", "(micros)", "(micros)", "(micros)", "(micros)", "(micros)"));
+ for (int i = 0; i < percentiles.length; i++)
+ {
+ out.println(format("%-10s%19.2f%19.2f%19.2f%19.2f%19.2f%19.2f",
+ percentiles[i],
+ readLatency[i],
+ writeLatency[i],
+ rangeLatency[i],
+ casReadLatency[i],
+ casWriteLatency[i],
+ viewWriteLatency[i]));
+ }
+ out.println();
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/RangeKeySample.java b/src/java/org/apache/cassandra/management/api/RangeKeySample.java
new file mode 100644
index 000000000000..0640f7ac08ca
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/RangeKeySample.java
@@ -0,0 +1,39 @@
+/*
+ * 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.management.api;
+
+import java.util.List;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+@Command(name = "rangekeysample", description = "Shows the sampled keys held across all keyspaces")
+public class RangeKeySample extends NodeToolCmd
+{
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ probe.output().out.println("RangeKeySample: ");
+ List tokenStrings = probe.sampleKeyRange();
+ for (String tokenString : tokenStrings)
+ {
+ probe.output().out.println("\t" + tokenString);
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/Rebuild.java b/src/java/org/apache/cassandra/management/api/Rebuild.java
new file mode 100644
index 000000000000..319e53778ab8
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/Rebuild.java
@@ -0,0 +1,64 @@
+/*
+ * 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.management.api;
+
+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.NodeTool.NodeToolCmd;
+
+@Command(name = "rebuild", description = "Rebuild data by streaming from other nodes (similarly to bootstrap)")
+public class Rebuild extends NodeToolCmd
+{
+ @Arguments(usage = "",
+ description = "Name of DC from which to select sources for streaming. By default, pick any DC (except local DC when --exclude-local-dc is set)")
+ private String sourceDataCenterName = null;
+
+ @Option(title = "specific_keyspace",
+ name = {"-ks", "--keyspace"},
+ description = "Use -ks to rebuild specific keyspace.")
+ private String keyspace = null;
+
+ @Option(title = "specific_tokens",
+ name = {"-ts", "--tokens"},
+ description = "Use -ts to rebuild specific token ranges, in the format of \"(start_token_1,end_token_1],(start_token_2,end_token_2],...(start_token_n,end_token_n]\".")
+ private String tokens = null;
+
+ @Option(title = "specific_sources",
+ name = {"-s", "--sources"},
+ description = "Use -s to specify hosts that this node should stream from when -ts is used. Multiple hosts should be separated using commas (e.g. 127.0.0.1,127.0.0.2,...)")
+ private String specificSources = null;
+
+ @Option(title = "exclude_local_dc",
+ name = {"--exclude-local-dc"},
+ description = "Use --exclude-local-dc to exclude nodes in local data center as source for streaming.")
+ private boolean excludeLocalDatacenterNodes = false;
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ // check the arguments
+ if (keyspace == null && tokens != null)
+ {
+ throw new IllegalArgumentException("Cannot specify tokens without keyspace.");
+ }
+
+ probe.rebuild(sourceDataCenterName, keyspace, tokens, specificSources, excludeLocalDatacenterNodes);
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/RebuildIndex.java b/src/java/org/apache/cassandra/management/api/RebuildIndex.java
new file mode 100644
index 000000000000..7e30dbc10496
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/RebuildIndex.java
@@ -0,0 +1,43 @@
+/*
+ * 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.management.api;
+
+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.NodeTool.NodeToolCmd;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.collect.Iterables.toArray;
+
+@Command(name = "rebuild_index", description = "A full rebuild of native secondary indexes for a given table")
+public class RebuildIndex extends NodeToolCmd
+{
+ @Arguments(usage = " ", description = "The keyspace and table name followed by a list of index names")
+ List args = new ArrayList<>();
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ checkArgument(args.size() >= 3, "rebuild_index requires ks, cf and idx args");
+ probe.rebuildIndex(args.get(0), args.get(1), toArray(args.subList(2, args.size()), String.class));
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/RecompressSSTables.java b/src/java/org/apache/cassandra/management/api/RecompressSSTables.java
new file mode 100644
index 000000000000..5cf7b36c87db
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/RecompressSSTables.java
@@ -0,0 +1,58 @@
+/*
+ * 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.management.api;
+
+import java.util.ArrayList;
+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.NodeTool.NodeToolCmd;
+
+@Command(name = "recompress_sstables", description = "Rewrite sstables (for the requested tables) that have compression configuration different from the current")
+public class RecompressSSTables extends NodeToolCmd
+{
+ @Arguments(usage = "[ ...]", description = "The keyspace followed by one or many tables")
+ private List args = new ArrayList<>();
+
+ @Option(title = "jobs",
+ name = {"-j", "--jobs"},
+ description = "Number of sstables to upgrade simultanously, set to 0 to use all available compaction threads")
+ private int jobs = 2;
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ List keyspaces = parseOptionalKeyspace(args, probe);
+ String[] tableNames = parseOptionalTables(args);
+
+ for (String keyspace : keyspaces)
+ {
+ try
+ {
+ probe.recompressSSTables(probe.output().out, keyspace, jobs, tableNames);
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException("Error occurred during enabling auto-compaction", e);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/java/org/apache/cassandra/management/api/Refresh.java b/src/java/org/apache/cassandra/management/api/Refresh.java
new file mode 100644
index 000000000000..3ccd4fd3bf55
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/Refresh.java
@@ -0,0 +1,43 @@
+/*
+ * 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.management.api;
+
+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.NodeTool.NodeToolCmd;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+@Command(name = "refresh", description = "Load newly placed SSTables to the system without restart")
+public class Refresh extends NodeToolCmd
+{
+ @Arguments(usage = " ", description = "The keyspace and table name")
+ private List args = new ArrayList<>();
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ probe.output().out.println("nodetool refresh is deprecated, use nodetool import instead");
+ checkArgument(args.size() == 2, "refresh requires ks and cf args");
+ probe.loadNewSSTables(args.get(0), args.get(1));
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/RefreshSizeEstimates.java b/src/java/org/apache/cassandra/management/api/RefreshSizeEstimates.java
new file mode 100644
index 000000000000..f0e045f95251
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/RefreshSizeEstimates.java
@@ -0,0 +1,33 @@
+/*
+ * 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.management.api;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool;
+
+@Command(name = "refreshsizeestimates", description = "Refresh system.size_estimates")
+public class RefreshSizeEstimates extends NodeTool.NodeToolCmd
+{
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ probe.refreshSizeEstimates();
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/ReloadCIDRGroupsCache.java b/src/java/org/apache/cassandra/management/api/ReloadCIDRGroupsCache.java
new file mode 100644
index 000000000000..c166a96f5f03
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/ReloadCIDRGroupsCache.java
@@ -0,0 +1,36 @@
+/*
+ * 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.management.api;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+/**
+ * Nodetool command to reload CIDR groups cache
+ */
+@Command(name = "reloadcidrgroupscache", description = "Reload CIDR groups cache with latest entries in cidr_groups table, when CIDR authorizer is enabled")
+public class ReloadCIDRGroupsCache extends NodeToolCmd
+{
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ probe.reloadCidrGroupsCache();
+ probe.output().out.println("Reloaded CIDR groups cache");
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/ReloadLocalSchema.java b/src/java/org/apache/cassandra/management/api/ReloadLocalSchema.java
new file mode 100644
index 000000000000..b55121a902c4
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/ReloadLocalSchema.java
@@ -0,0 +1,32 @@
+/*
+ * 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.management.api;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+@Command(name = "reloadlocalschema", description = "Reload local node schema from system tables")
+public class ReloadLocalSchema extends NodeToolCmd
+{
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ probe.reloadLocalSchema();
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/ReloadSeeds.java b/src/java/org/apache/cassandra/management/api/ReloadSeeds.java
new file mode 100644
index 000000000000..756464e784d8
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/ReloadSeeds.java
@@ -0,0 +1,48 @@
+/*
+ * 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.management.api;
+
+import java.io.PrintStream;
+import java.util.List;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+@Command(name = "reloadseeds", description = "Reload the seed node list from the seed node provider")
+public class ReloadSeeds extends NodeToolCmd
+{
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ PrintStream out = probe.output().out;
+ List seedList = probe.reloadSeeds();
+ if (seedList == null)
+ {
+ out.println("Failed to reload the seed node list.");
+ }
+ else if (seedList.isEmpty())
+ {
+ out.println("Seed node list does not contain any remote node IPs");
+ }
+ else
+ {
+ out.println("Updated seed node IP list, excluding the current node's IP: " + String.join(" ", seedList));
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/ReloadTriggers.java b/src/java/org/apache/cassandra/management/api/ReloadTriggers.java
new file mode 100644
index 000000000000..2685791b5802
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/ReloadTriggers.java
@@ -0,0 +1,32 @@
+/*
+ * 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.management.api;
+
+import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+@Command(name = "reloadtriggers", description = "Reload trigger classes")
+public class ReloadTriggers extends NodeToolCmd
+{
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ probe.reloadTriggers();
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/RelocateSSTables.java b/src/java/org/apache/cassandra/management/api/RelocateSSTables.java
new file mode 100644
index 000000000000..e514e243e7b3
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/RelocateSSTables.java
@@ -0,0 +1,55 @@
+/*
+ * 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.management.api;
+
+import java.util.ArrayList;
+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.NodeTool;
+
+@Command(name = "relocatesstables", description = "Relocates sstables to the correct disk")
+public class RelocateSSTables extends NodeTool.NodeToolCmd
+{
+ @Arguments(usage = " ", description = "The keyspace and table name")
+ private List args = new ArrayList<>();
+
+ @Option(title = "jobs",
+ name = {"-j", "--jobs"},
+ description = "Number of sstables to relocate simultanously, set to 0 to use all available compaction threads")
+ private int jobs = 2;
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ List keyspaces = parseOptionalKeyspace(args, probe);
+ String[] cfnames = parseOptionalTables(args);
+ try
+ {
+ for (String keyspace : keyspaces)
+ probe.relocateSSTables(jobs, keyspace, cfnames);
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException("Got error while relocating", e);
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/RemoveNode.java b/src/java/org/apache/cassandra/management/api/RemoveNode.java
new file mode 100644
index 000000000000..2cc9f33ab681
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/RemoveNode.java
@@ -0,0 +1,54 @@
+/*
+ * 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.management.api;
+
+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.NodeTool.NodeToolCmd;
+
+@Command(name = "removenode", description = "Show status of current node removal, abort removal or remove provided ID")
+public class RemoveNode extends NodeToolCmd
+{
+ @Arguments(title = "remove_operation", usage = "| || --force", description = "Show status of current node removal, abort removal, or remove provided ID", required = true)
+ private List removeOperation = null;
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ switch (removeOperation.get(0))
+ {
+ case "status":
+ probe.output().out.println("RemovalStatus: " + probe.getRemovalStatus(printPort));
+ break;
+ case "force":
+ throw new IllegalArgumentException("Can't force a nodetool removenode. Instead abort the ongoing removenode and retry.");
+ case "abort":
+ if (removeOperation.size() < 2)
+ probe.output().err.print("Abort requires the node id to abort the removal for.");
+ probe.getCMSOperationsProxy().cancelInProgressSequences(removeOperation.get(1), "REMOVE");
+ break;
+ default:
+ boolean force = removeOperation.size() > 1 && removeOperation.get(1).equals("--force");
+ probe.removeNode(removeOperation.get(0), force);
+ break;
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/Repair.java b/src/java/org/apache/cassandra/management/api/Repair.java
new file mode 100644
index 000000000000..ee30bb491069
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/Repair.java
@@ -0,0 +1,205 @@
+/*
+ * 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.management.api;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Supplier;
+
+import com.google.common.collect.Sets;
+import org.apache.commons.lang3.StringUtils;
+
+import io.airlift.airline.Arguments;
+import io.airlift.airline.Cli;
+import io.airlift.airline.Command;
+import io.airlift.airline.Option;
+import org.apache.cassandra.repair.RepairParallelism;
+import org.apache.cassandra.repair.messages.RepairOption;
+import org.apache.cassandra.schema.SchemaConstants;
+import org.apache.cassandra.streaming.PreviewKind;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static org.apache.commons.lang3.StringUtils.EMPTY;
+
+@Command(name = "repair", description = "Repair one or more tables")
+public class Repair extends NodeToolCmd
+{
+ public final static Set ONLY_EXPLICITLY_REPAIRED = Sets.newHashSet(SchemaConstants.DISTRIBUTED_KEYSPACE_NAME);
+
+ @Arguments(usage = "[ ...]", description = "The keyspace followed by one or many tables")
+ private List args = new ArrayList<>();
+
+ @Option(title = "seqential", name = {"-seq", "--sequential"}, description = "Use -seq to carry out a sequential repair")
+ private boolean sequential = false;
+
+ @Option(title = "dc parallel", name = {"-dcpar", "--dc-parallel"}, description = "Use -dcpar to repair data centers in parallel.")
+ private boolean dcParallel = false;
+
+ @Option(title = "local_dc", name = {"-local", "--in-local-dc"}, description = "Use -local to only repair against nodes in the same datacenter")
+ private boolean localDC = false;
+
+ @Option(title = "specific_dc", name = {"-dc", "--in-dc"}, description = "Use -dc to repair specific datacenters")
+ private List specificDataCenters = new ArrayList<>();;
+
+ @Option(title = "specific_host", name = {"-hosts", "--in-hosts"}, description = "Use -hosts to repair specific hosts")
+ private List specificHosts = new ArrayList<>();
+
+ @Option(title = "start_token", name = {"-st", "--start-token"}, description = "Use -st to specify a token at which the repair range starts (exclusive)")
+ private String startToken = EMPTY;
+
+ @Option(title = "end_token", name = {"-et", "--end-token"}, description = "Use -et to specify a token at which repair range ends (inclusive)")
+ private String endToken = EMPTY;
+
+ @Option(title = "primary_range", name = {"-pr", "--partitioner-range"}, description = "Use -pr to repair only the first range returned by the partitioner")
+ private boolean primaryRange = false;
+
+ @Option(title = "full", name = {"-full", "--full"}, description = "Use -full to issue a full repair.")
+ private boolean fullRepair = false;
+
+ @Option(title = "force", name = {"-force", "--force"}, description = "Use -force to filter out down endpoints")
+ private boolean force = false;
+
+ @Option(title = "preview", name = {"-prv", "--preview"}, description = "Determine ranges and amount of data to be streamed, but don't actually perform repair")
+ private boolean preview = false;
+
+ @Option(title = "validate", name = {"-vd", "--validate"}, description = "Checks that repaired data is in sync between nodes. Out of sync repaired data indicates a full repair should be run.")
+ private boolean validate = false;
+
+ @Option(title = "job_threads", name = {"-j", "--job-threads"}, description = "Number of threads to run repair jobs. " +
+ "Usually this means number of CFs to repair concurrently. " +
+ "WARNING: increasing this puts more load on repairing nodes, so be careful. (default: 1, max: 4)")
+ private int numJobThreads = 1;
+
+ @Option(title = "trace_repair", name = {"-tr", "--trace"}, description = "Use -tr to trace the repair. Traces are logged to system_traces.events.")
+ private boolean trace = false;
+
+ @Option(title = "pull_repair", name = {"-pl", "--pull"}, description = "Use --pull to perform a one way repair where data is only streamed from a remote node to this node.")
+ private boolean pullRepair = false;
+
+ @Option(title = "optimise_streams", name = {"-os", "--optimise-streams"}, description = "Use --optimise-streams to try to reduce the number of streams we do (EXPERIMENTAL, see CASSANDRA-3200).")
+ private boolean optimiseStreams = false;
+
+ @Option(title = "skip-paxos", name = {"-skip-paxos", "--skip-paxos"}, description = "If the --skip-paxos flag is included, the paxos repair step is skipped. Paxos repair is also skipped for preview repairs.")
+ private boolean skipPaxos = false;
+
+ @Option(title = "paxos-only", name = {"-paxos-only", "--paxos-only"}, description = "If the --paxos-only flag is included, no table data is repaired, only paxos operations..")
+ private boolean paxosOnly = false;
+
+ @Option(title = "ignore_unreplicated_keyspaces", name = {"-iuk","--ignore-unreplicated-keyspaces"}, description = "Use --ignore-unreplicated-keyspaces to ignore keyspaces which are not replicated, otherwise the repair will fail")
+ private boolean ignoreUnreplicatedKeyspaces = false;
+
+ private PreviewKind getPreviewKind()
+ {
+ if (validate)
+ {
+ return PreviewKind.REPAIRED;
+ }
+ else if (preview && fullRepair)
+ {
+ return PreviewKind.ALL;
+ }
+ else if (preview)
+ {
+ return PreviewKind.UNREPAIRED;
+ }
+ else
+ {
+ return PreviewKind.NONE;
+ }
+ }
+
+ @Override
+ public void execute(NodeProbe probe)
+ {
+ List keyspaces = parseOptionalKeyspace(args, probe, KeyspaceSet.NON_LOCAL_STRATEGY);
+ String[] cfnames = parseOptionalTables(args);
+
+ if (primaryRange && (!specificDataCenters.isEmpty() || !specificHosts.isEmpty()))
+ throw new RuntimeException("Primary range repair should be performed on all nodes in the cluster.");
+
+ for (String keyspace : keyspaces)
+ {
+ // avoid repairing system_distributed by default (CASSANDRA-9621)
+ if ((args == null || args.isEmpty()) && ONLY_EXPLICITLY_REPAIRED.contains(keyspace))
+ continue;
+
+ Map options = createOptions(probe::getDataCenter, cfnames);
+ try
+ {
+ probe.repairAsync(probe.output().out, keyspace, options);
+ } catch (Exception e)
+ {
+ throw new RuntimeException("Error occurred during repair", e);
+ }
+ }
+ }
+
+ public static Map parseOptionMap(Supplier localDCOption, List args)
+ {
+ List realArgs = new ArrayList<>(args.size() + 1);
+ realArgs.add("repair");
+ realArgs.addAll(args);
+ Cli parser = Cli.builder("fortesting").withCommand(Repair.class).build();
+ Repair repair = (Repair) parser.parse(realArgs);
+ String[] cfnames = repair.parseOptionalTables(repair.args);
+ return repair.createOptions(localDCOption, cfnames);
+ }
+
+ private Map createOptions(Supplier localDCOption, String[] cfnames)
+ {
+ Map options = new HashMap<>();
+ RepairParallelism parallelismDegree = RepairParallelism.PARALLEL;
+ if (sequential)
+ parallelismDegree = RepairParallelism.SEQUENTIAL;
+ else if (dcParallel)
+ parallelismDegree = RepairParallelism.DATACENTER_AWARE;
+ options.put(RepairOption.PARALLELISM_KEY, parallelismDegree.getName());
+ options.put(RepairOption.PRIMARY_RANGE_KEY, Boolean.toString(primaryRange));
+ options.put(RepairOption.INCREMENTAL_KEY, Boolean.toString(!fullRepair && !(paxosOnly && getPreviewKind() == PreviewKind.NONE)));
+ options.put(RepairOption.JOB_THREADS_KEY, Integer.toString(numJobThreads));
+ options.put(RepairOption.TRACE_KEY, Boolean.toString(trace));
+ options.put(RepairOption.COLUMNFAMILIES_KEY, StringUtils.join(cfnames, ","));
+ options.put(RepairOption.PULL_REPAIR_KEY, Boolean.toString(pullRepair));
+ options.put(RepairOption.FORCE_REPAIR_KEY, Boolean.toString(force));
+ options.put(RepairOption.PREVIEW, getPreviewKind().toString());
+ options.put(RepairOption.OPTIMISE_STREAMS_KEY, Boolean.toString(optimiseStreams));
+ options.put(RepairOption.IGNORE_UNREPLICATED_KS, Boolean.toString(ignoreUnreplicatedKeyspaces));
+ options.put(RepairOption.REPAIR_PAXOS_KEY, Boolean.toString(!skipPaxos && getPreviewKind() == PreviewKind.NONE));
+ options.put(RepairOption.PAXOS_ONLY_KEY, Boolean.toString(paxosOnly && getPreviewKind() == PreviewKind.NONE));
+
+ if (!startToken.isEmpty() || !endToken.isEmpty())
+ {
+ options.put(RepairOption.RANGES_KEY, startToken + ":" + endToken);
+ }
+ if (localDC)
+ {
+ options.put(RepairOption.DATACENTERS_KEY, StringUtils.join(newArrayList(localDCOption.get()), ","));
+ }
+ else
+ {
+ options.put(RepairOption.DATACENTERS_KEY, StringUtils.join(specificDataCenters, ","));
+ }
+ options.put(RepairOption.HOSTS_KEY, StringUtils.join(specificHosts, ","));
+ return options;
+ }
+}
diff --git a/src/java/org/apache/cassandra/management/api/RepairAdmin.java b/src/java/org/apache/cassandra/management/api/RepairAdmin.java
new file mode 100644
index 000000000000..ea19e35b74f9
--- /dev/null
+++ b/src/java/org/apache/cassandra/management/api/RepairAdmin.java
@@ -0,0 +1,331 @@
+/*
+ * 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.management.api;
+
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import javax.management.openmbean.CompositeData;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+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.repair.consistent.LocalSessionInfo;
+import org.apache.cassandra.repair.consistent.admin.CleanupSummary;
+import org.apache.cassandra.repair.consistent.admin.PendingStats;
+import org.apache.cassandra.repair.consistent.admin.RepairStats;
+import org.apache.cassandra.tools.NodeProbe;
+import org.apache.cassandra.tools.NodeTool;
+import org.apache.cassandra.utils.FBUtilities;
+
+/**
+ * Supports listing and failing incremental repair sessions
+ */
+public abstract class RepairAdmin extends NodeTool.NodeToolCmd
+{
+ @Command(name = "list", description = "list repair sessions")
+ public static class ListCmd extends RepairAdmin
+ {
+ @Option(title = "all", name = {"-a", "--all"}, description = "include completed and failed sessions")
+ private boolean all;
+
+ @Option(title = "start_token", name = {"-st", "--start-token"}, description = "Use -st to specify a token at which the repair range starts")
+ private String startToken = StringUtils.EMPTY;
+
+ @Option(title = "end_token", name = {"-et", "--end-token"}, description = "Use -et to specify a token at which repair range ends")
+ private String endToken = StringUtils.EMPTY;
+
+ protected void execute(NodeProbe probe)
+ {
+ PrintStream out = probe.output().out;
+ List> sessions = probe.getRepairServiceProxy().getSessions(all, getRangeString(startToken, endToken));
+ if (sessions.isEmpty())
+ {
+ out.println("no sessions");
+ }
+ else
+ {
+ List> rows = new ArrayList<>();
+ rows.add(Lists.newArrayList("id",
+ "state",
+ "last activity",
+ "coordinator",
+ "participants",
+ "participants_wp"));
+ long now = FBUtilities.nowInSeconds();
+ for (Map session : sessions)
+ {
+ int updated = Integer.parseInt(session.get(LocalSessionInfo.LAST_UPDATE));
+ List values = Lists.newArrayList(session.get(LocalSessionInfo.SESSION_ID),
+ session.get(LocalSessionInfo.STATE),
+ (now - updated) + " (s)",
+ session.get(LocalSessionInfo.COORDINATOR),
+ session.get(LocalSessionInfo.PARTICIPANTS),
+ session.get(LocalSessionInfo.PARTICIPANTS_WP));
+ rows.add(values);
+ }
+
+ printTable(rows, out);
+ }
+ }
+ }
+ @Command(name = "summarize-pending", description = "report the amount of data marked pending repair for the given token " +
+ "range (or all replicated range if no tokens are provided")
+ public static class SummarizePendingCmd extends RepairAdmin
+ {
+ @Option(title = "verbose", name = {"-v", "--verbose"}, description = "print additional info ")
+ private boolean verbose;
+
+ @Option(title = "start_token", name = {"-st", "--start-token"}, description = "Use -st to specify a token at which the repair range starts")
+ private String startToken = StringUtils.EMPTY;
+
+ @Option(title = "end_token", name = {"-et", "--end-token"}, description = "Use -et to specify a token at which repair range ends")
+ private String endToken = StringUtils.EMPTY;
+
+ @Arguments(usage = "[ ...]", description = "The keyspace followed by one or many tables")
+ private List schemaArgs = new ArrayList<>();
+
+ protected void execute(NodeProbe probe)
+ {
+ List cds = probe.getRepairServiceProxy().getPendingStats(schemaArgs, getRangeString(startToken, endToken));
+ List stats = new ArrayList<>(cds.size());
+ cds.forEach(cd -> stats.add(PendingStats.fromComposite(cd)));
+
+ stats.sort((l, r) -> {
+ int cmp = l.keyspace.compareTo(r.keyspace);
+ if (cmp != 0)
+ return cmp;
+
+ return l.table.compareTo(r.table);
+ });
+
+ List header = Lists.newArrayList("keyspace", "table", "total");
+ if (verbose)
+ {
+ header.addAll(Lists.newArrayList("pending", "finalized", "failed"));
+ }
+
+ List> rows = new ArrayList<>(stats.size() + 1);
+ rows.add(header);
+
+ for (PendingStats stat : stats)
+ {
+ List row = new ArrayList<>(header.size());
+
+ row.add(stat.keyspace);
+ row.add(stat.table);
+ row.add(stat.total.sizeString());
+ if (verbose)
+ {
+ row.add(stat.pending.sizeString());
+ row.add(stat.finalized.sizeString());
+ row.add(stat.failed.sizeString());
+ }
+ rows.add(row);
+ }
+
+ printTable(rows, probe.output().out);
+ }
+ }
+
+ @Command(name = "summarize-repaired", description = "return the most recent repairedAt timestamp for the given token range " +
+ "(or all replicated ranges if no tokens are provided)")
+ public static class SummarizeRepairedCmd extends RepairAdmin
+ {
+ @Option(title = "verbose", name = {"-v", "--verbose"}, description = "print additional info ")
+ private boolean verbose = false;
+
+ @Option(title = "start_token", name = {"-st", "--start-token"}, description = "Use -st to specify a token at which the repair range starts")
+ private String startToken = StringUtils.EMPTY;
+
+ @Option(title = "end_token", name = {"-et", "--end-token"}, description = "Use -et to specify a token at which repair range ends")
+ private String endToken = StringUtils.EMPTY;
+
+ @Arguments(usage = "[ ...]", description = "The keyspace followed by one or many tables")
+ private List schemaArgs = new ArrayList<>();
+
+ protected void execute(NodeProbe probe)
+ {
+ PrintStream out = probe.output().out;
+ List compositeData = probe.getRepairServiceProxy().getRepairStats(schemaArgs, getRangeString(startToken, endToken));
+
+ if (compositeData.isEmpty())
+ {
+ out.println("no stats");
+ return;
+ }
+
+ List stats = new ArrayList<>(compositeData.size());
+ compositeData.forEach(cd -> stats.add(RepairStats.fromComposite(cd)));
+
+ stats.sort((l, r) -> {
+ int cmp = l.keyspace.compareTo(r.keyspace);
+ if (cmp != 0)
+ return cmp;
+
+ return l.table.compareTo(r.table);
+ });
+
+ List