From 26bd244ad8628b240892d4c59f17a133f5f85f5b Mon Sep 17 00:00:00 2001 From: Maxim Muzafarov Date: Sat, 15 Jun 2024 21:04:26 +0200 Subject: [PATCH 1/8] CASSANDRA-17445 use picocli with nodetoolV2 instead of ariline --- .build/cassandra-deps-template.xml | 8 + .build/parent-pom-template.xml | 10 + bin/nodetool | 10 +- .../cassandra/management/BaseCommand.java | 38 ++ .../management/CassandraHelpCommand.java | 100 +++ .../management/CassandraHelpLayout.java | 444 +++++++++++++ .../cassandra/management/CommandUtils.java | 39 ++ .../management/ManagementContext.java | 27 + .../management/api/AbortBootstrap.java | 47 ++ .../cassandra/management/api/Assassinate.java | 38 ++ .../management/api/ForceCompact.java | 72 +++ .../cassandra/management/api/JmxConnect.java | 92 +++ .../management/api/TopLevelCommand.java | 29 + .../org/apache/cassandra/tools/NodeProbe.java | 64 +- .../org/apache/cassandra/tools/NodeTool.java | 6 +- .../apache/cassandra/tools/NodeToolV2.java | 585 ++++++++++++++++++ .../cassandra/tools/NodeToolSynopsisTest.java | 137 ++++ .../apache/cassandra/tools/ToolRunner.java | 5 + 18 files changed, 1722 insertions(+), 29 deletions(-) create mode 100644 src/java/org/apache/cassandra/management/BaseCommand.java create mode 100644 src/java/org/apache/cassandra/management/CassandraHelpCommand.java create mode 100644 src/java/org/apache/cassandra/management/CassandraHelpLayout.java create mode 100644 src/java/org/apache/cassandra/management/CommandUtils.java create mode 100644 src/java/org/apache/cassandra/management/ManagementContext.java create mode 100644 src/java/org/apache/cassandra/management/api/AbortBootstrap.java create mode 100644 src/java/org/apache/cassandra/management/api/Assassinate.java create mode 100644 src/java/org/apache/cassandra/management/api/ForceCompact.java create mode 100644 src/java/org/apache/cassandra/management/api/JmxConnect.java create mode 100644 src/java/org/apache/cassandra/management/api/TopLevelCommand.java create mode 100644 src/java/org/apache/cassandra/tools/NodeToolV2.java create mode 100644 test/unit/org/apache/cassandra/tools/NodeToolSynopsisTest.java 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/management/BaseCommand.java b/src/java/org/apache/cassandra/management/BaseCommand.java new file mode 100644 index 000000000000..aa22619854af --- /dev/null +++ b/src/java/org/apache/cassandra/management/BaseCommand.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; + +import javax.inject.Inject; + +/** + * Base class for all nodetool commands. + */ +public abstract class BaseCommand implements Runnable +{ + @Inject + protected ManagementContext probe; + + @Override + public void run() + { + execute(probe); + } + + protected abstract void execute(ManagementContext 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..5efd76b8500c --- /dev/null +++ b/src/java/org/apache/cassandra/management/CassandraHelpCommand.java @@ -0,0 +1,100 @@ +/* + * 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 no input command argument is specified, print help for the top-level command. + if (commands == null) + { + printUsage(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); + + printUsage(subcommand, colors, out); + } + + private static void printUsage(CommandLine command, CommandLine.Help.ColorScheme colors, PrintWriter out) + { + if (command == null) + return; + command.usage(out, colors); + } + + /** + * 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..76346bf28a4e --- /dev/null +++ b/src/java/org/apache/cassandra/management/CassandraHelpLayout.java @@ -0,0 +1,444 @@ +/* + * 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.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import com.google.common.base.Preconditions; + +import org.apache.cassandra.utils.AbstractGuavaIterator; +import picocli.CommandLine; + +import static com.google.common.collect.ObjectArrays.concat; +import static org.apache.cassandra.management.CommandUtils.leadingSpaces; +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 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(); + + 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 detailedSynopsis(int synopsisHeadingLength, Comparator optionSort, boolean clusterBooleanOptions) + { + Preconditions.checkState(synopsisHeadingLength >= 0, + "synopsisHeadingLength must be a positive number but was " + synopsisHeadingLength); + + // Cassandra uses end of options delimiter in usage help. + commandSpec().usageMessage().showEndOfOptionsDelimiterInUsageHelp(true); + + 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 = createDetailedSynopsisCommandText(); + + CommandLine.Model.CommandSpec commandSpec = commandSpec(); + String parentCommandName = commandSpec.parent().qualifiedName(); + ColorScheme colorScheme = colorScheme(); + + int leadingColumnWidth = parentCommandName.length() + COLUMN_INDENT; + int followingColumnWidth = commandSpec.usageMessage().width() - leadingColumnWidth; + TextTable textTable = TextTable.forColumns(colorScheme, + new Column(leadingColumnWidth, 0, Column.Overflow.TRUNCATE), + new Column(followingColumnWidth, 0, Column.Overflow.WRAP)); + textTable.setAdjustLineBreaksForWideCJKCharacters(commandSpec.usageMessage().adjustLineBreaksForWideCJKCharacters()); + textTable.indentWrappedLines = 0; + + Ansi.Text emptyCell = Ansi.OFF.new Text(leadingSpaces(leadingColumnWidth), colorScheme); + Ansi.Text cmdPadding = Ansi.OFF.new Text(leadingSpaces(COLUMN_INDENT), colorScheme); + Ansi.Text parentCommandText = cmdPadding.concat(colorScheme.commandText(parentCommandName)).concat(" "); + // All other fields added to the synopsis are left-adjusted, so we don't need to align them. + Ansi.Text text = groupsText.concat(" ") + .concat(commandSpec.name()) + .concat(endOfOptionsText) + .concat(" ") + .concat(positionalParamText) + .concat(commandText); + + LineBreakingOptionsIterator iter = new LineBreakingOptionsIterator(optionsList.iterator(), followingColumnWidth); + boolean commandTextNotAdded = true; + while (iter.hasNext()) + { + Ansi.Text row = iter.next(); + Ansi.Text leadingCell = emptyCell; + + if (commandTextNotAdded) + { + leadingCell = parentCommandText; + row = colorScheme.text(" ").concat(row); + commandTextNotAdded = false; + } + + if (iter.hasNext()) + textTable.addRowValues(leadingCell, row); + else + textTable.addRowValues(leadingCell, row.concat(text)); + } + + textTable.addRowValues(Ansi.OFF.new Text("", colorScheme)); + return textTable.toString(); + } + + 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 footerHeading(Object... params) + { + return createHeading("%n", params); + } + + 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; + } + + private static class LineBreakingOptionsIterator extends AbstractGuavaIterator + { + private final Iterator optionsIterator; + private final int width; + private Ansi.Text prev; + + LineBreakingOptionsIterator(Iterator optionsIterator, int width) + { + this.optionsIterator = optionsIterator; + this.width = width; + } + + @Override + protected Ansi.Text computeNext() + { + while (optionsIterator.hasNext()) + { + Ansi.Text next = optionsIterator.next(); + if (prev == null) + prev = next; + + Ansi.Text curr; + if (prev == next) + curr = next; + else + curr = prev.concat(" ").concat(next); + + if (curr.plainString().length() > width) + { + Ansi.Text result = prev; + prev = next; + return result; + } + else + { + prev = curr; + } + } + + if (prev == null) + return endOfData(); + else + { + Ansi.Text result = prev; + prev = null; + return result; + } + } + } + + 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.text(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..86fc0d8c3cc0 --- /dev/null +++ b/src/java/org/apache/cassandra/management/CommandUtils.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; + +import java.util.Arrays; + +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); + } +} diff --git a/src/java/org/apache/cassandra/management/ManagementContext.java b/src/java/org/apache/cassandra/management/ManagementContext.java new file mode 100644 index 000000000000..cddfcb81abef --- /dev/null +++ b/src/java/org/apache/cassandra/management/ManagementContext.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 ManagementContext +{ + T getManagementService(Class serviceClass); +} 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..d741f9dce195 --- /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.ManagementContext; +import org.apache.cassandra.service.StorageServiceMBean; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; + +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") + private String nodeId = EMPTY; + + @Option(names = "--ip", description = "IP of the node that failed bootstrap") + private String endpoint = EMPTY; + + @Override + public void execute(ManagementContext 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"); + probe.getManagementService(StorageServiceMBean.class).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..0ecb7696436a --- /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.ManagementContext; +import org.apache.cassandra.service.StorageServiceMBean; +import picocli.CommandLine; + +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") + private String ip_address = EMPTY; + + @Override + public void execute(ManagementContext probe) + { + probe.getManagementService(StorageServiceMBean.class).assassinateEndpoint(ip_address); + } +} 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..aad5e7bed1c0 --- /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.ManagementContext; +import org.apache.cassandra.service.StorageServiceMBean; +import picocli.CommandLine; + +import static com.google.common.base.Preconditions.checkArgument; +import static org.apache.cassandra.tools.NodeToolV2.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" }) + private List args; + + @CommandLine.Parameters(index = "0", arity = "1", description = "The keyspace name to compact") + private String keyspace; + + @CommandLine.Parameters(index = "1", arity = "1", description = "The table name to compact") + private String table; + + @CommandLine.Parameters(index = "2..*", arity = "1", description = "The partition keys to compact") + private String[] keys; + + @Override + public void execute(ManagementContext 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 + { + probe.getManagementService(StorageServiceMBean.class).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/JmxConnect.java b/src/java/org/apache/cassandra/management/api/JmxConnect.java new file mode 100644 index 000000000000..f169b523be80 --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/JmxConnect.java @@ -0,0 +1,92 @@ +/* + * 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 javax.inject.Inject; + +import com.google.common.base.Throwables; + +import org.apache.cassandra.management.ManagementContext; +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.commons.lang3.StringUtils.EMPTY; + +/** + * Command options for NodeTool commands that are executed via JMX. + */ +@CommandLine.Command(name = "connect", description = "Connect NodeTool to a Cassandra node using JMX") +public class JmxConnect implements Runnable +{ + public static final String MIXIN_KEY = "jmx"; + + @CommandLine.Option(names = { "-h", "--host"}, description = "Node hostname or ip address") + private String host = "127.0.0.1"; + + @CommandLine.Option(names = {"-p", "--port"}, description = "Remote jmx agent port number") + private String port = "7199"; + + @CommandLine.Option(names = {"-u", "--username"}, description = "Remote jmx agent username") + private String username = EMPTY; + + @CommandLine.Option(names = {"-pw", "--password"}, description = "Remote jmx agent password") + private String password = EMPTY; + + @CommandLine.Option(names = {"-pwf", "--password-file"}, description = "Path to the JMX password file") + private String passwordFilePath = EMPTY; + + @CommandLine.Option(names = { "-pp", "--print-port"}, description = "Operate in 4.0 mode with hosts disambiguated by port number") + private boolean printPort = false; + + @Inject + private INodeProbeFactory nodeProbeFactory; + @Inject + private Output output; + public ManagementContext nodeClient; + + public ManagementContext init() + { + try + { + if (username.isEmpty()) + nodeClient = nodeProbeFactory.create(host, parseInt(port)); + else + nodeClient = nodeProbeFactory.create(host, parseInt(port), username, password); + + return nodeClient; + } + 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()); + } + + return null; + } + + @Override + public void run() + { + // no-op + } +} diff --git a/src/java/org/apache/cassandra/management/api/TopLevelCommand.java b/src/java/org/apache/cassandra/management/api/TopLevelCommand.java new file mode 100644 index 000000000000..ff5c7658c665 --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/TopLevelCommand.java @@ -0,0 +1,29 @@ +/* + * 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.CassandraHelpCommand; +import picocli.CommandLine; + +@CommandLine.Command(name = "nodetool", + subcommands = { CassandraHelpCommand.class }, + description = "Manage your Cassandra cluster") +public class TopLevelCommand +{ +} diff --git a/src/java/org/apache/cassandra/tools/NodeProbe.java b/src/java/org/apache/cassandra/tools/NodeProbe.java index b121cb310091..1151fc685c02 100644 --- a/src/java/org/apache/cassandra/tools/NodeProbe.java +++ b/src/java/org/apache/cassandra/tools/NodeProbe.java @@ -37,11 +37,11 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Optional; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; - import javax.annotation.Nullable; import javax.management.JMX; import javax.management.MBeanServerConnection; @@ -55,12 +55,20 @@ import javax.management.remote.JMXServiceURL; import javax.rmi.ssl.SslRMIClientSocketFactory; +import com.google.common.base.Function; +import com.google.common.base.Strings; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import com.google.common.collect.Sets; +import com.google.common.util.concurrent.Uninterruptibles; + import org.apache.cassandra.audit.AuditLogManager; import org.apache.cassandra.audit.AuditLogManagerMBean; import org.apache.cassandra.audit.AuditLogOptions; import org.apache.cassandra.audit.AuditLogOptionsCompositeData; - -import com.google.common.collect.ImmutableMap; import org.apache.cassandra.auth.AuthCache; import org.apache.cassandra.auth.AuthCacheMBean; import org.apache.cassandra.auth.CIDRGroupsMappingManager; @@ -97,12 +105,12 @@ import org.apache.cassandra.metrics.StorageMetrics; import org.apache.cassandra.metrics.TableMetrics; import org.apache.cassandra.metrics.ThreadPoolMetrics; +import org.apache.cassandra.management.ManagementContext; import org.apache.cassandra.net.MessagingService; import org.apache.cassandra.net.MessagingServiceMBean; import org.apache.cassandra.service.ActiveRepairServiceMBean; import org.apache.cassandra.service.CacheService; import org.apache.cassandra.service.CacheServiceMBean; -import org.apache.cassandra.tcm.CMSOperationsMBean; import org.apache.cassandra.service.GCInspector; import org.apache.cassandra.service.GCInspectorMXBean; import org.apache.cassandra.service.StorageProxy; @@ -111,19 +119,10 @@ import org.apache.cassandra.streaming.StreamManagerMBean; import org.apache.cassandra.streaming.StreamState; import org.apache.cassandra.streaming.management.StreamStateCompositeData; -import org.apache.cassandra.tools.nodetool.formatter.TableBuilder; - -import com.google.common.base.Function; -import com.google.common.base.Strings; -import com.google.common.collect.HashMultimap; -import com.google.common.collect.Iterables; -import com.google.common.collect.Maps; -import com.google.common.collect.Multimap; -import com.google.common.collect.Sets; -import com.google.common.util.concurrent.Uninterruptibles; - import org.apache.cassandra.tcm.CMSOperations; +import org.apache.cassandra.tcm.CMSOperationsMBean; import org.apache.cassandra.tools.nodetool.GetTimeout; +import org.apache.cassandra.tools.nodetool.formatter.TableBuilder; import org.apache.cassandra.utils.NativeLibrary; import static org.apache.cassandra.config.CassandraRelevantProperties.NODETOOL_JMX_NOTIFICATION_POLL_INTERVAL_SECONDS; @@ -132,7 +131,7 @@ /** * JMX client operations for Cassandra. */ -public class NodeProbe implements AutoCloseable +public class NodeProbe implements AutoCloseable, ManagementContext { private static final String fmtUrl = "service:jmx:rmi:///jndi/rmi://%s:%d/jmxrmi"; private static final String ssObjName = "org.apache.cassandra.db:type=StorageService"; @@ -175,6 +174,7 @@ public class NodeProbe implements AutoCloseable private boolean failed; protected CIDRFilteringMetricsTableMBean cfmProxy; + private final Map cachedMBeans = new HashMap<>(); /** * Creates a NodeProbe using the specified JMX host, port, username, and password. @@ -233,6 +233,20 @@ protected NodeProbe() this.output = Output.CONSOLE; } + private T cacheProxy(T proxy) + { + cachedMBeans.put(proxy.getClass().getName(), proxy); + return proxy; + } + + @Override + public T getManagementService(Class serviceClass) + { + + return serviceClass.cast(Optional.ofNullable(cachedMBeans.get(serviceClass.getName())) + .orElseThrow(() -> new IllegalArgumentException("No MBean found for " + serviceClass.getName()))); + } + /** * Create a connection to the JMX agent and setup the M[X]Bean proxies. * @@ -262,23 +276,23 @@ protected void connect() throws IOException try { ObjectName name = new ObjectName(ssObjName); - ssProxy = JMX.newMBeanProxy(mbeanServerConn, name, StorageServiceMBean.class); + ssProxy = cacheProxy(JMX.newMBeanProxy(mbeanServerConn, name, StorageServiceMBean.class)); name = new ObjectName(CMSOperations.MBEAN_OBJECT_NAME); - cmsProxy = JMX.newMBeanProxy(mbeanServerConn, name, CMSOperationsMBean.class); + cmsProxy = cacheProxy(JMX.newMBeanProxy(mbeanServerConn, name, CMSOperationsMBean.class)); name = new ObjectName(MessagingService.MBEAN_NAME); - msProxy = JMX.newMBeanProxy(mbeanServerConn, name, MessagingServiceMBean.class); + msProxy = cacheProxy(JMX.newMBeanProxy(mbeanServerConn, name, MessagingServiceMBean.class)); name = new ObjectName(StreamManagerMBean.OBJECT_NAME); - streamProxy = JMX.newMBeanProxy(mbeanServerConn, name, StreamManagerMBean.class); + streamProxy = cacheProxy(JMX.newMBeanProxy(mbeanServerConn, name, StreamManagerMBean.class)); name = new ObjectName(CompactionManager.MBEAN_OBJECT_NAME); - compactionProxy = JMX.newMBeanProxy(mbeanServerConn, name, CompactionManagerMBean.class); + compactionProxy = cacheProxy(JMX.newMBeanProxy(mbeanServerConn, name, CompactionManagerMBean.class)); name = new ObjectName(FailureDetector.MBEAN_NAME); - fdProxy = JMX.newMBeanProxy(mbeanServerConn, name, FailureDetectorMBean.class); + fdProxy = cacheProxy(JMX.newMBeanProxy(mbeanServerConn, name, FailureDetectorMBean.class)); name = new ObjectName(CacheService.MBEAN_NAME); - cacheService = JMX.newMBeanProxy(mbeanServerConn, name, CacheServiceMBean.class); + cacheService = cacheProxy(JMX.newMBeanProxy(mbeanServerConn, name, CacheServiceMBean.class)); name = new ObjectName(StorageProxy.MBEAN_NAME); - spProxy = JMX.newMBeanProxy(mbeanServerConn, name, StorageProxyMBean.class); + spProxy = cacheProxy(JMX.newMBeanProxy(mbeanServerConn, name, StorageProxyMBean.class)); name = new ObjectName(HintsService.MBEAN_NAME); - hsProxy = JMX.newMBeanProxy(mbeanServerConn, name, HintsServiceMBean.class); + hsProxy = cacheProxy(JMX.newMBeanProxy(mbeanServerConn, name, HintsServiceMBean.class)); name = new ObjectName(GCInspector.MBEAN_NAME); gcProxy = JMX.newMBeanProxy(mbeanServerConn, name, GCInspectorMXBean.class); name = new ObjectName(Gossiper.MBEAN_NAME); diff --git a/src/java/org/apache/cassandra/tools/NodeTool.java b/src/java/org/apache/cassandra/tools/NodeTool.java index bd1e302ba066..94a784bfc06d 100644 --- a/src/java/org/apache/cassandra/tools/NodeTool.java +++ b/src/java/org/apache/cassandra/tools/NodeTool.java @@ -337,11 +337,13 @@ public static class CassHelp extends Help implements NodeToolCmdRunnable { public void run(INodeProbeFactory nodeProbeFactory, Output output) { - run(); + StringBuilder sb = new StringBuilder(); + help(global, command, sb); + output.out.println(sb); } } - interface NodeToolCmdRunnable + public interface NodeToolCmdRunnable { void run(INodeProbeFactory nodeProbeFactory, Output output); } diff --git a/src/java/org/apache/cassandra/tools/NodeToolV2.java b/src/java/org/apache/cassandra/tools/NodeToolV2.java new file mode 100644 index 000000000000..bcfb776873c5 --- /dev/null +++ b/src/java/org/apache/cassandra/tools/NodeToolV2.java @@ -0,0 +1,585 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.cassandra.tools; + +import java.io.Console; +import java.io.FileNotFoundException; +import java.io.IOError; +import java.io.IOException; +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Scanner; +import javax.management.InstanceNotFoundException; + +import com.google.common.base.Joiner; +import com.google.common.base.Throwables; + +import io.airlift.airline.Cli; +import io.airlift.airline.Help; +import io.airlift.airline.Option; +import io.airlift.airline.OptionType; +import io.airlift.airline.ParseArgumentsMissingException; +import io.airlift.airline.ParseArgumentsUnexpectedException; +import io.airlift.airline.ParseCommandMissingException; +import io.airlift.airline.ParseCommandUnrecognizedException; +import io.airlift.airline.ParseOptionConversionException; +import io.airlift.airline.ParseOptionMissingException; +import io.airlift.airline.ParseOptionMissingValueException; +import org.apache.cassandra.io.util.File; +import org.apache.cassandra.io.util.FileWriter; +import org.apache.cassandra.management.BaseCommand; +import org.apache.cassandra.management.CassandraHelpLayout; +import org.apache.cassandra.management.api.AbortBootstrap; +import org.apache.cassandra.management.api.Assassinate; +import org.apache.cassandra.management.api.ForceCompact; +import org.apache.cassandra.management.api.JmxConnect; +import org.apache.cassandra.management.api.TopLevelCommand; +import org.apache.cassandra.utils.FBUtilities; +import picocli.CommandLine; + +import static com.google.common.base.Throwables.getStackTraceAsString; +import static com.google.common.collect.Iterables.toArray; +import static com.google.common.collect.Lists.newArrayList; +import static java.lang.Integer.parseInt; +import static java.lang.String.format; +import static org.apache.cassandra.io.util.File.WriteMode.APPEND; +import static org.apache.commons.lang3.ArrayUtils.EMPTY_STRING_ARRAY; +import static org.apache.commons.lang3.StringUtils.EMPTY; +import static org.apache.commons.lang3.StringUtils.isEmpty; +import static org.apache.commons.lang3.StringUtils.isNotEmpty; + +public class NodeToolV2 +{ + static + { + FBUtilities.preventIllegalAccessWarnings(); + } + + private static final String HISTORYFILE = "nodetool.history"; + + private final INodeProbeFactory nodeProbeFactory; + private final Output output; + + public static void main(String... args) + { + System.exit(new NodeToolV2(new NodeProbeFactory(), Output.CONSOLE).execute(args)); + } + + public NodeToolV2(INodeProbeFactory nodeProbeFactory, Output output) + { + this.nodeProbeFactory = nodeProbeFactory; + this.output = output; + } + + public int execute(String... args) + { + List> commands = newArrayList( + CassHelp.class +// CIDRFilteringStats.class, +// Cleanup.class, +// ClearSnapshot.class, +// ClientStats.class, +// Compact.class, +// CompactionHistory.class, +// CompactionStats.class, +// DataPaths.class, +// Decommission.class, +// DescribeCluster.class, +// DescribeRing.class, +// DisableAuditLog.class, +// DisableAutoCompaction.class, +// DisableBackup.class, +// DisableBinary.class, +// DisableFullQueryLog.class, +// DisableGossip.class, +// DisableHandoff.class, +// DisableHintsForDC.class, +// DisableOldProtocolVersions.class, +// Drain.class, +// DropCIDRGroup.class, +// EnableAuditLog.class, +// EnableAutoCompaction.class, +// EnableBackup.class, +// EnableBinary.class, +// EnableFullQueryLog.class, +// EnableGossip.class, +// EnableHandoff.class, +// EnableHintsForDC.class, +// EnableOldProtocolVersions.class, +// FailureDetectorInfo.class, +// Flush.class, +// GarbageCollect.class, +// GcStats.class, +// GetAuditLog.class, +// GetAuthCacheConfig.class, +// GetBatchlogReplayTrottle.class, +// GetCIDRGroupsOfIP.class, +// GetColumnIndexSize.class, +// GetCompactionThreshold.class, +// GetCompactionThroughput.class, +// GetConcurrency.class, +// GetConcurrentCompactors.class, +// GetConcurrentViewBuilders.class, +// GetDefaultKeyspaceRF.class, +// GetEndpoints.class, +// GetFullQueryLog.class, +// GetInterDCStreamThroughput.class, +// GetLoggingLevels.class, +// GetMaxHintWindow.class, +// GetSSTables.class, +// GetSeeds.class, +// GetSnapshotThrottle.class, +// GetStreamThroughput.class, +// GetTimeout.class, +// GetTraceProbability.class, +// GossipInfo.class, +// Import.class, +// Info.class, +// InvalidateCIDRPermissionsCache.class, +// InvalidateCounterCache.class, +// InvalidateCredentialsCache.class, +// InvalidateJmxPermissionsCache.class, +// ReloadCIDRGroupsCache.class, +// InvalidateKeyCache.class, +// InvalidateNetworkPermissionsCache.class, +// InvalidatePermissionsCache.class, +// InvalidateRolesCache.class, +// InvalidateRowCache.class, +// Join.class, +// ListCIDRGroups.class, +// ListPendingHints.class, +// ListSnapshots.class, +// Move.class, +// NetStats.class, +// PauseHandoff.class, +// ProfileLoad.class, +// ProxyHistograms.class, +// RangeKeySample.class, +// Rebuild.class, +// RebuildIndex.class, +// RecompressSSTables.class, +// Refresh.class, +// RefreshSizeEstimates.class, +// ReloadLocalSchema.class, +// ReloadSeeds.class, +// ReloadSslCertificates.class, +// ReloadTriggers.class, +// RelocateSSTables.class, +// RemoveNode.class, +// Repair.class, +// ReplayBatchlog.class, +// ResetFullQueryLog.class, +// ResetLocalSchema.class, +// ResumeHandoff.class, +// Ring.class, +// Scrub.class, +// SetAuthCacheConfig.class, +// SetBatchlogReplayThrottle.class, +// SetCacheCapacity.class, +// SetCacheKeysToSave.class, +// SetColumnIndexSize.class, +// SetCompactionThreshold.class, +// SetCompactionThroughput.class, +// SetConcurrency.class, +// SetConcurrentCompactors.class, +// SetConcurrentViewBuilders.class, +// SetDefaultKeyspaceRF.class, +// SetHintedHandoffThrottleInKB.class, +// SetInterDCStreamThroughput.class, +// SetLoggingLevel.class, +// SetMaxHintWindow.class, +// SetSnapshotThrottle.class, +// SetStreamThroughput.class, +// SetTimeout.class, +// SetTraceProbability.class, +// Sjk.class, +// Snapshot.class, +// Status.class, +// StatusAutoCompaction.class, +// StatusBackup.class, +// StatusBinary.class, +// StatusGossip.class, +// StatusHandoff.class, +// Stop.class, +// StopDaemon.class, +// TableHistograms.class, +// TableStats.class, +// TopPartitions.class, +// TpStats.class, +// TruncateHints.class, +// UpdateCIDRGroup.class, +// UpgradeSSTable.class, +// Verify.class, +// Version.class, +// ViewBuildStatus.class + ); + + List> cliCommands = newArrayList( + AbortBootstrap.class, + Assassinate.class, + ForceCompact.class); + + CommandLine commandLine = new CommandLine(new TopLevelCommand()); +// commandLine.setExecutionStrategy(new CommandLine.RunAll()); + commandLine.addMixin(JmxConnect.MIXIN_KEY, new JmxConnect()); + commandLine.setOut(new PrintWriter(output.out)) + .setErr(new PrintWriter(output.err)) + .setExecutionExceptionHandler((ex, cmdLine, parseResult) -> { + if (ex instanceof CommandLine.ParameterException) + { + output.err.println(ex.getMessage()); + commandLine.usage(output.err); + } + return 1; + }); + + try + { + for (Class commandClass : cliCommands) + commandLine.addSubcommand(commandLine.getFactory().create(commandClass)); + + // This must be after all the subcommands are added to the commandLine. + commandLine.setHelpFactory(CassandraHelpLayout::new); + commandLine.getSubcommands().values().forEach(sub -> sub.addMixin(JmxConnect.MIXIN_KEY, new JmxConnect())); + commandLine.setUsageHelpWidth(CassandraHelpLayout.DEFAULT_USAGE_HELP_WIDTH); + commandLine.setHelpSectionKeys(CassandraHelpLayout.cassandraHelpSectionKeys()); + } + catch (Exception e) + { + err(Throwables.getRootCause(e)); + return 2; + } + +// return commandLine.execute(args); + + Cli.CliBuilder builder = Cli.builder("nodetool"); + + builder.withDescription("Manage your Cassandra cluster") + .withDefaultCommand(CassHelp.class) + .withCommands(commands); + + // bootstrap commands +// builder.withGroup("bootstrap") +// .withDescription("Monitor/manage node's bootstrap process") +// .withDefaultCommand(CassHelp.class) +// .withCommand(BootstrapResume.class); +// +// builder.withGroup("repair_admin") +// .withDescription("list and fail incremental repair sessions") +// .withDefaultCommand(RepairAdmin.ListCmd.class) +// .withCommand(RepairAdmin.ListCmd.class) +// .withCommand(RepairAdmin.CancelCmd.class) +// .withCommand(RepairAdmin.CleanupDataCmd.class) +// .withCommand(RepairAdmin.SummarizePendingCmd.class) +// .withCommand(RepairAdmin.SummarizeRepairedCmd.class); +// +// builder.withGroup("cms") +// .withDescription("Manage cluster metadata") +// .withDefaultCommand(CMSAdmin.DescribeCMS.class) +// .withCommand(CMSAdmin.DescribeCMS.class) +// .withCommand(CMSAdmin.InitializeCMS.class) +// .withCommand(CMSAdmin.ReconfigureCMS.class) +// .withCommand(CMSAdmin.Snapshot.class) +// .withCommand(CMSAdmin.Unregister.class); + + Cli parser = builder.build(); + + int status = 0; + try + { +// NodeToolCmdRunnable parse = parser.parse(args); + printHistory(args); + commandLine.execute(args); +// parse.run(nodeProbeFactory, output); + } catch (IllegalArgumentException | + IllegalStateException | + ParseArgumentsMissingException | + ParseArgumentsUnexpectedException | + ParseOptionConversionException | + ParseOptionMissingException | + ParseOptionMissingValueException | + ParseCommandMissingException | + ParseCommandUnrecognizedException e) + { + badUse(e); + status = 1; + } catch (Throwable throwable) + { + err(Throwables.getRootCause(throwable)); + status = 2; + } + + return status; + } + + private static Object instantiateCommand(Class commandClass) + { + try + { + return commandClass.getDeclaredConstructor().newInstance(); + } + catch (Exception e) + { + throw new RuntimeException("Failed to instantiate command " + commandClass.getName(), e); + } + } + + private static void printHistory(String... args) + { + //don't bother to print if no args passed (meaning, nodetool is just printing out the sub-commands list) + if (args.length == 0) + return; + + String cmdLine = Joiner.on(" ").skipNulls().join(args); + cmdLine = cmdLine.replaceFirst("(?<=(-pw|--password))\\s+\\S+", " "); + + try (FileWriter writer = new File(FBUtilities.getToolsOutputDirectory(), HISTORYFILE).newWriter(APPEND)) + { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS"); + writer.append(sdf.format(new Date())).append(": ").append(cmdLine).append(System.lineSeparator()); + } + catch (IOException | IOError ioe) + { + //quietly ignore any errors about not being able to write out history + } + } + + protected void badUse(Exception e) + { + output.out.println("nodetool: " + e.getMessage()); + output.out.println("See 'nodetool help' or 'nodetool help '."); + } + + protected void err(Throwable e) + { + // CASSANDRA-11537: friendly error message when server is not ready + if (e instanceof InstanceNotFoundException) + throw new IllegalArgumentException("Server is not initialized yet, cannot run nodetool."); + + output.err.println("error: " + e.getMessage()); + output.err.println("-- StackTrace --"); + output.err.println(getStackTraceAsString(e)); + } + + public static class CassHelp extends Help implements NodeToolCmdRunnable + { + public void run(INodeProbeFactory nodeProbeFactory, Output output) + { + run(); + } + } + + interface NodeToolCmdRunnable + { + void run(INodeProbeFactory nodeProbeFactory, Output output); + } + + public static abstract class NodeToolCmd implements NodeToolCmdRunnable + { + + @Option(type = OptionType.GLOBAL, name = {"-h", "--host"}, description = "Node hostname or ip address") + private String host = "127.0.0.1"; + + @Option(type = OptionType.GLOBAL, name = {"-p", "--port"}, description = "Remote jmx agent port number") + private String port = "7199"; + + @Option(type = OptionType.GLOBAL, name = {"-u", "--username"}, description = "Remote jmx agent username") + private String username = EMPTY; + + @Option(type = OptionType.GLOBAL, name = {"-pw", "--password"}, description = "Remote jmx agent password") + private String password = EMPTY; + + @Option(type = OptionType.GLOBAL, name = {"-pwf", "--password-file"}, description = "Path to the JMX password file") + private String passwordFilePath = EMPTY; + + @Option(type = OptionType.GLOBAL, name = { "-pp", "--print-port"}, description = "Operate in 4.0 mode with hosts disambiguated by port number", arity = 0) + protected boolean printPort = false; + + private INodeProbeFactory nodeProbeFactory; + protected Output output; + + @Override + public void run(INodeProbeFactory nodeProbeFactory, Output output) + { + this.nodeProbeFactory = nodeProbeFactory; + this.output = output; + runInternal(); + } + + public void runInternal() + { + if (isNotEmpty(username)) { + if (isNotEmpty(passwordFilePath)) + password = readUserPasswordFromFile(username, passwordFilePath); + + if (isEmpty(password)) + password = promptAndReadPassword(); + } + + try (NodeProbe probe = connect()) + { + execute(probe); + if (probe.isFailed()) + throw new RuntimeException("nodetool failed, check server logs"); + } + catch (IOException e) + { + throw new RuntimeException("Error while closing JMX connection", e); + } + + } + + private String readUserPasswordFromFile(String username, String passwordFilePath) { + String password = EMPTY; + + File passwordFile = new File(passwordFilePath); + try (Scanner scanner = new Scanner(passwordFile.toJavaIOFile()).useDelimiter("\\s+")) + { + while (scanner.hasNextLine()) + { + if (scanner.hasNext()) + { + String jmxRole = scanner.next(); + if (jmxRole.equals(username) && scanner.hasNext()) + { + password = scanner.next(); + break; + } + } + scanner.nextLine(); + } + } + catch (FileNotFoundException e) + { + throw new RuntimeException(e); + } + + return password; + } + + private String promptAndReadPassword() + { + String password = EMPTY; + + Console console = System.console(); + if (console != null) + password = String.valueOf(console.readPassword("Password:")); + + return password; + } + + protected abstract void execute(NodeProbe probe); + + private NodeProbe connect() + { + NodeProbe nodeClient = null; + + try + { + if (username.isEmpty()) + nodeClient = nodeProbeFactory.create(host, parseInt(port)); + else + nodeClient = nodeProbeFactory.create(host, parseInt(port), username, password); + + nodeClient.setOutput(output); + } catch (IOException | SecurityException e) + { + Throwable rootCause = Throwables.getRootCause(e); + output.err.println(format("nodetool: Failed to connect to '%s:%s' - %s: '%s'.", host, port, rootCause.getClass().getSimpleName(), rootCause.getMessage())); + System.exit(1); + } + + return nodeClient; + } + + protected enum KeyspaceSet + { + ALL, NON_SYSTEM, NON_LOCAL_STRATEGY + } + + protected List parseOptionalKeyspace(List cmdArgs, NodeProbe nodeProbe) + { + return parseOptionalKeyspace(cmdArgs, nodeProbe, KeyspaceSet.ALL); + } + + protected List parseOptionalKeyspace(List cmdArgs, NodeProbe nodeProbe, KeyspaceSet defaultKeyspaceSet) + { + List keyspaces = new ArrayList<>(); + + + if (cmdArgs == null || cmdArgs.isEmpty()) + { + if (defaultKeyspaceSet == KeyspaceSet.NON_LOCAL_STRATEGY) + keyspaces.addAll(keyspaces = nodeProbe.getNonLocalStrategyKeyspaces()); + else if (defaultKeyspaceSet == KeyspaceSet.NON_SYSTEM) + keyspaces.addAll(keyspaces = nodeProbe.getNonSystemKeyspaces()); + else + keyspaces.addAll(nodeProbe.getKeyspaces()); + } + else + { + keyspaces.add(cmdArgs.get(0)); + } + + for (String keyspace : keyspaces) + { + if (!nodeProbe.getKeyspaces().contains(keyspace)) + throw new IllegalArgumentException("Keyspace [" + keyspace + "] does not exist."); + } + + return Collections.unmodifiableList(keyspaces); + } + + protected String[] parseOptionalTables(List cmdArgs) + { + return cmdArgs.size() <= 1 ? EMPTY_STRING_ARRAY : toArray(cmdArgs.subList(1, cmdArgs.size()), String.class); + } + + public static String[] parsePartitionKeys(List cmdArgs) + { + return cmdArgs.size() <= 2 ? EMPTY_STRING_ARRAY : toArray(cmdArgs.subList(2, cmdArgs.size()), String.class); + } + } + +// public static SortedMap getOwnershipByDcWithPort(NodeProbe probe, boolean resolveIp, +// Map tokenToEndpoint, +// Map ownerships) +// { +// SortedMap ownershipByDc = Maps.newTreeMap(); +// EndpointSnitchInfoMBean epSnitchInfo = probe.getEndpointSnitchInfoProxy(); +// try +// { +// for (Entry tokenAndEndPoint : tokenToEndpoint.entrySet()) +// { +// String dc = epSnitchInfo.getDatacenter(tokenAndEndPoint.getValue()); +// if (!ownershipByDc.containsKey(dc)) +// ownershipByDc.put(dc, new SetHostStatWithPort(resolveIp)); +// ownershipByDc.get(dc).add(tokenAndEndPoint.getKey(), tokenAndEndPoint.getValue(), ownerships); +// } +// } +// catch (UnknownHostException e) +// { +// throw new RuntimeException(e); +// } +// return ownershipByDc; +// } +} diff --git a/test/unit/org/apache/cassandra/tools/NodeToolSynopsisTest.java b/test/unit/org/apache/cassandra/tools/NodeToolSynopsisTest.java new file mode 100644 index 000000000000..67d7d2bbd4c4 --- /dev/null +++ b/test/unit/org/apache/cassandra/tools/NodeToolSynopsisTest.java @@ -0,0 +1,137 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.cassandra.tools; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; +import org.apache.commons.lang3.StringUtils; +import org.junit.Test; + +import com.github.difflib.DiffUtils; +import com.github.difflib.patch.AbstractDelta; +import com.github.difflib.patch.Patch; +import org.apache.cassandra.cql3.CQLTester; +import org.apache.cassandra.tools.ToolRunner.ToolResult; + +import static org.apache.cassandra.tools.ToolRunner.invokeNodetool; +import static org.junit.Assert.assertTrue; + +public class NodeToolSynopsisTest +{ + private static final Map NODETOOLV2_ENV = ImmutableMap.of("NODETOOL_RUNNER", "org.apache.cassandra.tools.NodeToolV2"); + + @Test + public void cliHelpForcecompact() + { + ToolResult toolHistory = invokeNodetool(List.of("help", "forcecompact")); + toolHistory.assertOnCleanExit(); + String outputV1 = toolHistory.getStdout(); + + toolHistory = invokeNodetool(NODETOOLV2_ENV, List.of("help", "forcecompact")); + String outputV2 = toolHistory.getStdout(); + } + + @Test + public void cliHelp() + { + ToolResult toolHistory = invokeNodetool("help"); + toolHistory.assertOnCleanExit(); + String outputV1 = toolHistory.getStdout(); + + toolHistory = invokeNodetool(NODETOOLV2_ENV, List.of("help")); + String outputV2 = toolHistory.getStdout(); + } + + @Test + public void cliDryRun() throws Exception + { + List args = CQLTester.buildNodetoolArgs(List.of("help", "assassinate")); + args.remove("bin/nodetool"); + ListOutputStream outputV1 = new ListOutputStream(); + ListOutputStream outputV2 = new ListOutputStream(); + + new NodeTool(new NodeProbeFactory(), new Output(new PrintStream(outputV1), new PrintStream(outputV1))) + .execute(args.toArray(new String[0])); + new NodeToolV2(new NodeProbeFactory(), new Output(new PrintStream(outputV2), new PrintStream(outputV2))) + .execute(args.toArray(new String[0])); + + String diff = computeDiff(outputV1.getOutputLines(), outputV2.getOutputLines()); + assertTrue(String.join("\n", outputV1.getOutputLines()) + + '\n' + String.join("\n", outputV2.getOutputLines()) + + '\n' + " difference: " + diff, + StringUtils.isBlank(diff)); + } + + public static String computeDiff(List original, List revised) { + Patch patch = DiffUtils.diff(original, revised); + List diffLines = new ArrayList<>(); + + for (AbstractDelta delta : patch.getDeltas()) { + for (String line : delta.getSource().getLines()) { + diffLines.add(delta.getType().toString().toLowerCase() + " source: " + line); + } + for (String line : delta.getTarget().getLines()) { + diffLines.add(delta.getType().toString().toLowerCase() + " target: " + line); + } + } + + return '\n' + String.join("\n", diffLines); + } + + private static class ListOutputStream extends OutputStream + { + private final List outputLines = new ArrayList<>(); + private final StringBuilder buffer = new StringBuilder(); + + @Override + public void write(int b) + { + char c = (char) b; + if (c == '\n') + { + // Add the buffer to the list if it's a new line + outputLines.add(buffer.toString()); + buffer.setLength(0); // Clear the buffer + } + else + buffer.append(c); + } + + public void flush() + { + if (buffer.length() > 0) + { + outputLines.add(buffer.toString()); + buffer.setLength(0); + } + } + + public List getOutputLines() + { + flush(); + return new ArrayList<>(outputLines); + } + } +} diff --git a/test/unit/org/apache/cassandra/tools/ToolRunner.java b/test/unit/org/apache/cassandra/tools/ToolRunner.java index ad1f80e8127d..13eeadd64c97 100644 --- a/test/unit/org/apache/cassandra/tools/ToolRunner.java +++ b/test/unit/org/apache/cassandra/tools/ToolRunner.java @@ -191,6 +191,11 @@ public static ToolResult invokeNodetool(List args) return invoke(CQLTester.buildNodetoolArgs(args)); } + public static ToolResult invokeNodetool(Map env, List args) + { + return invoke(env, CQLTester.buildNodetoolArgs(args)); + } + public static ToolResult invoke(List args) { return invoke(args.toArray(new String[args.size()])); From 7a0a282a71e65736ecbc901bf2d845aa5ab4fb33 Mon Sep 17 00:00:00 2001 From: Maxim Muzafarov Date: Mon, 17 Jun 2024 21:42:10 +0200 Subject: [PATCH 2/8] top level command help message --- .../management/CassandraHelpCommand.java | 30 ++- .../management/CassandraHelpLayout.java | 199 ++++++++++-------- .../cassandra/management/CommandUtils.java | 19 ++ .../apache/cassandra/tools/NodeToolV2.java | 7 +- .../cassandra/tools/NodeToolSynopsisTest.java | 87 +++++--- 5 files changed, 214 insertions(+), 128 deletions(-) diff --git a/src/java/org/apache/cassandra/management/CassandraHelpCommand.java b/src/java/org/apache/cassandra/management/CassandraHelpCommand.java index 5efd76b8500c..905b46091d15 100644 --- a/src/java/org/apache/cassandra/management/CassandraHelpCommand.java +++ b/src/java/org/apache/cassandra/management/CassandraHelpCommand.java @@ -58,10 +58,11 @@ public void run() CommandLine.Help.ColorScheme colors = colorScheme == null ? CommandLine.Help.defaultColorScheme(CommandLine.Help.Ansi.AUTO) : colorScheme; - // If no input command argument is specified, print help for the top-level command. + if (commands == null) { - printUsage(parent, colors, out); + // If the parent command is the top-level command, print help for the top-level command. + printTopCommandUsage(parent, colors, out); return; } @@ -74,14 +75,33 @@ public void run() if (subcommand == null) throw new CommandLine.ParameterException(parent, "Unknown subcommand '" + commands + "'.", null, commands); - printUsage(subcommand, colors, out); + subcommand.usage(out, colors); } - private static void printUsage(CommandLine command, CommandLine.Help.ColorScheme colors, PrintWriter out) + private static void printTopCommandUsage(CommandLine command, CommandLine.Help.ColorScheme colors, PrintWriter writer) { if (command == null) return; - command.usage(out, colors); + + 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(); } /** diff --git a/src/java/org/apache/cassandra/management/CassandraHelpLayout.java b/src/java/org/apache/cassandra/management/CassandraHelpLayout.java index 76346bf28a4e..c8ce1a7e6732 100644 --- a/src/java/org/apache/cassandra/management/CassandraHelpLayout.java +++ b/src/java/org/apache/cassandra/management/CassandraHelpLayout.java @@ -23,8 +23,10 @@ import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Set; import com.google.common.base.Preconditions; @@ -32,8 +34,8 @@ import org.apache.cassandra.utils.AbstractGuavaIterator; import picocli.CommandLine; -import static com.google.common.collect.ObjectArrays.concat; 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; @@ -64,6 +66,7 @@ public class CassandraHelpLayout extends CommandLine.Help 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 " + @@ -71,6 +74,9 @@ public class CassandraHelpLayout extends CommandLine.Help "command-line options") .arity("0") .build(); + private static final String TOP_LEVEL_SYNOPSIS_LIST_HEADING = "usage:"; + private static final String TOP_LEVEL_COMMAND_HEADING = "The most commonly used nodetool commands are:%n"; + private static final String TOP_LEVEL_SYNOPSIS_SUBCOMMANDS_LABEL = " []"; public CassandraHelpLayout(CommandLine.Model.CommandSpec spec, ColorScheme scheme) { @@ -120,61 +126,86 @@ public String detailedSynopsis(int synopsisHeadingLength, Comparator argsInGroups = new HashSet<>(); Ansi.Text groupsText = createDetailedSynopsisGroupsText(argsInGroups); - List optionsList = createCassandraSynopsisOptionsText(argsInGroups); Ansi.Text endOfOptionsText = createDetailedSynopsisEndOfOptionsText(); Ansi.Text positionalParamText = createCassandraSynopsisPositionalsText(argsInGroups); Ansi.Text commandText = createDetailedSynopsisCommandText(); - CommandLine.Model.CommandSpec commandSpec = commandSpec(); - String parentCommandName = commandSpec.parent().qualifiedName(); - ColorScheme colorScheme = colorScheme(); + boolean isTopLevelCommand = commandSpec.parent() == null; + String parentCommandName = isTopLevelCommand ? "" : commandSpec.parent().qualifiedName(); + int width = commandSpec.usageMessage().width(); - int leadingColumnWidth = parentCommandName.length() + COLUMN_INDENT; - int followingColumnWidth = commandSpec.usageMessage().width() - leadingColumnWidth; - TextTable textTable = TextTable.forColumns(colorScheme, - new Column(leadingColumnWidth, 0, Column.Overflow.TRUNCATE), - new Column(followingColumnWidth, 0, Column.Overflow.WRAP)); + Ansi.Text parentCommandText = isTopLevelCommand ? colorScheme.commandText(commandSpec.name()) + : colorScheme.commandText(parentCommandName); + TextTable textTable = TextTable.forColumns(colorScheme, new Column(width, COLUMN_INDENT, Column.Overflow.WRAP)); + textTable.indentWrappedLines = parentCommandText.plainString().length(); textTable.setAdjustLineBreaksForWideCJKCharacters(commandSpec.usageMessage().adjustLineBreaksForWideCJKCharacters()); - textTable.indentWrappedLines = 0; - Ansi.Text emptyCell = Ansi.OFF.new Text(leadingSpaces(leadingColumnWidth), colorScheme); - Ansi.Text cmdPadding = Ansi.OFF.new Text(leadingSpaces(COLUMN_INDENT), colorScheme); - Ansi.Text parentCommandText = cmdPadding.concat(colorScheme.commandText(parentCommandName)).concat(" "); // All other fields added to the synopsis are left-adjusted, so we don't need to align them. - Ansi.Text text = groupsText.concat(" ") - .concat(commandSpec.name()) - .concat(endOfOptionsText) - .concat(" ") - .concat(positionalParamText) - .concat(commandText); - - LineBreakingOptionsIterator iter = new LineBreakingOptionsIterator(optionsList.iterator(), followingColumnWidth); - boolean commandTextNotAdded = true; - while (iter.hasNext()) + Ansi.Text text; + if (isTopLevelCommand) + { + text = groupsText.concat(" ") + .concat(TOP_LEVEL_SYNOPSIS_LIST_HEADING) + .concat(" ") + .concat(positionalParamText) + .concat(commandText); + + textTable.addRowValues(text); + textTable.addEmptyRow(); + } + else { - Ansi.Text row = iter.next(); - Ansi.Text leadingCell = emptyCell; + text = groupsText.concat(" ").concat(commandSpec.name()).concat(endOfOptionsText).concat(" ") + .concat(positionalParamText).concat(commandText); + } - if (commandTextNotAdded) - { - leadingCell = parentCommandText; - row = colorScheme.text(" ").concat(row); - commandTextNotAdded = false; - } + Ansi.Text padding = Ansi.OFF.new Text(leadingSpaces(parentCommandText.plainString().length()), colorScheme); + List alignedOptions = alignByWidth(optionsList, + width - COLUMN_INDENT - textTable.indentWrappedLines, + colorScheme); + // Align options by width + for (int i = 0; i < alignedOptions.size(); i++) + { + Ansi.Text option = alignedOptions.get(i); + if (i == 0) + option = parentCommandText.concat(" ").concat(option); + else + option = padding.concat(option); - if (iter.hasNext()) - textTable.addRowValues(leadingCell, row); + if (i == alignedOptions.size() - 1) + textTable.addRowValues(option.concat(text)); else - textTable.addRowValues(leadingCell, row.concat(text)); + textTable.addRowValues(option); } - textTable.addRowValues(Ansi.OFF.new Text("", colorScheme)); + 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()); @@ -290,12 +321,43 @@ public String parameterList() 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); } + public String topLevelCommandListHeading(Object... params) { + return createHeading(TOP_LEVEL_COMMAND_HEADING, params); + } + private static List cassandraPositionals(CommandLine.Model.CommandSpec commandSpec) { List positionals = new ArrayList<>(commandSpec.positionalParameters()); @@ -343,54 +405,25 @@ public static List cassandraHelpSectionKeys() return result; } - private static class LineBreakingOptionsIterator extends AbstractGuavaIterator + /** + * 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) { - private final Iterator optionsIterator; - private final int width; - private Ansi.Text prev; - - LineBreakingOptionsIterator(Iterator optionsIterator, int width) - { - this.optionsIterator = optionsIterator; - this.width = width; - } - - @Override - protected Ansi.Text computeNext() - { - while (optionsIterator.hasNext()) - { - Ansi.Text next = optionsIterator.next(); - if (prev == null) - prev = next; - - Ansi.Text curr; - if (prev == next) - curr = next; - else - curr = prev.concat(" ").concat(next); - - if (curr.plainString().length() > width) - { - Ansi.Text result = prev; - prev = next; - return result; - } - else - { - prev = curr; - } - } - - if (prev == null) - return endOfData(); - else - { - Ansi.Text result = prev; - prev = null; - return result; - } - } + 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, help -> help.synopsis(help.synopsisHeadingLength())); + 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, diff --git a/src/java/org/apache/cassandra/management/CommandUtils.java b/src/java/org/apache/cassandra/management/CommandUtils.java index 86fc0d8c3cc0..27efcd9f2bc9 100644 --- a/src/java/org/apache/cassandra/management/CommandUtils.java +++ b/src/java/org/apache/cassandra/management/CommandUtils.java @@ -19,6 +19,7 @@ package org.apache.cassandra.management; import java.util.Arrays; +import java.util.Collection; public final class CommandUtils { @@ -36,4 +37,22 @@ public static String leadingSpaces(int 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 boolean empty(String str) + { + return str == null || str.trim().isEmpty(); + } + + private static boolean empty(Object[] array) + { + return array == null || array.length == 0; + } } diff --git a/src/java/org/apache/cassandra/tools/NodeToolV2.java b/src/java/org/apache/cassandra/tools/NodeToolV2.java index bcfb776873c5..83135a29bb18 100644 --- a/src/java/org/apache/cassandra/tools/NodeToolV2.java +++ b/src/java/org/apache/cassandra/tools/NodeToolV2.java @@ -244,11 +244,8 @@ public int execute(String... args) commandLine.setOut(new PrintWriter(output.out)) .setErr(new PrintWriter(output.err)) .setExecutionExceptionHandler((ex, cmdLine, parseResult) -> { - if (ex instanceof CommandLine.ParameterException) - { - output.err.println(ex.getMessage()); - commandLine.usage(output.err); - } + output.err.println(ex.getMessage()); + commandLine.usage(output.err); return 1; }); diff --git a/test/unit/org/apache/cassandra/tools/NodeToolSynopsisTest.java b/test/unit/org/apache/cassandra/tools/NodeToolSynopsisTest.java index 67d7d2bbd4c4..c7ad71164066 100644 --- a/test/unit/org/apache/cassandra/tools/NodeToolSynopsisTest.java +++ b/test/unit/org/apache/cassandra/tools/NodeToolSynopsisTest.java @@ -18,14 +18,12 @@ package org.apache.cassandra.tools; -import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.util.ArrayList; import java.util.List; -import java.util.Map; +import java.util.function.BiFunction; -import com.google.common.collect.ImmutableMap; import org.apache.commons.lang3.StringUtils; import org.junit.Test; @@ -33,55 +31,74 @@ import com.github.difflib.patch.AbstractDelta; import com.github.difflib.patch.Patch; import org.apache.cassandra.cql3.CQLTester; -import org.apache.cassandra.tools.ToolRunner.ToolResult; -import static org.apache.cassandra.tools.ToolRunner.invokeNodetool; import static org.junit.Assert.assertTrue; public class NodeToolSynopsisTest { - private static final Map NODETOOLV2_ENV = ImmutableMap.of("NODETOOL_RUNNER", "org.apache.cassandra.tools.NodeToolV2"); - @Test - public void cliHelpForcecompact() + public void cliHelp() { - ToolResult toolHistory = invokeNodetool(List.of("help", "forcecompact")); - toolHistory.assertOnCleanExit(); - String outputV1 = toolHistory.getStdout(); + List outNodeTool = invokeNodetool(NodeTool::new, "help"); + List outNodeToolV2 = invokeNodetool(NodeToolV2::new, "help"); + + String diff = computeDiff(outNodeTool, outNodeToolV2); + assertTrue(concatNodetoolOutput(outNodeTool) + + '\n' + "-----------------------------------------------------" + + '\n' + concatNodetoolOutput(outNodeToolV2) + + '\n' + "Difference for \"" + "help" + "\":" + diff, + StringUtils.isBlank(diff)); + } - toolHistory = invokeNodetool(NODETOOLV2_ENV, List.of("help", "forcecompact")); - String outputV2 = toolHistory.getStdout(); + @Test + public void dummy() + { + List outNodeToolV2 = invokeNodetool(NodeToolV2::new, "help"); + System.out.println(concatNodetoolOutput(outNodeToolV2)); } @Test - public void cliHelp() + public void compareNodeToolHelpOutput() throws Exception { - ToolResult toolHistory = invokeNodetool("help"); - toolHistory.assertOnCleanExit(); - String outputV1 = toolHistory.getStdout(); +// runCommandHelpOutputComparison("abortbootstrap"); + runCommandHelpOutputComparison("assassinate"); + runCommandHelpOutputComparison("forcecompact"); + } - toolHistory = invokeNodetool(NODETOOLV2_ENV, List.of("help")); - String outputV2 = toolHistory.getStdout(); + public void runCommandHelpOutputComparison(String commandName) + { + List outNodeTool = invokeNodetool(NodeTool::new, "help", commandName); + List outNodeToolV2 = invokeNodetool(NodeToolV2::new, "help", commandName); + String diff = computeDiff(outNodeTool, outNodeToolV2); + assertTrue(concatNodetoolOutput(outNodeTool) + + '\n' + "-----------------------------------------------------" + + '\n' + concatNodetoolOutput(outNodeToolV2) + + '\n' + " difference for \"" + commandName + "\":" + diff, + StringUtils.isBlank(diff)); } - @Test - public void cliDryRun() throws Exception + private static String concatNodetoolOutput(List output) { - List args = CQLTester.buildNodetoolArgs(List.of("help", "assassinate")); + return '\n' + String.join("\n", output); + } + private static List invokeNodetool(BiFunction factory, String... commands) + { + ListOutputStream output = new ListOutputStream(); + List args = CQLTester.buildNodetoolArgs(List.of(commands)); args.remove("bin/nodetool"); - ListOutputStream outputV1 = new ListOutputStream(); - ListOutputStream outputV2 = new ListOutputStream(); - - new NodeTool(new NodeProbeFactory(), new Output(new PrintStream(outputV1), new PrintStream(outputV1))) - .execute(args.toArray(new String[0])); - new NodeToolV2(new NodeProbeFactory(), new Output(new PrintStream(outputV2), new PrintStream(outputV2))) - .execute(args.toArray(new String[0])); - - String diff = computeDiff(outputV1.getOutputLines(), outputV2.getOutputLines()); - assertTrue(String.join("\n", outputV1.getOutputLines()) + - '\n' + String.join("\n", outputV2.getOutputLines()) + - '\n' + " difference: " + diff, - StringUtils.isBlank(diff)); + try + { + Object runner = factory.apply(new NodeProbeFactory(), new Output(new PrintStream(output), new PrintStream(output))); + Object result = runner.getClass().getMethod("execute", String[].class) + .invoke(runner, new Object[] { args.toArray(new String[0]) }); + if (result instanceof Integer && (Integer) result != 0) + throw new RuntimeException("Command failed with exit code " + result); + return output.getOutputLines(); + } + catch (Exception e) + { + throw new RuntimeException(e); + } } public static String computeDiff(List original, List revised) { From f641bb94531c37c257f441e9ae38f05f1c09c87a Mon Sep 17 00:00:00 2001 From: Maxim Muzafarov Date: Tue, 18 Jun 2024 15:41:27 +0200 Subject: [PATCH 3/8] align help and command nodetool outputs --- .../management/CassandraHelpLayout.java | 77 +++++++++---------- 1 file changed, 37 insertions(+), 40 deletions(-) diff --git a/src/java/org/apache/cassandra/management/CassandraHelpLayout.java b/src/java/org/apache/cassandra/management/CassandraHelpLayout.java index c8ce1a7e6732..85d1cccf406b 100644 --- a/src/java/org/apache/cassandra/management/CassandraHelpLayout.java +++ b/src/java/org/apache/cassandra/management/CassandraHelpLayout.java @@ -22,16 +22,12 @@ import java.util.Collection; import java.util.Comparator; import java.util.HashSet; -import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; -import com.google.common.base.Preconditions; - -import org.apache.cassandra.utils.AbstractGuavaIterator; import picocli.CommandLine; import static org.apache.cassandra.management.CommandUtils.leadingSpaces; @@ -74,9 +70,9 @@ public class CassandraHelpLayout extends CommandLine.Help "command-line options") .arity("0") .build(); - private static final String TOP_LEVEL_SYNOPSIS_LIST_HEADING = "usage:"; + 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 TOP_LEVEL_SYNOPSIS_SUBCOMMANDS_LABEL = " []"; + private static final String SYNOPSIS_SUBCOMMANDS_LABEL = " []"; public CassandraHelpLayout(CommandLine.Model.CommandSpec spec, ColorScheme scheme) { @@ -118,13 +114,23 @@ public String synopsisHeading(Object... params) } @Override - public String detailedSynopsis(int synopsisHeadingLength, Comparator optionSort, boolean clusterBooleanOptions) + public String synopsis(int synopsisHeadingLength) + { + return printDetailedSynopsis("", COLUMN_INDENT, true); + } + + private Ansi.Text createCassandraSynopsisCommandText() { - Preconditions.checkState(synopsisHeadingLength >= 0, - "synopsisHeadingLength must be a positive number but was " + synopsisHeadingLength); + 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(true); + commandSpec().usageMessage().showEndOfOptionsDelimiterInUsageHelp(showEndOfOptionsDelimiter); CommandLine.Model.CommandSpec commandSpec = commandSpec(); ColorScheme colorScheme = colorScheme(); @@ -134,47 +140,33 @@ public String detailedSynopsis(int synopsisHeadingLength, Comparator optionsList = createCassandraSynopsisOptionsText(argsInGroups); Ansi.Text endOfOptionsText = createDetailedSynopsisEndOfOptionsText(); Ansi.Text positionalParamText = createCassandraSynopsisPositionalsText(argsInGroups); - Ansi.Text commandText = createDetailedSynopsisCommandText(); + Ansi.Text commandText = createCassandraSynopsisCommandText(); - boolean isTopLevelCommand = commandSpec.parent() == null; - String parentCommandName = isTopLevelCommand ? "" : commandSpec.parent().qualifiedName(); int width = commandSpec.usageMessage().width(); - - Ansi.Text parentCommandText = isTopLevelCommand ? colorScheme.commandText(commandSpec.name()) - : colorScheme.commandText(parentCommandName); - TextTable textTable = TextTable.forColumns(colorScheme, new Column(width, COLUMN_INDENT, Column.Overflow.WRAP)); - textTable.indentWrappedLines = parentCommandText.plainString().length(); + 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; - if (isTopLevelCommand) - { - text = groupsText.concat(" ") - .concat(TOP_LEVEL_SYNOPSIS_LIST_HEADING) - .concat(" ") - .concat(positionalParamText) - .concat(commandText); - - textTable.addRowValues(text); - textTable.addEmptyRow(); - } - else - { - text = groupsText.concat(" ").concat(commandSpec.name()).concat(endOfOptionsText).concat(" ") - .concat(positionalParamText).concat(commandText); - } - - Ansi.Text padding = Ansi.OFF.new Text(leadingSpaces(parentCommandText.plainString().length()), colorScheme); + 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 - COLUMN_INDENT - textTable.indentWrappedLines, + 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 = parentCommandText.concat(" ").concat(option); + option = colorScheme.text(synopsisPrefix).concat(synopsisPrefix.isEmpty() ? "" : " ") + .concat(mainCommandText).concat(" ") + .concat(option); else option = padding.concat(option); @@ -358,6 +350,11 @@ 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()); @@ -416,7 +413,7 @@ public static Map cassandraTopLevelHel 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, help -> help.synopsis(help.synopsisHeadingLength())); + 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); From 80dfba026533d6630d72d9cbe5ad33b00d7cca3f Mon Sep 17 00:00:00 2001 From: Maxim Muzafarov Date: Wed, 19 Jun 2024 23:27:47 +0200 Subject: [PATCH 4/8] init context for nodetool2 --- .../cassandra/management/BaseCommand.java | 23 +++- .../cassandra/management/CommandUtils.java | 21 ++- ...agementContext.java => ServiceBridge.java} | 4 +- .../management/api/AbortBootstrap.java | 8 +- .../cassandra/management/api/Assassinate.java | 8 +- .../cassandra/management/api/Compact.java | 108 +++++++++++++++ .../management/api/ForceCompact.java | 8 +- .../cassandra/management/api/JmxConnect.java | 92 ------------- .../management/api/JmxConnectionMixin.java | 129 ++++++++++++++++++ .../org/apache/cassandra/tools/NodeProbe.java | 11 +- .../org/apache/cassandra/tools/NodeTool.java | 57 ++++---- .../apache/cassandra/tools/NodeToolV2.java | 62 +++++++-- .../cassandra/tools/NodeToolSynopsisTest.java | 6 +- .../apache/cassandra/tools/ToolRunner.java | 30 ++++ .../cassandra/tools/nodetool/CompactTest.java | 10 +- 15 files changed, 412 insertions(+), 165 deletions(-) rename src/java/org/apache/cassandra/management/{ManagementContext.java => ServiceBridge.java} (91%) create mode 100644 src/java/org/apache/cassandra/management/api/Compact.java delete mode 100644 src/java/org/apache/cassandra/management/api/JmxConnect.java create mode 100644 src/java/org/apache/cassandra/management/api/JmxConnectionMixin.java diff --git a/src/java/org/apache/cassandra/management/BaseCommand.java b/src/java/org/apache/cassandra/management/BaseCommand.java index aa22619854af..3de461bb12f5 100644 --- a/src/java/org/apache/cassandra/management/BaseCommand.java +++ b/src/java/org/apache/cassandra/management/BaseCommand.java @@ -18,21 +18,34 @@ package org.apache.cassandra.management; -import javax.inject.Inject; +import picocli.CommandLine; /** * Base class for all nodetool commands. */ public abstract class BaseCommand implements Runnable { - @Inject - protected ManagementContext probe; + /** 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(probe); + execute(bridge); } - protected abstract void execute(ManagementContext probe); + protected abstract void execute(ServiceBridge probe); } diff --git a/src/java/org/apache/cassandra/management/CommandUtils.java b/src/java/org/apache/cassandra/management/CommandUtils.java index 27efcd9f2bc9..05ece3c86151 100644 --- a/src/java/org/apache/cassandra/management/CommandUtils.java +++ b/src/java/org/apache/cassandra/management/CommandUtils.java @@ -18,8 +18,21 @@ package org.apache.cassandra.management; +import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ExecutionException; + +import org.apache.cassandra.db.compaction.CompactionManagerMBean; +import org.apache.cassandra.service.StorageServiceMBean; +import org.apache.cassandra.tools.NodeProbe; +import org.apache.cassandra.tools.NodeTool; + +import static com.google.common.collect.Iterables.toArray; +import static org.apache.commons.lang3.ArrayUtils.EMPTY_STRING_ARRAY; public final class CommandUtils { @@ -46,13 +59,13 @@ public static int maxLength(Collection any) return result; } - public static boolean empty(String str) + public static StorageServiceMBean ssProxy(ServiceBridge bridge) { - return str == null || str.trim().isEmpty(); + return bridge.mBean(StorageServiceMBean.class); } - private static boolean empty(Object[] array) + public static CompactionManagerMBean cmProxy(ServiceBridge bridge) { - return array == null || array.length == 0; + return bridge.mBean(CompactionManagerMBean.class); } } diff --git a/src/java/org/apache/cassandra/management/ManagementContext.java b/src/java/org/apache/cassandra/management/ServiceBridge.java similarity index 91% rename from src/java/org/apache/cassandra/management/ManagementContext.java rename to src/java/org/apache/cassandra/management/ServiceBridge.java index cddfcb81abef..80edce06824a 100644 --- a/src/java/org/apache/cassandra/management/ManagementContext.java +++ b/src/java/org/apache/cassandra/management/ServiceBridge.java @@ -21,7 +21,7 @@ /** * Management context for nodetool commands to access management services like StorageServiceMBean etc. */ -public interface ManagementContext +public interface ServiceBridge { - T getManagementService(Class serviceClass); + 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 index d741f9dce195..e5ce36506a4b 100644 --- a/src/java/org/apache/cassandra/management/api/AbortBootstrap.java +++ b/src/java/org/apache/cassandra/management/api/AbortBootstrap.java @@ -18,11 +18,11 @@ package org.apache.cassandra.management.api; import org.apache.cassandra.management.BaseCommand; -import org.apache.cassandra.management.ManagementContext; -import org.apache.cassandra.service.StorageServiceMBean; +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; @@ -36,12 +36,12 @@ public class AbortBootstrap extends BaseCommand private String endpoint = EMPTY; @Override - public void execute(ManagementContext probe) + 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"); - probe.getManagementService(StorageServiceMBean.class).abortBootstrap(nodeId, endpoint); + 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 index 0ecb7696436a..93bdb58b0de7 100644 --- a/src/java/org/apache/cassandra/management/api/Assassinate.java +++ b/src/java/org/apache/cassandra/management/api/Assassinate.java @@ -18,10 +18,10 @@ package org.apache.cassandra.management.api; import org.apache.cassandra.management.BaseCommand; -import org.apache.cassandra.management.ManagementContext; -import org.apache.cassandra.service.StorageServiceMBean; +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") @@ -31,8 +31,8 @@ public class Assassinate extends BaseCommand private String ip_address = EMPTY; @Override - public void execute(ManagementContext probe) + public void execute(ServiceBridge probe) { - probe.getManagementService(StorageServiceMBean.class).assassinateEndpoint(ip_address); + ssProxy(probe).assassinateEndpoint(ip_address); } } 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..a5596b8c357a --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/Compact.java @@ -0,0 +1,108 @@ +/* + * 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.db.compaction.CompactionManagerMBean; +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 ...", arity = "0..*", + description = "The keyspace followed by one or many tables or list of SSTable data files when using --user-defined") + private List args = new ArrayList<>(); + + @CommandLine.Option(names = { "-s", "--split-output"}, description = "Use -s to not create a single big file") + private boolean splitOutput = false; + + @CommandLine.Option(names = { "--user-defined"}, description = "Use --user-defined to submit listed files for user-defined compaction") + private boolean userDefined = false; + + @CommandLine.Option(names = { "-st", "--start-token"}, description = "Use -st to specify a token at which the compaction range starts (inclusive)") + private String startToken = EMPTY; + + @CommandLine.Option(names = { "-et", "--end-token"}, description = "Use -et to specify a token at which compaction range ends (inclusive)") + private String endToken = EMPTY; + + @CommandLine.Option(names = { "--partition"}, description = "String representation of the partition key") + private 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/ForceCompact.java b/src/java/org/apache/cassandra/management/api/ForceCompact.java index aad5e7bed1c0..aa758b8e4eb8 100644 --- a/src/java/org/apache/cassandra/management/api/ForceCompact.java +++ b/src/java/org/apache/cassandra/management/api/ForceCompact.java @@ -24,11 +24,11 @@ import org.apache.cassandra.management.BaseCommand; import org.apache.cassandra.management.CommandUtils; -import org.apache.cassandra.management.ManagementContext; -import org.apache.cassandra.service.StorageServiceMBean; +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.NodeToolV2.NodeToolCmd.parsePartitionKeys; @CommandLine.Command(name = "forcecompact", description = "Force a (major) compaction on a table") @@ -49,7 +49,7 @@ public class ForceCompact extends BaseCommand private String[] keys; @Override - public void execute(ManagementContext probe) + public void execute(ServiceBridge probe) { args = Lists.asList(keyspace, table, keys); // Check if the input has valid size @@ -62,7 +62,7 @@ public void execute(ManagementContext probe) try { - probe.getManagementService(StorageServiceMBean.class).forceCompactionKeysIgnoringGcGrace(keyspaceName, tableName, partitionKeysIgnoreGcGrace); + ssProxy(probe).forceCompactionKeysIgnoringGcGrace(keyspaceName, tableName, partitionKeysIgnoreGcGrace); } catch (Exception e) { diff --git a/src/java/org/apache/cassandra/management/api/JmxConnect.java b/src/java/org/apache/cassandra/management/api/JmxConnect.java deleted file mode 100644 index f169b523be80..000000000000 --- a/src/java/org/apache/cassandra/management/api/JmxConnect.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * 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 javax.inject.Inject; - -import com.google.common.base.Throwables; - -import org.apache.cassandra.management.ManagementContext; -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.commons.lang3.StringUtils.EMPTY; - -/** - * Command options for NodeTool commands that are executed via JMX. - */ -@CommandLine.Command(name = "connect", description = "Connect NodeTool to a Cassandra node using JMX") -public class JmxConnect implements Runnable -{ - public static final String MIXIN_KEY = "jmx"; - - @CommandLine.Option(names = { "-h", "--host"}, description = "Node hostname or ip address") - private String host = "127.0.0.1"; - - @CommandLine.Option(names = {"-p", "--port"}, description = "Remote jmx agent port number") - private String port = "7199"; - - @CommandLine.Option(names = {"-u", "--username"}, description = "Remote jmx agent username") - private String username = EMPTY; - - @CommandLine.Option(names = {"-pw", "--password"}, description = "Remote jmx agent password") - private String password = EMPTY; - - @CommandLine.Option(names = {"-pwf", "--password-file"}, description = "Path to the JMX password file") - private String passwordFilePath = EMPTY; - - @CommandLine.Option(names = { "-pp", "--print-port"}, description = "Operate in 4.0 mode with hosts disambiguated by port number") - private boolean printPort = false; - - @Inject - private INodeProbeFactory nodeProbeFactory; - @Inject - private Output output; - public ManagementContext nodeClient; - - public ManagementContext init() - { - try - { - if (username.isEmpty()) - nodeClient = nodeProbeFactory.create(host, parseInt(port)); - else - nodeClient = nodeProbeFactory.create(host, parseInt(port), username, password); - - return nodeClient; - } - 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()); - } - - return null; - } - - @Override - public void run() - { - // no-op - } -} 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..4439f9afaf98 --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/JmxConnectionMixin.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.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. + */ +@CommandLine.Command(name = "connect", description = "Connect NodeTool to a Cassandra node using 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); + Preconditions.checkNotNull(jmx, "No JmxConnect mixin found in the command hierarchy"); + + ((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/tools/NodeProbe.java b/src/java/org/apache/cassandra/tools/NodeProbe.java index 1151fc685c02..f5268fca37e8 100644 --- a/src/java/org/apache/cassandra/tools/NodeProbe.java +++ b/src/java/org/apache/cassandra/tools/NodeProbe.java @@ -105,7 +105,7 @@ import org.apache.cassandra.metrics.StorageMetrics; import org.apache.cassandra.metrics.TableMetrics; import org.apache.cassandra.metrics.ThreadPoolMetrics; -import org.apache.cassandra.management.ManagementContext; +import org.apache.cassandra.management.ServiceBridge; import org.apache.cassandra.net.MessagingService; import org.apache.cassandra.net.MessagingServiceMBean; import org.apache.cassandra.service.ActiveRepairServiceMBean; @@ -131,7 +131,7 @@ /** * JMX client operations for Cassandra. */ -public class NodeProbe implements AutoCloseable, ManagementContext +public class NodeProbe implements AutoCloseable, ServiceBridge { private static final String fmtUrl = "service:jmx:rmi:///jndi/rmi://%s:%d/jmxrmi"; private static final String ssObjName = "org.apache.cassandra.db:type=StorageService"; @@ -240,11 +240,10 @@ private T cacheProxy(T proxy) } @Override - public T getManagementService(Class serviceClass) + public T mBean(Class clazz) { - - return serviceClass.cast(Optional.ofNullable(cachedMBeans.get(serviceClass.getName())) - .orElseThrow(() -> new IllegalArgumentException("No MBean found for " + serviceClass.getName()))); + return clazz.cast(Optional.ofNullable(cachedMBeans.get(clazz.getName())) + .orElseThrow(() -> new IllegalArgumentException("No MBean found for " + clazz.getName()))); } /** diff --git a/src/java/org/apache/cassandra/tools/NodeTool.java b/src/java/org/apache/cassandra/tools/NodeTool.java index 94a784bfc06d..d4bc7e130b7d 100644 --- a/src/java/org/apache/cassandra/tools/NodeTool.java +++ b/src/java/org/apache/cassandra/tools/NodeTool.java @@ -17,20 +17,7 @@ */ package org.apache.cassandra.tools; -import static com.google.common.base.Throwables.getStackTraceAsString; -import static com.google.common.collect.Iterables.toArray; -import static com.google.common.collect.Lists.newArrayList; -import static java.lang.Integer.parseInt; -import static java.lang.String.format; -import static org.apache.cassandra.io.util.File.WriteMode.APPEND; -import static org.apache.commons.lang3.ArrayUtils.EMPTY_STRING_ARRAY; -import static org.apache.commons.lang3.StringUtils.EMPTY; -import static org.apache.commons.lang3.StringUtils.isEmpty; -import static org.apache.commons.lang3.StringUtils.isNotEmpty; - import java.io.Console; -import org.apache.cassandra.io.util.File; -import org.apache.cassandra.io.util.FileWriter; import java.io.FileNotFoundException; import java.io.IOError; import java.io.IOException; @@ -44,16 +31,10 @@ import java.util.Map.Entry; import java.util.Scanner; import java.util.SortedMap; - import javax.management.InstanceNotFoundException; import com.google.common.base.Joiner; import com.google.common.base.Throwables; - -import org.apache.cassandra.locator.EndpointSnitchInfoMBean; -import org.apache.cassandra.tools.nodetool.*; -import org.apache.cassandra.utils.FBUtilities; - import com.google.common.collect.Maps; import io.airlift.airline.Cli; @@ -67,6 +48,24 @@ import io.airlift.airline.ParseOptionConversionException; import io.airlift.airline.ParseOptionMissingException; import io.airlift.airline.ParseOptionMissingValueException; +import org.apache.cassandra.io.util.File; +import org.apache.cassandra.io.util.FileWriter; +import org.apache.cassandra.locator.EndpointSnitchInfoMBean; +import org.apache.cassandra.management.ServiceBridge; +import org.apache.cassandra.tools.nodetool.*; +import org.apache.cassandra.utils.FBUtilities; + +import static com.google.common.base.Throwables.getStackTraceAsString; +import static com.google.common.collect.Iterables.toArray; +import static com.google.common.collect.Lists.newArrayList; +import static java.lang.Integer.parseInt; +import static java.lang.String.format; +import static org.apache.cassandra.io.util.File.WriteMode.APPEND; +import static org.apache.cassandra.management.CommandUtils.ssProxy; +import static org.apache.commons.lang3.ArrayUtils.EMPTY_STRING_ARRAY; +import static org.apache.commons.lang3.StringUtils.EMPTY; +import static org.apache.commons.lang3.StringUtils.isEmpty; +import static org.apache.commons.lang3.StringUtils.isNotEmpty; public class NodeTool { @@ -403,7 +402,7 @@ public void runInternal() } - private String readUserPasswordFromFile(String username, String passwordFilePath) { + public static String readUserPasswordFromFile(String username, String passwordFilePath) { String password = EMPTY; File passwordFile = new File(passwordFilePath); @@ -431,7 +430,7 @@ private String readUserPasswordFromFile(String username, String passwordFilePath return password; } - private String promptAndReadPassword() + public static String promptAndReadPassword() { String password = EMPTY; @@ -471,12 +470,12 @@ protected enum KeyspaceSet ALL, NON_SYSTEM, NON_LOCAL_STRATEGY } - protected List parseOptionalKeyspace(List cmdArgs, NodeProbe nodeProbe) + public static List parseOptionalKeyspace(List cmdArgs, ServiceBridge nodeProbe) { return parseOptionalKeyspace(cmdArgs, nodeProbe, KeyspaceSet.ALL); } - protected List parseOptionalKeyspace(List cmdArgs, NodeProbe nodeProbe, KeyspaceSet defaultKeyspaceSet) + public static List parseOptionalKeyspace(List cmdArgs, ServiceBridge nodeProbe, KeyspaceSet defaultKeyspaceSet) { List keyspaces = new ArrayList<>(); @@ -484,11 +483,11 @@ protected List parseOptionalKeyspace(List cmdArgs, NodeProbe nod if (cmdArgs == null || cmdArgs.isEmpty()) { if (defaultKeyspaceSet == KeyspaceSet.NON_LOCAL_STRATEGY) - keyspaces.addAll(keyspaces = nodeProbe.getNonLocalStrategyKeyspaces()); + keyspaces.addAll(keyspaces = ssProxy(nodeProbe).getNonLocalStrategyKeyspaces()); else if (defaultKeyspaceSet == KeyspaceSet.NON_SYSTEM) - keyspaces.addAll(keyspaces = nodeProbe.getNonSystemKeyspaces()); + keyspaces.addAll(keyspaces = ssProxy(nodeProbe).getNonSystemKeyspaces()); else - keyspaces.addAll(nodeProbe.getKeyspaces()); + keyspaces.addAll(ssProxy(nodeProbe).getKeyspaces()); } else { @@ -497,19 +496,19 @@ else if (defaultKeyspaceSet == KeyspaceSet.NON_SYSTEM) for (String keyspace : keyspaces) { - if (!nodeProbe.getKeyspaces().contains(keyspace)) + if (!ssProxy(nodeProbe).getKeyspaces().contains(keyspace)) throw new IllegalArgumentException("Keyspace [" + keyspace + "] does not exist."); } return Collections.unmodifiableList(keyspaces); } - protected String[] parseOptionalTables(List cmdArgs) + public static String[] parseOptionalTables(List cmdArgs) { return cmdArgs.size() <= 1 ? EMPTY_STRING_ARRAY : toArray(cmdArgs.subList(1, cmdArgs.size()), String.class); } - protected String[] parsePartitionKeys(List cmdArgs) + public static String[] parsePartitionKeys(List cmdArgs) { return cmdArgs.size() <= 2 ? EMPTY_STRING_ARRAY : toArray(cmdArgs.subList(2, cmdArgs.size()), String.class); } diff --git a/src/java/org/apache/cassandra/tools/NodeToolV2.java b/src/java/org/apache/cassandra/tools/NodeToolV2.java index 83135a29bb18..a8ed8fdea8a9 100644 --- a/src/java/org/apache/cassandra/tools/NodeToolV2.java +++ b/src/java/org/apache/cassandra/tools/NodeToolV2.java @@ -22,12 +22,14 @@ import java.io.IOError; import java.io.IOException; import java.io.PrintWriter; +import java.lang.reflect.Field; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Scanner; +import javax.inject.Inject; import javax.management.InstanceNotFoundException; import com.google.common.base.Joiner; @@ -50,8 +52,9 @@ import org.apache.cassandra.management.CassandraHelpLayout; import org.apache.cassandra.management.api.AbortBootstrap; import org.apache.cassandra.management.api.Assassinate; +import org.apache.cassandra.management.api.Compact; import org.apache.cassandra.management.api.ForceCompact; -import org.apache.cassandra.management.api.JmxConnect; +import org.apache.cassandra.management.api.JmxConnectionMixin; import org.apache.cassandra.management.api.TopLevelCommand; import org.apache.cassandra.utils.FBUtilities; import picocli.CommandLine; @@ -236,16 +239,19 @@ public int execute(String... args) List> cliCommands = newArrayList( AbortBootstrap.class, Assassinate.class, - ForceCompact.class); + ForceCompact.class, + Compact.class); - CommandLine commandLine = new CommandLine(new TopLevelCommand()); -// commandLine.setExecutionStrategy(new CommandLine.RunAll()); - commandLine.addMixin(JmxConnect.MIXIN_KEY, new JmxConnect()); + CommandLine.IFactory factory; + CommandLine commandLine = new CommandLine(new TopLevelCommand(), factory = new CassandraCliFactory(nodeProbeFactory, output)); + commandLine.setExecutionStrategy(JmxConnectionMixin::executionStrategy); + + JmxConnectionMixin mixin = create(factory, JmxConnectionMixin.class); + commandLine.addMixin(JmxConnectionMixin.MIXIN_KEY, mixin); commandLine.setOut(new PrintWriter(output.out)) .setErr(new PrintWriter(output.err)) .setExecutionExceptionHandler((ex, cmdLine, parseResult) -> { - output.err.println(ex.getMessage()); - commandLine.usage(output.err); + ex.printStackTrace(cmdLine.getErr()); return 1; }); @@ -256,7 +262,7 @@ public int execute(String... args) // This must be after all the subcommands are added to the commandLine. commandLine.setHelpFactory(CassandraHelpLayout::new); - commandLine.getSubcommands().values().forEach(sub -> sub.addMixin(JmxConnect.MIXIN_KEY, new JmxConnect())); + commandLine.getSubcommands().values().forEach(sub -> sub.addMixin(JmxConnectionMixin.MIXIN_KEY, mixin)); commandLine.setUsageHelpWidth(CassandraHelpLayout.DEFAULT_USAGE_HELP_WIDTH); commandLine.setHelpSectionKeys(CassandraHelpLayout.cassandraHelpSectionKeys()); } @@ -305,7 +311,7 @@ public int execute(String... args) { // NodeToolCmdRunnable parse = parser.parse(args); printHistory(args); - commandLine.execute(args); + status = commandLine.execute(args); // parse.run(nodeProbeFactory, output); } catch (IllegalArgumentException | IllegalStateException | @@ -328,15 +334,47 @@ public int execute(String... args) return status; } - private static Object instantiateCommand(Class commandClass) + private static T create(CommandLine.IFactory factory, Class clazz) { try { - return commandClass.getDeclaredConstructor().newInstance(); + return factory.create(clazz); } catch (Exception e) { - throw new RuntimeException("Failed to instantiate command " + commandClass.getName(), e); + throw new RuntimeException(e); + } + } + + private static class CassandraCliFactory implements CommandLine.IFactory + { + private final CommandLine.IFactory fallback; + private final INodeProbeFactory nodeProbeFactory; + private final Output output; + + + public CassandraCliFactory(INodeProbeFactory nodeProbeFactory, Output output) + { + this.fallback = CommandLine.defaultFactory(); + this.nodeProbeFactory = nodeProbeFactory; + this.output = output; + } + + public K create(Class cls) throws Exception + { + Object bean = this.fallback.create(cls); + Field[] fields = bean.getClass().getDeclaredFields(); + for (Field field : fields) + { + if (!field.isAnnotationPresent(Inject.class)) + continue; + field.setAccessible(true); + if (field.getType().equals(INodeProbeFactory.class)) + field.set(bean, nodeProbeFactory); + else if (field.getType().equals(Output.class)) + field.set(bean, Output.CONSOLE); + } + return (K) bean; } } diff --git a/test/unit/org/apache/cassandra/tools/NodeToolSynopsisTest.java b/test/unit/org/apache/cassandra/tools/NodeToolSynopsisTest.java index c7ad71164066..a29570a06225 100644 --- a/test/unit/org/apache/cassandra/tools/NodeToolSynopsisTest.java +++ b/test/unit/org/apache/cassandra/tools/NodeToolSynopsisTest.java @@ -63,6 +63,7 @@ public void compareNodeToolHelpOutput() throws Exception // runCommandHelpOutputComparison("abortbootstrap"); runCommandHelpOutputComparison("assassinate"); runCommandHelpOutputComparison("forcecompact"); + runCommandHelpOutputComparison("compact"); } public void runCommandHelpOutputComparison(String commandName) @@ -81,7 +82,8 @@ private static String concatNodetoolOutput(List output) { return '\n' + String.join("\n", output); } - private static List invokeNodetool(BiFunction factory, String... commands) + + public static List invokeNodetool(BiFunction factory, String... commands) { ListOutputStream output = new ListOutputStream(); List args = CQLTester.buildNodetoolArgs(List.of(commands)); @@ -117,7 +119,7 @@ public static String computeDiff(List original, List revised) { return '\n' + String.join("\n", diffLines); } - private static class ListOutputStream extends OutputStream + public static class ListOutputStream extends OutputStream { private final List outputLines = new ArrayList<>(); private final StringBuilder buffer = new StringBuilder(); diff --git a/test/unit/org/apache/cassandra/tools/ToolRunner.java b/test/unit/org/apache/cassandra/tools/ToolRunner.java index 13eeadd64c97..dd1995909384 100644 --- a/test/unit/org/apache/cassandra/tools/ToolRunner.java +++ b/test/unit/org/apache/cassandra/tools/ToolRunner.java @@ -32,6 +32,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.function.BiFunction; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -45,6 +46,7 @@ import org.apache.cassandra.cql3.CQLTester; import org.apache.cassandra.distributed.api.IInstance; import org.apache.cassandra.distributed.api.NodeToolResult; +import org.apache.cassandra.tools.nodetool.CompactTest; import org.apache.cassandra.utils.Pair; import org.assertj.core.util.Lists; @@ -191,6 +193,11 @@ public static ToolResult invokeNodetool(List args) return invoke(CQLTester.buildNodetoolArgs(args)); } + public static ToolResult invokeNodetool(Map env, String... args) + { + return invokeNodetool(env, Arrays.asList(args)); + } + public static ToolResult invokeNodetool(Map env, List args) { return invoke(env, CQLTester.buildNodetoolArgs(args)); @@ -310,6 +317,29 @@ public NodeToolResult get() res.right.getException()); } + public static ToolRunner.ToolResult invokeNodetoolInJvm(BiFunction factory, String... commands) + { + NodeToolSynopsisTest.ListOutputStream out = new NodeToolSynopsisTest.ListOutputStream(); + NodeToolSynopsisTest.ListOutputStream err = new NodeToolSynopsisTest.ListOutputStream(); + List args = CQLTester.buildNodetoolArgs(List.of(commands)); + args.remove("bin/nodetool"); + try + { + Object runner = factory.apply(new NodeProbeFactory(), new Output(new PrintStream(out), new PrintStream(err))); + Object result = runner.getClass().getMethod("execute", String[].class) + .invoke(runner, new Object[] { args.toArray(new String[0]) }); + assertTrue(result instanceof Integer); + + return new ToolResult(args, (Integer) result, String.join("\n", out.getOutputLines()), + String.join("\n", err.getOutputLines()), null); + } + catch (Exception e) + { + return new ToolResult(args, -1, String.join("\n", out.getOutputLines()), + String.join("\n", err.getOutputLines()), e); + } + } + public static Pair invokeSupplier(Supplier runMe) { return invokeSupplier(runMe, null); diff --git a/test/unit/org/apache/cassandra/tools/nodetool/CompactTest.java b/test/unit/org/apache/cassandra/tools/nodetool/CompactTest.java index 928f8851e8a9..91c4ba54af21 100644 --- a/test/unit/org/apache/cassandra/tools/nodetool/CompactTest.java +++ b/test/unit/org/apache/cassandra/tools/nodetool/CompactTest.java @@ -26,6 +26,8 @@ import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.db.Keyspace; import org.apache.cassandra.dht.Murmur3Partitioner; +import org.apache.cassandra.tools.NodeToolV2; +import org.apache.cassandra.tools.ToolRunner; import org.assertj.core.api.Assertions; import static org.apache.cassandra.tools.ToolRunner.invokeNodetool; @@ -54,7 +56,8 @@ public void keyPresent() throws Throwable flush(keyspace()); } Assertions.assertThat(cfs.getTracker().getView().liveSSTables()).hasSize(10); - invokeNodetool("compact", "--partition", Long.toString(key), keyspace(), currentTable()).assertOnCleanExit(); +// createMBeanServerConnection(); + invokeNodeToolV2("compact", "--partition", Long.toString(key), keyspace(), currentTable()).assertOnCleanExit(); // only 1 SSTable should exist Assertions.assertThat(cfs.getTracker().getView().liveSSTables()).hasSize(1); @@ -104,4 +107,9 @@ public void keyWrongType() .failure() .errorContains(String.format("Unable to parse partition key 'this_will_not_work' for table %s.%s; Unable to make long from 'this_will_not_work'", keyspace(), currentTable())); } + + public static ToolRunner.ToolResult invokeNodeToolV2(String... commands) + { + return ToolRunner.invokeNodetoolInJvm(NodeToolV2::new, commands); + } } From c9ef5d5214e1c0d107ffeeb3a590f01ecfcbcdf2 Mon Sep 17 00:00:00 2001 From: Maxim Muzafarov Date: Thu, 20 Jun 2024 15:46:30 +0200 Subject: [PATCH 5/8] cleanup code nodetool2 --- .../config/CassandraRelevantProperties.java | 1 + .../management/api/ForceCompact.java | 2 +- .../management/api/TopLevelCommand.java | 8 +- .../org/apache/cassandra/tools/NodeProbe.java | 60 +- .../org/apache/cassandra/tools/NodeTool.java | 25 +- .../apache/cassandra/tools/NodeToolV2.java | 530 ++---------------- .../cassandra/tools/NodeToolSynopsisTest.java | 6 + .../apache/cassandra/tools/ToolRunner.java | 19 +- .../tools/nodetool/CQLToolRunnerTester.java | 58 ++ .../cassandra/tools/nodetool/CompactTest.java | 15 +- 10 files changed, 170 insertions(+), 554 deletions(-) create mode 100644 test/unit/org/apache/cassandra/tools/nodetool/CQLToolRunnerTester.java diff --git a/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java b/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java index e1f6d28d86a9..565d21e6ff39 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_DEFAULT_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/api/ForceCompact.java b/src/java/org/apache/cassandra/management/api/ForceCompact.java index aa758b8e4eb8..146aa345e80f 100644 --- a/src/java/org/apache/cassandra/management/api/ForceCompact.java +++ b/src/java/org/apache/cassandra/management/api/ForceCompact.java @@ -29,7 +29,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static org.apache.cassandra.management.CommandUtils.ssProxy; -import static org.apache.cassandra.tools.NodeToolV2.NodeToolCmd.parsePartitionKeys; +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 diff --git a/src/java/org/apache/cassandra/management/api/TopLevelCommand.java b/src/java/org/apache/cassandra/management/api/TopLevelCommand.java index ff5c7658c665..4716b6e7bc59 100644 --- a/src/java/org/apache/cassandra/management/api/TopLevelCommand.java +++ b/src/java/org/apache/cassandra/management/api/TopLevelCommand.java @@ -22,8 +22,12 @@ import picocli.CommandLine; @CommandLine.Command(name = "nodetool", - subcommands = { CassandraHelpCommand.class }, - description = "Manage your Cassandra cluster") + description = "Manage your Cassandra cluster", + subcommands = { CassandraHelpCommand.class, + AbortBootstrap.class, + Assassinate.class, + ForceCompact.class, + Compact.class }) public class TopLevelCommand { } diff --git a/src/java/org/apache/cassandra/tools/NodeProbe.java b/src/java/org/apache/cassandra/tools/NodeProbe.java index f5268fca37e8..ee7a41640260 100644 --- a/src/java/org/apache/cassandra/tools/NodeProbe.java +++ b/src/java/org/apache/cassandra/tools/NodeProbe.java @@ -42,6 +42,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.function.BiConsumer; import javax.annotation.Nullable; import javax.management.JMX; import javax.management.MBeanServerConnection; @@ -233,9 +234,14 @@ protected NodeProbe() this.output = Output.CONSOLE; } - private T cacheProxy(T proxy) + private static T cachedNewMBeanProxy(BiConsumer cache, + MBeanServerConnection connection, + ObjectName objectName, + Class clazz) { - cachedMBeans.put(proxy.getClass().getName(), proxy); + + T proxy = JMX.newMBeanProxy(connection, objectName, clazz); + cache.accept(clazz.getName(), proxy); return proxy; } @@ -275,55 +281,49 @@ protected void connect() throws IOException try { ObjectName name = new ObjectName(ssObjName); - ssProxy = cacheProxy(JMX.newMBeanProxy(mbeanServerConn, name, StorageServiceMBean.class)); + ssProxy = cachedNewMBeanProxy(cachedMBeans::put, mbeanServerConn, name, StorageServiceMBean.class); name = new ObjectName(CMSOperations.MBEAN_OBJECT_NAME); - cmsProxy = cacheProxy(JMX.newMBeanProxy(mbeanServerConn, name, CMSOperationsMBean.class)); + cmsProxy = cachedNewMBeanProxy(cachedMBeans::put, mbeanServerConn, name, CMSOperationsMBean.class); name = new ObjectName(MessagingService.MBEAN_NAME); - msProxy = cacheProxy(JMX.newMBeanProxy(mbeanServerConn, name, MessagingServiceMBean.class)); + msProxy = cachedNewMBeanProxy(cachedMBeans::put, mbeanServerConn, name, MessagingServiceMBean.class); name = new ObjectName(StreamManagerMBean.OBJECT_NAME); - streamProxy = cacheProxy(JMX.newMBeanProxy(mbeanServerConn, name, StreamManagerMBean.class)); + streamProxy = cachedNewMBeanProxy(cachedMBeans::put, mbeanServerConn, name, StreamManagerMBean.class); name = new ObjectName(CompactionManager.MBEAN_OBJECT_NAME); - compactionProxy = cacheProxy(JMX.newMBeanProxy(mbeanServerConn, name, CompactionManagerMBean.class)); + compactionProxy = cachedNewMBeanProxy(cachedMBeans::put, mbeanServerConn, name, CompactionManagerMBean.class); name = new ObjectName(FailureDetector.MBEAN_NAME); - fdProxy = cacheProxy(JMX.newMBeanProxy(mbeanServerConn, name, FailureDetectorMBean.class)); + fdProxy = cachedNewMBeanProxy(cachedMBeans::put, mbeanServerConn, name, FailureDetectorMBean.class); name = new ObjectName(CacheService.MBEAN_NAME); - cacheService = cacheProxy(JMX.newMBeanProxy(mbeanServerConn, name, CacheServiceMBean.class)); + cacheService = cachedNewMBeanProxy(cachedMBeans::put, mbeanServerConn, name, CacheServiceMBean.class); name = new ObjectName(StorageProxy.MBEAN_NAME); - spProxy = cacheProxy(JMX.newMBeanProxy(mbeanServerConn, name, StorageProxyMBean.class)); + spProxy = cachedNewMBeanProxy(cachedMBeans::put, mbeanServerConn, name, StorageProxyMBean.class); name = new ObjectName(HintsService.MBEAN_NAME); - hsProxy = cacheProxy(JMX.newMBeanProxy(mbeanServerConn, name, HintsServiceMBean.class)); + hsProxy = cachedNewMBeanProxy(cachedMBeans::put, mbeanServerConn, name, HintsServiceMBean.class); name = new ObjectName(GCInspector.MBEAN_NAME); - gcProxy = JMX.newMBeanProxy(mbeanServerConn, name, GCInspectorMXBean.class); + gcProxy = cachedNewMBeanProxy(cachedMBeans::put, mbeanServerConn, name, GCInspectorMXBean.class); name = new ObjectName(Gossiper.MBEAN_NAME); - gossProxy = JMX.newMBeanProxy(mbeanServerConn, name, GossiperMBean.class); + gossProxy = cachedNewMBeanProxy(cachedMBeans::put, mbeanServerConn, name, GossiperMBean.class); name = new ObjectName(BatchlogManager.MBEAN_NAME); - bmProxy = JMX.newMBeanProxy(mbeanServerConn, name, BatchlogManagerMBean.class); + bmProxy = cachedNewMBeanProxy(cachedMBeans::put, mbeanServerConn, name, BatchlogManagerMBean.class); name = new ObjectName(ActiveRepairServiceMBean.MBEAN_NAME); - arsProxy = JMX.newMBeanProxy(mbeanServerConn, name, ActiveRepairServiceMBean.class); + arsProxy = cachedNewMBeanProxy(cachedMBeans::put, mbeanServerConn, name, ActiveRepairServiceMBean.class); name = new ObjectName(AuditLogManager.MBEAN_NAME); - almProxy = JMX.newMBeanProxy(mbeanServerConn, name, AuditLogManagerMBean.class); + almProxy = cachedNewMBeanProxy(cachedMBeans::put, mbeanServerConn, name, AuditLogManagerMBean.class); name = new ObjectName(AuthCache.MBEAN_NAME_BASE + PasswordAuthenticator.CredentialsCacheMBean.CACHE_NAME); - ccProxy = JMX.newMBeanProxy(mbeanServerConn, name, PasswordAuthenticator.CredentialsCacheMBean.class); + ccProxy = cachedNewMBeanProxy(cachedMBeans::put, mbeanServerConn, name, PasswordAuthenticator.CredentialsCacheMBean.class); name = new ObjectName(AuthCache.MBEAN_NAME_BASE + AuthorizationProxy.JmxPermissionsCacheMBean.CACHE_NAME); - jpcProxy = JMX.newMBeanProxy(mbeanServerConn, name, AuthorizationProxy.JmxPermissionsCacheMBean.class); - + jpcProxy = cachedNewMBeanProxy(cachedMBeans::put, mbeanServerConn, name, AuthorizationProxy.JmxPermissionsCacheMBean.class); name = new ObjectName(AuthCache.MBEAN_NAME_BASE + NetworkPermissionsCache.CACHE_NAME); - npcProxy = JMX.newMBeanProxy(mbeanServerConn, name, NetworkPermissionsCacheMBean.class); - + npcProxy = cachedNewMBeanProxy(cachedMBeans::put, mbeanServerConn, name, NetworkPermissionsCacheMBean.class); name = new ObjectName(AuthCache.MBEAN_NAME_BASE + PermissionsCache.CACHE_NAME); - pcProxy = JMX.newMBeanProxy(mbeanServerConn, name, PermissionsCacheMBean.class); - + pcProxy = cachedNewMBeanProxy(cachedMBeans::put, mbeanServerConn, name, PermissionsCacheMBean.class); name = new ObjectName(AuthCache.MBEAN_NAME_BASE + RolesCache.CACHE_NAME); - rcProxy = JMX.newMBeanProxy(mbeanServerConn, name, RolesCacheMBean.class); - + rcProxy = cachedNewMBeanProxy(cachedMBeans::put, mbeanServerConn, name, RolesCacheMBean.class); name = new ObjectName(CIDRPermissionsManager.MBEAN_NAME); - cpbProxy = JMX.newMBeanProxy(mbeanServerConn, name, CIDRPermissionsManagerMBean.class); - + cpbProxy = cachedNewMBeanProxy(cachedMBeans::put, mbeanServerConn, name, CIDRPermissionsManagerMBean.class); name = new ObjectName(CIDRGroupsMappingManager.MBEAN_NAME); - cmbProxy = JMX.newMBeanProxy(mbeanServerConn, name, CIDRGroupsMappingManagerMBean.class); - + cmbProxy = cachedNewMBeanProxy(cachedMBeans::put, mbeanServerConn, name, CIDRGroupsMappingManagerMBean.class); name = new ObjectName(CIDRFilteringMetricsTable.MBEAN_NAME); - cfmProxy = JMX.newMBeanProxy(mbeanServerConn, name, CIDRFilteringMetricsTableMBean.class); + cfmProxy = cachedNewMBeanProxy(cachedMBeans::put, mbeanServerConn, name, CIDRFilteringMetricsTableMBean.class); } catch (MalformedObjectNameException e) { diff --git a/src/java/org/apache/cassandra/tools/NodeTool.java b/src/java/org/apache/cassandra/tools/NodeTool.java index d4bc7e130b7d..fe72e2ed0bb7 100644 --- a/src/java/org/apache/cassandra/tools/NodeTool.java +++ b/src/java/org/apache/cassandra/tools/NodeTool.java @@ -31,6 +31,7 @@ import java.util.Map.Entry; import java.util.Scanner; import java.util.SortedMap; +import java.util.function.Consumer; import javax.management.InstanceNotFoundException; import com.google.common.base.Joiner; @@ -295,7 +296,7 @@ public int execute(String... args) return status; } - private static void printHistory(String... args) + public static void printHistory(String... args) { //don't bother to print if no args passed (meaning, nodetool is just printing out the sub-commands list) if (args.length == 0) @@ -315,21 +316,31 @@ private static void printHistory(String... args) } } + public static void badUse(Consumer out, Throwable e) + { + out.accept("nodetool: " + e.getMessage()); + out.accept("See 'nodetool help' or 'nodetool help '."); + } + protected void badUse(Exception e) { - output.out.println("nodetool: " + e.getMessage()); - output.out.println("See 'nodetool help' or 'nodetool help '."); + badUse(output.out::println, e); } - protected void err(Throwable e) + public static void err(Consumer out, Throwable e) { // CASSANDRA-11537: friendly error message when server is not ready if (e instanceof InstanceNotFoundException) throw new IllegalArgumentException("Server is not initialized yet, cannot run nodetool."); - output.err.println("error: " + e.getMessage()); - output.err.println("-- StackTrace --"); - output.err.println(getStackTraceAsString(e)); + out.accept("error: " + e.getMessage()); + out.accept("-- StackTrace --"); + out.accept(getStackTraceAsString(e)); + } + + protected void err(Throwable e) + { + err(output.err::println, e); } public static class CassHelp extends Help implements NodeToolCmdRunnable diff --git a/src/java/org/apache/cassandra/tools/NodeToolV2.java b/src/java/org/apache/cassandra/tools/NodeToolV2.java index a8ed8fdea8a9..7f12758e7f73 100644 --- a/src/java/org/apache/cassandra/tools/NodeToolV2.java +++ b/src/java/org/apache/cassandra/tools/NodeToolV2.java @@ -17,58 +17,22 @@ */ package org.apache.cassandra.tools; -import java.io.Console; -import java.io.FileNotFoundException; -import java.io.IOError; -import java.io.IOException; import java.io.PrintWriter; import java.lang.reflect.Field; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.List; -import java.util.Scanner; import javax.inject.Inject; -import javax.management.InstanceNotFoundException; -import com.google.common.base.Joiner; import com.google.common.base.Throwables; -import io.airlift.airline.Cli; -import io.airlift.airline.Help; -import io.airlift.airline.Option; -import io.airlift.airline.OptionType; -import io.airlift.airline.ParseArgumentsMissingException; -import io.airlift.airline.ParseArgumentsUnexpectedException; -import io.airlift.airline.ParseCommandMissingException; -import io.airlift.airline.ParseCommandUnrecognizedException; -import io.airlift.airline.ParseOptionConversionException; -import io.airlift.airline.ParseOptionMissingException; -import io.airlift.airline.ParseOptionMissingValueException; -import org.apache.cassandra.io.util.File; -import org.apache.cassandra.io.util.FileWriter; -import org.apache.cassandra.management.BaseCommand; +import org.apache.cassandra.config.CassandraRelevantProperties; import org.apache.cassandra.management.CassandraHelpLayout; -import org.apache.cassandra.management.api.AbortBootstrap; -import org.apache.cassandra.management.api.Assassinate; -import org.apache.cassandra.management.api.Compact; -import org.apache.cassandra.management.api.ForceCompact; import org.apache.cassandra.management.api.JmxConnectionMixin; import org.apache.cassandra.management.api.TopLevelCommand; import org.apache.cassandra.utils.FBUtilities; import picocli.CommandLine; -import static com.google.common.base.Throwables.getStackTraceAsString; -import static com.google.common.collect.Iterables.toArray; -import static com.google.common.collect.Lists.newArrayList; -import static java.lang.Integer.parseInt; -import static java.lang.String.format; -import static org.apache.cassandra.io.util.File.WriteMode.APPEND; -import static org.apache.commons.lang3.ArrayUtils.EMPTY_STRING_ARRAY; -import static org.apache.commons.lang3.StringUtils.EMPTY; -import static org.apache.commons.lang3.StringUtils.isEmpty; -import static org.apache.commons.lang3.StringUtils.isNotEmpty; +import static org.apache.cassandra.tools.NodeTool.badUse; +import static org.apache.cassandra.tools.NodeTool.err; +import static org.apache.cassandra.tools.NodeTool.printHistory; public class NodeToolV2 { @@ -77,8 +41,6 @@ public class NodeToolV2 FBUtilities.preventIllegalAccessWarnings(); } - private static final String HISTORYFILE = "nodetool.history"; - private final INodeProbeFactory nodeProbeFactory; private final Output output; @@ -93,257 +55,53 @@ public NodeToolV2(INodeProbeFactory nodeProbeFactory, Output output) this.output = output; } + /** + * Execute the command line utility with the given arguments via the JMX connection. + * @param args command line arguments + * @return 0 on success, 1 on bad use, 2 on execution error + */ public int execute(String... args) { - List> commands = newArrayList( - CassHelp.class -// CIDRFilteringStats.class, -// Cleanup.class, -// ClearSnapshot.class, -// ClientStats.class, -// Compact.class, -// CompactionHistory.class, -// CompactionStats.class, -// DataPaths.class, -// Decommission.class, -// DescribeCluster.class, -// DescribeRing.class, -// DisableAuditLog.class, -// DisableAutoCompaction.class, -// DisableBackup.class, -// DisableBinary.class, -// DisableFullQueryLog.class, -// DisableGossip.class, -// DisableHandoff.class, -// DisableHintsForDC.class, -// DisableOldProtocolVersions.class, -// Drain.class, -// DropCIDRGroup.class, -// EnableAuditLog.class, -// EnableAutoCompaction.class, -// EnableBackup.class, -// EnableBinary.class, -// EnableFullQueryLog.class, -// EnableGossip.class, -// EnableHandoff.class, -// EnableHintsForDC.class, -// EnableOldProtocolVersions.class, -// FailureDetectorInfo.class, -// Flush.class, -// GarbageCollect.class, -// GcStats.class, -// GetAuditLog.class, -// GetAuthCacheConfig.class, -// GetBatchlogReplayTrottle.class, -// GetCIDRGroupsOfIP.class, -// GetColumnIndexSize.class, -// GetCompactionThreshold.class, -// GetCompactionThroughput.class, -// GetConcurrency.class, -// GetConcurrentCompactors.class, -// GetConcurrentViewBuilders.class, -// GetDefaultKeyspaceRF.class, -// GetEndpoints.class, -// GetFullQueryLog.class, -// GetInterDCStreamThroughput.class, -// GetLoggingLevels.class, -// GetMaxHintWindow.class, -// GetSSTables.class, -// GetSeeds.class, -// GetSnapshotThrottle.class, -// GetStreamThroughput.class, -// GetTimeout.class, -// GetTraceProbability.class, -// GossipInfo.class, -// Import.class, -// Info.class, -// InvalidateCIDRPermissionsCache.class, -// InvalidateCounterCache.class, -// InvalidateCredentialsCache.class, -// InvalidateJmxPermissionsCache.class, -// ReloadCIDRGroupsCache.class, -// InvalidateKeyCache.class, -// InvalidateNetworkPermissionsCache.class, -// InvalidatePermissionsCache.class, -// InvalidateRolesCache.class, -// InvalidateRowCache.class, -// Join.class, -// ListCIDRGroups.class, -// ListPendingHints.class, -// ListSnapshots.class, -// Move.class, -// NetStats.class, -// PauseHandoff.class, -// ProfileLoad.class, -// ProxyHistograms.class, -// RangeKeySample.class, -// Rebuild.class, -// RebuildIndex.class, -// RecompressSSTables.class, -// Refresh.class, -// RefreshSizeEstimates.class, -// ReloadLocalSchema.class, -// ReloadSeeds.class, -// ReloadSslCertificates.class, -// ReloadTriggers.class, -// RelocateSSTables.class, -// RemoveNode.class, -// Repair.class, -// ReplayBatchlog.class, -// ResetFullQueryLog.class, -// ResetLocalSchema.class, -// ResumeHandoff.class, -// Ring.class, -// Scrub.class, -// SetAuthCacheConfig.class, -// SetBatchlogReplayThrottle.class, -// SetCacheCapacity.class, -// SetCacheKeysToSave.class, -// SetColumnIndexSize.class, -// SetCompactionThreshold.class, -// SetCompactionThroughput.class, -// SetConcurrency.class, -// SetConcurrentCompactors.class, -// SetConcurrentViewBuilders.class, -// SetDefaultKeyspaceRF.class, -// SetHintedHandoffThrottleInKB.class, -// SetInterDCStreamThroughput.class, -// SetLoggingLevel.class, -// SetMaxHintWindow.class, -// SetSnapshotThrottle.class, -// SetStreamThroughput.class, -// SetTimeout.class, -// SetTraceProbability.class, -// Sjk.class, -// Snapshot.class, -// Status.class, -// StatusAutoCompaction.class, -// StatusBackup.class, -// StatusBinary.class, -// StatusGossip.class, -// StatusHandoff.class, -// Stop.class, -// StopDaemon.class, -// TableHistograms.class, -// TableStats.class, -// TopPartitions.class, -// TpStats.class, -// TruncateHints.class, -// UpdateCIDRGroup.class, -// UpgradeSSTable.class, -// Verify.class, -// Version.class, -// ViewBuildStatus.class - ); - - List> cliCommands = newArrayList( - AbortBootstrap.class, - Assassinate.class, - ForceCompact.class, - Compact.class); - CommandLine.IFactory factory; CommandLine commandLine = new CommandLine(new TopLevelCommand(), factory = new CassandraCliFactory(nodeProbeFactory, output)); - commandLine.setExecutionStrategy(JmxConnectionMixin::executionStrategy); - JmxConnectionMixin mixin = create(factory, JmxConnectionMixin.class); - commandLine.addMixin(JmxConnectionMixin.MIXIN_KEY, mixin); - commandLine.setOut(new PrintWriter(output.out)) - .setErr(new PrintWriter(output.err)) - .setExecutionExceptionHandler((ex, cmdLine, parseResult) -> { - ex.printStackTrace(cmdLine.getErr()); - return 1; - }); + configureCliLayout(commandLine); + commandLine.setOut(new PrintWriter(output.out, true)) + .setErr(new PrintWriter(output.err, true)) + .setExecutionExceptionHandler((ex, cmdLine, parseResult) -> { + err(cmdLine.getErr()::println, Throwables.getRootCause(ex)); + return 2; + }) + .setParameterExceptionHandler((ex, arg) -> { + badUse(commandLine.getOut()::println, Throwables.getRootCause(ex)); + return 1; + }); try { - for (Class commandClass : cliCommands) - commandLine.addSubcommand(commandLine.getFactory().create(commandClass)); - - // This must be after all the subcommands are added to the commandLine. - commandLine.setHelpFactory(CassandraHelpLayout::new); + JmxConnectionMixin mixin = factory.create(JmxConnectionMixin.class); + commandLine.setExecutionStrategy(JmxConnectionMixin::executionStrategy); + commandLine.addMixin(JmxConnectionMixin.MIXIN_KEY, mixin); commandLine.getSubcommands().values().forEach(sub -> sub.addMixin(JmxConnectionMixin.MIXIN_KEY, mixin)); - commandLine.setUsageHelpWidth(CassandraHelpLayout.DEFAULT_USAGE_HELP_WIDTH); - commandLine.setHelpSectionKeys(CassandraHelpLayout.cassandraHelpSectionKeys()); + + printHistory(args); + return commandLine.execute(args); } catch (Exception e) { - err(Throwables.getRootCause(e)); + err(commandLine.getErr()::println, e); return 2; } - -// return commandLine.execute(args); - - Cli.CliBuilder builder = Cli.builder("nodetool"); - - builder.withDescription("Manage your Cassandra cluster") - .withDefaultCommand(CassHelp.class) - .withCommands(commands); - - // bootstrap commands -// builder.withGroup("bootstrap") -// .withDescription("Monitor/manage node's bootstrap process") -// .withDefaultCommand(CassHelp.class) -// .withCommand(BootstrapResume.class); -// -// builder.withGroup("repair_admin") -// .withDescription("list and fail incremental repair sessions") -// .withDefaultCommand(RepairAdmin.ListCmd.class) -// .withCommand(RepairAdmin.ListCmd.class) -// .withCommand(RepairAdmin.CancelCmd.class) -// .withCommand(RepairAdmin.CleanupDataCmd.class) -// .withCommand(RepairAdmin.SummarizePendingCmd.class) -// .withCommand(RepairAdmin.SummarizeRepairedCmd.class); -// -// builder.withGroup("cms") -// .withDescription("Manage cluster metadata") -// .withDefaultCommand(CMSAdmin.DescribeCMS.class) -// .withCommand(CMSAdmin.DescribeCMS.class) -// .withCommand(CMSAdmin.InitializeCMS.class) -// .withCommand(CMSAdmin.ReconfigureCMS.class) -// .withCommand(CMSAdmin.Snapshot.class) -// .withCommand(CMSAdmin.Unregister.class); - - Cli parser = builder.build(); - - int status = 0; - try - { -// NodeToolCmdRunnable parse = parser.parse(args); - printHistory(args); - status = commandLine.execute(args); -// parse.run(nodeProbeFactory, output); - } catch (IllegalArgumentException | - IllegalStateException | - ParseArgumentsMissingException | - ParseArgumentsUnexpectedException | - ParseOptionConversionException | - ParseOptionMissingException | - ParseOptionMissingValueException | - ParseCommandMissingException | - ParseCommandUnrecognizedException e) - { - badUse(e); - status = 1; - } catch (Throwable throwable) - { - err(Throwables.getRootCause(throwable)); - status = 2; - } - - return status; } - private static T create(CommandLine.IFactory factory, Class clazz) + private static void configureCliLayout(CommandLine commandLine) { - try - { - return factory.create(clazz); - } - catch (Exception e) - { - throw new RuntimeException(e); - } + if (CassandraRelevantProperties.CASSANDRA_CLI_DEFAULT_LAYOUT.getBoolean()) + return; + + commandLine.setHelpFactory(CassandraHelpLayout::new) + .setUsageHelpWidth(CassandraHelpLayout.DEFAULT_USAGE_HELP_WIDTH) + .setHelpSectionKeys(CassandraHelpLayout.cassandraHelpSectionKeys()); } private static class CassandraCliFactory implements CommandLine.IFactory @@ -352,7 +110,6 @@ private static class CassandraCliFactory implements CommandLine.IFactory private final INodeProbeFactory nodeProbeFactory; private final Output output; - public CassandraCliFactory(INodeProbeFactory nodeProbeFactory, Output output) { this.fallback = CommandLine.defaultFactory(); @@ -372,229 +129,12 @@ public K create(Class cls) throws Exception if (field.getType().equals(INodeProbeFactory.class)) field.set(bean, nodeProbeFactory); else if (field.getType().equals(Output.class)) - field.set(bean, Output.CONSOLE); + field.set(bean, output); } return (K) bean; } } - private static void printHistory(String... args) - { - //don't bother to print if no args passed (meaning, nodetool is just printing out the sub-commands list) - if (args.length == 0) - return; - - String cmdLine = Joiner.on(" ").skipNulls().join(args); - cmdLine = cmdLine.replaceFirst("(?<=(-pw|--password))\\s+\\S+", " "); - - try (FileWriter writer = new File(FBUtilities.getToolsOutputDirectory(), HISTORYFILE).newWriter(APPEND)) - { - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS"); - writer.append(sdf.format(new Date())).append(": ").append(cmdLine).append(System.lineSeparator()); - } - catch (IOException | IOError ioe) - { - //quietly ignore any errors about not being able to write out history - } - } - - protected void badUse(Exception e) - { - output.out.println("nodetool: " + e.getMessage()); - output.out.println("See 'nodetool help' or 'nodetool help '."); - } - - protected void err(Throwable e) - { - // CASSANDRA-11537: friendly error message when server is not ready - if (e instanceof InstanceNotFoundException) - throw new IllegalArgumentException("Server is not initialized yet, cannot run nodetool."); - - output.err.println("error: " + e.getMessage()); - output.err.println("-- StackTrace --"); - output.err.println(getStackTraceAsString(e)); - } - - public static class CassHelp extends Help implements NodeToolCmdRunnable - { - public void run(INodeProbeFactory nodeProbeFactory, Output output) - { - run(); - } - } - - interface NodeToolCmdRunnable - { - void run(INodeProbeFactory nodeProbeFactory, Output output); - } - - public static abstract class NodeToolCmd implements NodeToolCmdRunnable - { - - @Option(type = OptionType.GLOBAL, name = {"-h", "--host"}, description = "Node hostname or ip address") - private String host = "127.0.0.1"; - - @Option(type = OptionType.GLOBAL, name = {"-p", "--port"}, description = "Remote jmx agent port number") - private String port = "7199"; - - @Option(type = OptionType.GLOBAL, name = {"-u", "--username"}, description = "Remote jmx agent username") - private String username = EMPTY; - - @Option(type = OptionType.GLOBAL, name = {"-pw", "--password"}, description = "Remote jmx agent password") - private String password = EMPTY; - - @Option(type = OptionType.GLOBAL, name = {"-pwf", "--password-file"}, description = "Path to the JMX password file") - private String passwordFilePath = EMPTY; - - @Option(type = OptionType.GLOBAL, name = { "-pp", "--print-port"}, description = "Operate in 4.0 mode with hosts disambiguated by port number", arity = 0) - protected boolean printPort = false; - - private INodeProbeFactory nodeProbeFactory; - protected Output output; - - @Override - public void run(INodeProbeFactory nodeProbeFactory, Output output) - { - this.nodeProbeFactory = nodeProbeFactory; - this.output = output; - runInternal(); - } - - public void runInternal() - { - if (isNotEmpty(username)) { - if (isNotEmpty(passwordFilePath)) - password = readUserPasswordFromFile(username, passwordFilePath); - - if (isEmpty(password)) - password = promptAndReadPassword(); - } - - try (NodeProbe probe = connect()) - { - execute(probe); - if (probe.isFailed()) - throw new RuntimeException("nodetool failed, check server logs"); - } - catch (IOException e) - { - throw new RuntimeException("Error while closing JMX connection", e); - } - - } - - private String readUserPasswordFromFile(String username, String passwordFilePath) { - String password = EMPTY; - - File passwordFile = new File(passwordFilePath); - try (Scanner scanner = new Scanner(passwordFile.toJavaIOFile()).useDelimiter("\\s+")) - { - while (scanner.hasNextLine()) - { - if (scanner.hasNext()) - { - String jmxRole = scanner.next(); - if (jmxRole.equals(username) && scanner.hasNext()) - { - password = scanner.next(); - break; - } - } - scanner.nextLine(); - } - } - catch (FileNotFoundException e) - { - throw new RuntimeException(e); - } - - return password; - } - - private String promptAndReadPassword() - { - String password = EMPTY; - - Console console = System.console(); - if (console != null) - password = String.valueOf(console.readPassword("Password:")); - - return password; - } - - protected abstract void execute(NodeProbe probe); - - private NodeProbe connect() - { - NodeProbe nodeClient = null; - - try - { - if (username.isEmpty()) - nodeClient = nodeProbeFactory.create(host, parseInt(port)); - else - nodeClient = nodeProbeFactory.create(host, parseInt(port), username, password); - - nodeClient.setOutput(output); - } catch (IOException | SecurityException e) - { - Throwable rootCause = Throwables.getRootCause(e); - output.err.println(format("nodetool: Failed to connect to '%s:%s' - %s: '%s'.", host, port, rootCause.getClass().getSimpleName(), rootCause.getMessage())); - System.exit(1); - } - - return nodeClient; - } - - protected enum KeyspaceSet - { - ALL, NON_SYSTEM, NON_LOCAL_STRATEGY - } - - protected List parseOptionalKeyspace(List cmdArgs, NodeProbe nodeProbe) - { - return parseOptionalKeyspace(cmdArgs, nodeProbe, KeyspaceSet.ALL); - } - - protected List parseOptionalKeyspace(List cmdArgs, NodeProbe nodeProbe, KeyspaceSet defaultKeyspaceSet) - { - List keyspaces = new ArrayList<>(); - - - if (cmdArgs == null || cmdArgs.isEmpty()) - { - if (defaultKeyspaceSet == KeyspaceSet.NON_LOCAL_STRATEGY) - keyspaces.addAll(keyspaces = nodeProbe.getNonLocalStrategyKeyspaces()); - else if (defaultKeyspaceSet == KeyspaceSet.NON_SYSTEM) - keyspaces.addAll(keyspaces = nodeProbe.getNonSystemKeyspaces()); - else - keyspaces.addAll(nodeProbe.getKeyspaces()); - } - else - { - keyspaces.add(cmdArgs.get(0)); - } - - for (String keyspace : keyspaces) - { - if (!nodeProbe.getKeyspaces().contains(keyspace)) - throw new IllegalArgumentException("Keyspace [" + keyspace + "] does not exist."); - } - - return Collections.unmodifiableList(keyspaces); - } - - protected String[] parseOptionalTables(List cmdArgs) - { - return cmdArgs.size() <= 1 ? EMPTY_STRING_ARRAY : toArray(cmdArgs.subList(1, cmdArgs.size()), String.class); - } - - public static String[] parsePartitionKeys(List cmdArgs) - { - return cmdArgs.size() <= 2 ? EMPTY_STRING_ARRAY : toArray(cmdArgs.subList(2, cmdArgs.size()), String.class); - } - } - // public static SortedMap getOwnershipByDcWithPort(NodeProbe probe, boolean resolveIp, // Map tokenToEndpoint, // Map ownerships) diff --git a/test/unit/org/apache/cassandra/tools/NodeToolSynopsisTest.java b/test/unit/org/apache/cassandra/tools/NodeToolSynopsisTest.java index a29570a06225..884a77cdc966 100644 --- a/test/unit/org/apache/cassandra/tools/NodeToolSynopsisTest.java +++ b/test/unit/org/apache/cassandra/tools/NodeToolSynopsisTest.java @@ -152,5 +152,11 @@ public List getOutputLines() flush(); return new ArrayList<>(outputLines); } + + public String getOutput() + { + flush(); + return String.join(System.lineSeparator(), outputLines); + } } } diff --git a/test/unit/org/apache/cassandra/tools/ToolRunner.java b/test/unit/org/apache/cassandra/tools/ToolRunner.java index dd1995909384..c8dac8fe6ca1 100644 --- a/test/unit/org/apache/cassandra/tools/ToolRunner.java +++ b/test/unit/org/apache/cassandra/tools/ToolRunner.java @@ -46,7 +46,6 @@ import org.apache.cassandra.cql3.CQLTester; import org.apache.cassandra.distributed.api.IInstance; import org.apache.cassandra.distributed.api.NodeToolResult; -import org.apache.cassandra.tools.nodetool.CompactTest; import org.apache.cassandra.utils.Pair; import org.assertj.core.util.Lists; @@ -317,6 +316,16 @@ public NodeToolResult get() res.right.getException()); } + public static ToolResult invokeNodetoolInJvmV2(String... commands) + { + return ToolRunner.invokeNodetoolInJvm(NodeToolV2::new, commands); + } + + public static ToolResult invokeNodetoolInJvmV1(String... commands) + { + return ToolRunner.invokeNodetoolInJvm(NodeTool::new, commands); + } + public static ToolRunner.ToolResult invokeNodetoolInJvm(BiFunction factory, String... commands) { NodeToolSynopsisTest.ListOutputStream out = new NodeToolSynopsisTest.ListOutputStream(); @@ -329,14 +338,12 @@ public static ToolRunner.ToolResult invokeNodetoolInJvm(BiFunction runnersMap = Map.of( + "invokeNodetool", ToolRunner::invokeNodetool, + "invokeNodetoolInJvmV1", ToolRunner::invokeNodetoolInJvmV1, + "invokeNodetoolInJvmV2", ToolRunner::invokeNodetoolInJvmV2); + + @Parameterized.Parameter + public String runner; + + @Parameterized.Parameters(name = "{0}") + public static Set runners() + { + return runnersMap.keySet(); + } + + protected ToolRunner.ToolResult invokeNodetool(String... args) + { + return runnersMap.get(runner).execute(args); + } + + public interface ToolHandler + { + ToolRunner.ToolResult execute(String... args); + default ToolRunner.ToolResult execute(List args) { return execute(args.toArray(new String[0])); } + } +} diff --git a/test/unit/org/apache/cassandra/tools/nodetool/CompactTest.java b/test/unit/org/apache/cassandra/tools/nodetool/CompactTest.java index 91c4ba54af21..60b55bf226f8 100644 --- a/test/unit/org/apache/cassandra/tools/nodetool/CompactTest.java +++ b/test/unit/org/apache/cassandra/tools/nodetool/CompactTest.java @@ -22,17 +22,12 @@ import org.junit.BeforeClass; import org.junit.Test; -import org.apache.cassandra.cql3.CQLTester; import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.db.Keyspace; import org.apache.cassandra.dht.Murmur3Partitioner; -import org.apache.cassandra.tools.NodeToolV2; -import org.apache.cassandra.tools.ToolRunner; import org.assertj.core.api.Assertions; -import static org.apache.cassandra.tools.ToolRunner.invokeNodetool; - -public class CompactTest extends CQLTester +public class CompactTest extends CQLToolRunnerTester { @BeforeClass public static void setup() throws Throwable @@ -56,8 +51,7 @@ public void keyPresent() throws Throwable flush(keyspace()); } Assertions.assertThat(cfs.getTracker().getView().liveSSTables()).hasSize(10); -// createMBeanServerConnection(); - invokeNodeToolV2("compact", "--partition", Long.toString(key), keyspace(), currentTable()).assertOnCleanExit(); + invokeNodetool("compact", "--partition", Long.toString(key), keyspace(), currentTable()).assertOnCleanExit(); // only 1 SSTable should exist Assertions.assertThat(cfs.getTracker().getView().liveSSTables()).hasSize(1); @@ -107,9 +101,4 @@ public void keyWrongType() .failure() .errorContains(String.format("Unable to parse partition key 'this_will_not_work' for table %s.%s; Unable to make long from 'this_will_not_work'", keyspace(), currentTable())); } - - public static ToolRunner.ToolResult invokeNodeToolV2(String... commands) - { - return ToolRunner.invokeNodetoolInJvm(NodeToolV2::new, commands); - } } From 4a0dc9fd2ca6ef159bff24b18ca7f3070ed5acf5 Mon Sep 17 00:00:00 2001 From: Maxim Muzafarov Date: Thu, 20 Jun 2024 22:52:03 +0200 Subject: [PATCH 6/8] command initialization fix --- .../management/CassandraHelpCommand.java | 2 +- .../management/CassandraHelpLayout.java | 23 +++++++++++++++---- .../cassandra/management/CommandUtils.java | 10 -------- .../management/api/AbortBootstrap.java | 4 ++-- .../cassandra/management/api/Assassinate.java | 2 +- .../cassandra/management/api/Compact.java | 15 ++++++------ .../management/api/ForceCompact.java | 8 +++---- .../management/api/JmxConnectionMixin.java | 4 ++-- .../management/api/TopLevelCommand.java | 13 ++++++++++- .../apache/cassandra/tools/NodeToolV2.java | 23 ------------------- .../tools/nodetool/CQLToolRunnerTester.java | 8 +++++++ .../cassandra/tools/nodetool/CompactTest.java | 8 ------- .../tools/nodetool/ForceCompactionTest.java | 15 ++++++------ 13 files changed, 64 insertions(+), 71 deletions(-) diff --git a/src/java/org/apache/cassandra/management/CassandraHelpCommand.java b/src/java/org/apache/cassandra/management/CassandraHelpCommand.java index 905b46091d15..6d8d6c912b87 100644 --- a/src/java/org/apache/cassandra/management/CassandraHelpCommand.java +++ b/src/java/org/apache/cassandra/management/CassandraHelpCommand.java @@ -78,7 +78,7 @@ public void run() subcommand.usage(out, colors); } - private static void printTopCommandUsage(CommandLine command, CommandLine.Help.ColorScheme colors, PrintWriter writer) + public static void printTopCommandUsage(CommandLine command, CommandLine.Help.ColorScheme colors, PrintWriter writer) { if (command == null) return; diff --git a/src/java/org/apache/cassandra/management/CassandraHelpLayout.java b/src/java/org/apache/cassandra/management/CassandraHelpLayout.java index 85d1cccf406b..c965f7826d57 100644 --- a/src/java/org/apache/cassandra/management/CassandraHelpLayout.java +++ b/src/java/org/apache/cassandra/management/CassandraHelpLayout.java @@ -346,6 +346,21 @@ 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); } @@ -448,9 +463,9 @@ public Ansi.Text[][] render(CommandLine.Model.OptionSpec option, IParamLabelRend 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)}; + 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; } } @@ -466,7 +481,7 @@ public Ansi.Text[][] render(CommandLine.Model.PositionalParamSpec param, IParamL 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.text(descriptionString)) }; + 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 index 05ece3c86151..58706f21e162 100644 --- a/src/java/org/apache/cassandra/management/CommandUtils.java +++ b/src/java/org/apache/cassandra/management/CommandUtils.java @@ -18,21 +18,11 @@ package org.apache.cassandra.management; -import java.io.IOException; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.ExecutionException; import org.apache.cassandra.db.compaction.CompactionManagerMBean; import org.apache.cassandra.service.StorageServiceMBean; -import org.apache.cassandra.tools.NodeProbe; -import org.apache.cassandra.tools.NodeTool; - -import static com.google.common.collect.Iterables.toArray; -import static org.apache.commons.lang3.ArrayUtils.EMPTY_STRING_ARRAY; public final class CommandUtils { diff --git a/src/java/org/apache/cassandra/management/api/AbortBootstrap.java b/src/java/org/apache/cassandra/management/api/AbortBootstrap.java index e5ce36506a4b..cbea96efedc5 100644 --- a/src/java/org/apache/cassandra/management/api/AbortBootstrap.java +++ b/src/java/org/apache/cassandra/management/api/AbortBootstrap.java @@ -30,10 +30,10 @@ public class AbortBootstrap extends BaseCommand { @Option(names = "--node", description = "Node ID of the node that failed bootstrap") - private String nodeId = EMPTY; + public String nodeId = EMPTY; @Option(names = "--ip", description = "IP of the node that failed bootstrap") - private String endpoint = EMPTY; + public String endpoint = EMPTY; @Override public void execute(ServiceBridge probe) diff --git a/src/java/org/apache/cassandra/management/api/Assassinate.java b/src/java/org/apache/cassandra/management/api/Assassinate.java index 93bdb58b0de7..49a7914dd126 100644 --- a/src/java/org/apache/cassandra/management/api/Assassinate.java +++ b/src/java/org/apache/cassandra/management/api/Assassinate.java @@ -28,7 +28,7 @@ public class Assassinate extends BaseCommand { @CommandLine.Parameters(description = "IP address of the endpoint to assassinate", arity = "1") - private String ip_address = EMPTY; + public String ip_address = EMPTY; @Override public void execute(ServiceBridge probe) diff --git a/src/java/org/apache/cassandra/management/api/Compact.java b/src/java/org/apache/cassandra/management/api/Compact.java index a5596b8c357a..e74b81141c9f 100644 --- a/src/java/org/apache/cassandra/management/api/Compact.java +++ b/src/java/org/apache/cassandra/management/api/Compact.java @@ -20,7 +20,6 @@ import java.util.ArrayList; import java.util.List; -import org.apache.cassandra.db.compaction.CompactionManagerMBean; import org.apache.cassandra.management.BaseCommand; import org.apache.cassandra.management.ServiceBridge; import picocli.CommandLine; @@ -34,24 +33,24 @@ @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 ...", arity = "0..*", + @CommandLine.Parameters(paramLabel = "[ ...] or ...", description = "The keyspace followed by one or many tables or list of SSTable data files when using --user-defined") - private List args = new ArrayList<>(); + public List args = new ArrayList<>(); @CommandLine.Option(names = { "-s", "--split-output"}, description = "Use -s to not create a single big file") - private boolean splitOutput = false; + public boolean splitOutput = false; @CommandLine.Option(names = { "--user-defined"}, description = "Use --user-defined to submit listed files for user-defined compaction") - private boolean userDefined = false; + public boolean userDefined = false; @CommandLine.Option(names = { "-st", "--start-token"}, description = "Use -st to specify a token at which the compaction range starts (inclusive)") - private String startToken = EMPTY; + public String startToken = EMPTY; @CommandLine.Option(names = { "-et", "--end-token"}, description = "Use -et to specify a token at which compaction range ends (inclusive)") - private String endToken = EMPTY; + public String endToken = EMPTY; @CommandLine.Option(names = { "--partition"}, description = "String representation of the partition key") - private String partitionKey = EMPTY; + public String partitionKey = EMPTY; @Override public void execute(ServiceBridge probe) diff --git a/src/java/org/apache/cassandra/management/api/ForceCompact.java b/src/java/org/apache/cassandra/management/api/ForceCompact.java index 146aa345e80f..25d18d0e597c 100644 --- a/src/java/org/apache/cassandra/management/api/ForceCompact.java +++ b/src/java/org/apache/cassandra/management/api/ForceCompact.java @@ -37,16 +37,16 @@ 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" }) - private List args; + public List args; @CommandLine.Parameters(index = "0", arity = "1", description = "The keyspace name to compact") - private String keyspace; + public String keyspace; @CommandLine.Parameters(index = "1", arity = "1", description = "The table name to compact") - private String table; + public String table; @CommandLine.Parameters(index = "2..*", arity = "1", description = "The partition keys to compact") - private String[] keys; + public String[] keys; @Override public void execute(ServiceBridge probe) diff --git a/src/java/org/apache/cassandra/management/api/JmxConnectionMixin.java b/src/java/org/apache/cassandra/management/api/JmxConnectionMixin.java index 4439f9afaf98..dc0ee5223f6a 100644 --- a/src/java/org/apache/cassandra/management/api/JmxConnectionMixin.java +++ b/src/java/org/apache/cassandra/management/api/JmxConnectionMixin.java @@ -41,7 +41,6 @@ /** * Command options for NodeTool commands that are executed via JMX. */ -@CommandLine.Command(name = "connect", description = "Connect NodeTool to a Cassandra node using JMX") public class JmxConnectionMixin { public static final String MIXIN_KEY = "jmx"; @@ -82,7 +81,8 @@ public static int executionStrategy(CommandLine.ParseResult parseResult) CommandLine.Model.CommandSpec jmx = lastParent.mixins().get(MIXIN_KEY); Preconditions.checkNotNull(jmx, "No JmxConnect mixin found in the command hierarchy"); - ((BaseCommand) lastParent.userObject()).setBridge(((JmxConnectionMixin) jmx.userObject()).init(lastParent)); + if (lastParent.userObject() instanceof BaseCommand) + ((BaseCommand) lastParent.userObject()).setBridge(((JmxConnectionMixin) jmx.userObject()).init(lastParent)); return new CommandLine.RunLast().execute(parseResult); } diff --git a/src/java/org/apache/cassandra/management/api/TopLevelCommand.java b/src/java/org/apache/cassandra/management/api/TopLevelCommand.java index 4716b6e7bc59..125d32fcedba 100644 --- a/src/java/org/apache/cassandra/management/api/TopLevelCommand.java +++ b/src/java/org/apache/cassandra/management/api/TopLevelCommand.java @@ -21,6 +21,8 @@ import org.apache.cassandra.management.CassandraHelpCommand; import picocli.CommandLine; +import static org.apache.cassandra.management.CassandraHelpCommand.printTopCommandUsage; + @CommandLine.Command(name = "nodetool", description = "Manage your Cassandra cluster", subcommands = { CassandraHelpCommand.class, @@ -28,6 +30,15 @@ Assassinate.class, ForceCompact.class, Compact.class }) -public class TopLevelCommand +public class TopLevelCommand implements Runnable { + @CommandLine.Spec + public CommandLine.Model.CommandSpec spec; + + public void run() + { + printTopCommandUsage(spec.commandLine(), + spec.commandLine().getColorScheme(), + spec.commandLine().getOut()); + } } diff --git a/src/java/org/apache/cassandra/tools/NodeToolV2.java b/src/java/org/apache/cassandra/tools/NodeToolV2.java index 7f12758e7f73..eea7686f8c6e 100644 --- a/src/java/org/apache/cassandra/tools/NodeToolV2.java +++ b/src/java/org/apache/cassandra/tools/NodeToolV2.java @@ -134,27 +134,4 @@ else if (field.getType().equals(Output.class)) return (K) bean; } } - -// public static SortedMap getOwnershipByDcWithPort(NodeProbe probe, boolean resolveIp, -// Map tokenToEndpoint, -// Map ownerships) -// { -// SortedMap ownershipByDc = Maps.newTreeMap(); -// EndpointSnitchInfoMBean epSnitchInfo = probe.getEndpointSnitchInfoProxy(); -// try -// { -// for (Entry tokenAndEndPoint : tokenToEndpoint.entrySet()) -// { -// String dc = epSnitchInfo.getDatacenter(tokenAndEndPoint.getValue()); -// if (!ownershipByDc.containsKey(dc)) -// ownershipByDc.put(dc, new SetHostStatWithPort(resolveIp)); -// ownershipByDc.get(dc).add(tokenAndEndPoint.getKey(), tokenAndEndPoint.getValue(), ownerships); -// } -// } -// catch (UnknownHostException e) -// { -// throw new RuntimeException(e); -// } -// return ownershipByDc; -// } } diff --git a/test/unit/org/apache/cassandra/tools/nodetool/CQLToolRunnerTester.java b/test/unit/org/apache/cassandra/tools/nodetool/CQLToolRunnerTester.java index 53f5c3a665a9..88718be27770 100644 --- a/test/unit/org/apache/cassandra/tools/nodetool/CQLToolRunnerTester.java +++ b/test/unit/org/apache/cassandra/tools/nodetool/CQLToolRunnerTester.java @@ -22,6 +22,7 @@ import java.util.Map; import java.util.Set; +import org.junit.BeforeClass; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -45,6 +46,13 @@ public static Set runners() return runnersMap.keySet(); } + @BeforeClass + public static void setupNetwork() throws Throwable + { + requireNetwork(); + startJMXServer(); + } + protected ToolRunner.ToolResult invokeNodetool(String... args) { return runnersMap.get(runner).execute(args); diff --git a/test/unit/org/apache/cassandra/tools/nodetool/CompactTest.java b/test/unit/org/apache/cassandra/tools/nodetool/CompactTest.java index 60b55bf226f8..0f844ca247ba 100644 --- a/test/unit/org/apache/cassandra/tools/nodetool/CompactTest.java +++ b/test/unit/org/apache/cassandra/tools/nodetool/CompactTest.java @@ -19,7 +19,6 @@ import java.util.Arrays; -import org.junit.BeforeClass; import org.junit.Test; import org.apache.cassandra.db.ColumnFamilyStore; @@ -29,13 +28,6 @@ public class CompactTest extends CQLToolRunnerTester { - @BeforeClass - public static void setup() throws Throwable - { - requireNetwork(); - startJMXServer(); - } - @Test public void keyPresent() throws Throwable { diff --git a/test/unit/org/apache/cassandra/tools/nodetool/ForceCompactionTest.java b/test/unit/org/apache/cassandra/tools/nodetool/ForceCompactionTest.java index 878433d702cb..528cd939dbcc 100644 --- a/test/unit/org/apache/cassandra/tools/nodetool/ForceCompactionTest.java +++ b/test/unit/org/apache/cassandra/tools/nodetool/ForceCompactionTest.java @@ -24,14 +24,10 @@ import java.util.Random; import java.util.concurrent.TimeUnit; -import org.apache.cassandra.Util; - import org.junit.Before; import org.junit.Test; -import static org.junit.Assert.*; - -import org.apache.cassandra.cql3.CQLTester; +import org.apache.cassandra.Util; import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.db.Keyspace; import org.apache.cassandra.db.rows.Cell; @@ -41,7 +37,11 @@ import org.apache.cassandra.io.sstable.ISSTableScanner; import org.apache.cassandra.io.sstable.format.SSTableReader; -public class ForceCompactionTest extends CQLTester +import static org.apache.commons.lang3.ArrayUtils.addAll; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class ForceCompactionTest extends CQLToolRunnerTester { private final static int NUM_PARTITIONS = 10; private final static int NUM_ROWS = 100; @@ -241,7 +241,8 @@ private void forceCompact(String[] partitionKeysIgnoreGcGrace) if (cfs != null) { cfs.forceMajorCompaction(); - cfs.forceCompactionKeysIgnoringGcGrace(partitionKeysIgnoreGcGrace); + invokeNodetool(addAll(new String[]{ "forcecompact", cfs.keyspace.getName(), cfs.getTableName() }, + partitionKeysIgnoreGcGrace)).assertOnCleanExit(); } } From 85428874a86ceec97c75feafd0524851950e09ac Mon Sep 17 00:00:00 2001 From: Maxim Muzafarov Date: Fri, 21 Jun 2024 13:13:41 +0200 Subject: [PATCH 7/8] wip --- .../apache/cassandra/config/CassandraRelevantProperties.java | 2 +- src/java/org/apache/cassandra/management/api/Compact.java | 2 +- .../apache/cassandra/management/api/JmxConnectionMixin.java | 3 ++- src/java/org/apache/cassandra/tools/NodeToolV2.java | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java b/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java index 565d21e6ff39..cfec7274f1f8 100644 --- a/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java +++ b/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java @@ -77,7 +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_DEFAULT_LAYOUT("cassandra.cli.picocli.layout", "false"), + 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/api/Compact.java b/src/java/org/apache/cassandra/management/api/Compact.java index e74b81141c9f..c50ed999d78c 100644 --- a/src/java/org/apache/cassandra/management/api/Compact.java +++ b/src/java/org/apache/cassandra/management/api/Compact.java @@ -33,7 +33,7 @@ @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 ...", + @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<>(); diff --git a/src/java/org/apache/cassandra/management/api/JmxConnectionMixin.java b/src/java/org/apache/cassandra/management/api/JmxConnectionMixin.java index dc0ee5223f6a..16c59f895179 100644 --- a/src/java/org/apache/cassandra/management/api/JmxConnectionMixin.java +++ b/src/java/org/apache/cassandra/management/api/JmxConnectionMixin.java @@ -79,7 +79,8 @@ public static int executionStrategy(CommandLine.ParseResult parseResult) int start = indexOfLastSubcommandWithSameParent(parsedCommands); CommandLine.Model.CommandSpec lastParent = parsedCommands.get(start).getCommandSpec(); CommandLine.Model.CommandSpec jmx = lastParent.mixins().get(MIXIN_KEY); - Preconditions.checkNotNull(jmx, "No JmxConnect mixin found in the command hierarchy"); + 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)); diff --git a/src/java/org/apache/cassandra/tools/NodeToolV2.java b/src/java/org/apache/cassandra/tools/NodeToolV2.java index eea7686f8c6e..a1fa5691b7af 100644 --- a/src/java/org/apache/cassandra/tools/NodeToolV2.java +++ b/src/java/org/apache/cassandra/tools/NodeToolV2.java @@ -96,7 +96,7 @@ public int execute(String... args) private static void configureCliLayout(CommandLine commandLine) { - if (CassandraRelevantProperties.CASSANDRA_CLI_DEFAULT_LAYOUT.getBoolean()) + if (CassandraRelevantProperties.CASSANDRA_CLI_PICOCLI_LAYOUT.getBoolean()) return; commandLine.setHelpFactory(CassandraHelpLayout::new) From 0644b1b9cac27ea75bfb5b035920bc42f2f8cb0e Mon Sep 17 00:00:00 2001 From: Maxim Muzafarov Date: Fri, 21 Jun 2024 13:19:51 +0200 Subject: [PATCH 8/8] all commands --- .../management/api/BootstrapResume.java | 52 ++ .../management/api/CIDRFilteringStats.java | 93 ++++ .../cassandra/management/api/CMSAdmin.java | 186 +++++++ .../cassandra/management/api/Cleanup.java | 62 +++ .../management/api/ClearSnapshot.java | 129 +++++ .../cassandra/management/api/ClientStats.java | 162 ++++++ .../management/api/CompactionHistory.java | 48 ++ .../management/api/CompactionStats.java | 171 +++++++ .../cassandra/management/api/DataPaths.java | 53 ++ .../management/api/Decommission.java | 58 +++ .../management/api/DescribeCluster.java | 158 ++++++ .../management/api/DescribeRing.java | 53 ++ .../management/api/DisableAuditLog.java | 33 ++ .../management/api/DisableAutoCompaction.java | 52 ++ .../management/api/DisableBackup.java | 32 ++ .../management/api/DisableBinary.java | 36 ++ .../management/api/DisableFullQueryLog.java | 33 ++ .../management/api/DisableGossip.java | 32 ++ .../management/api/DisableHandoff.java | 32 ++ .../management/api/DisableHintsForDC.java | 42 ++ .../api/DisableOldProtocolVersions.java | 33 ++ .../cassandra/management/api/Drain.java | 41 ++ .../management/api/DropCIDRGroup.java | 50 ++ .../management/api/EnableAuditLog.java | 87 ++++ .../management/api/EnableAutoCompaction.java | 52 ++ .../management/api/EnableBackup.java | 32 ++ .../management/api/EnableBinary.java | 32 ++ .../management/api/EnableFullQueryLog.java | 65 +++ .../management/api/EnableGossip.java | 32 ++ .../management/api/EnableHandoff.java | 32 ++ .../management/api/EnableHintsForDC.java | 42 ++ .../api/EnableOldProtocolVersions.java | 34 ++ .../management/api/FailureDetectorInfo.java | 44 ++ .../cassandra/management/api/Flush.java | 51 ++ .../management/api/GarbageCollect.java | 65 +++ .../cassandra/management/api/GcStats.java | 36 ++ .../cassandra/management/api/GetAuditLog.java | 56 ++ .../management/api/GetAuthCacheConfig.java | 47 ++ .../api/GetBatchlogReplayTrottle.java | 33 ++ .../management/api/GetCIDRGroupsOfIP.java | 50 ++ .../management/api/GetColumnIndexSize.java | 33 ++ .../api/GetCompactionThreshold.java | 49 ++ .../api/GetCompactionThroughput.java | 49 ++ .../management/api/GetConcurrency.java | 50 ++ .../api/GetConcurrentCompactors.java | 33 ++ .../api/GetConcurrentViewBuilders.java | 33 ++ .../management/api/GetDefaultKeyspaceRF.java | 32 ++ .../management/api/GetEndpoints.java | 61 +++ .../management/api/GetFullQueryLog.java | 48 ++ .../api/GetInterDCStreamThroughput.java | 87 ++++ .../management/api/GetLoggingLevels.java | 37 ++ .../management/api/GetMaxHintWindow.java | 33 ++ .../cassandra/management/api/GetSSTables.java | 70 +++ .../cassandra/management/api/GetSeeds.java | 43 ++ .../management/api/GetSnapshotThrottle.java | 36 ++ .../management/api/GetStreamThroughput.java | 86 ++++ .../cassandra/management/api/GetTimeout.java | 51 ++ .../management/api/GetTraceProbability.java | 32 ++ .../cassandra/management/api/GossipInfo.java | 36 ++ .../cassandra/management/api/HostStat.java | 41 ++ .../management/api/HostStatWithPort.java | 47 ++ .../cassandra/management/api/Import.java | 119 +++++ .../apache/cassandra/management/api/Info.java | 208 ++++++++ .../api/InvalidateCIDRPermissionsCache.java | 56 ++ .../api/InvalidateCounterCache.java | 32 ++ .../api/InvalidateCredentialsCache.java | 49 ++ .../api/InvalidateJmxPermissionsCache.java | 48 ++ .../management/api/InvalidateKeyCache.java | 32 ++ .../InvalidateNetworkPermissionsCache.java | 49 ++ .../api/InvalidatePermissionsCache.java | 188 +++++++ .../management/api/InvalidateRolesCache.java | 50 ++ .../management/api/InvalidateRowCache.java | 32 ++ .../apache/cassandra/management/api/Join.java | 43 ++ .../management/api/ListPendingHints.java | 98 ++++ .../management/api/ListSnapshots.java | 97 ++++ .../apache/cassandra/management/api/Move.java | 46 ++ .../cassandra/management/api/NetStats.java | 172 +++++++ .../management/api/PauseHandoff.java | 32 ++ .../cassandra/management/api/ProfileLoad.java | 187 +++++++ .../management/api/ProxyHistograms.java | 61 +++ .../management/api/RangeKeySample.java | 39 ++ .../cassandra/management/api/Rebuild.java | 64 +++ .../management/api/RebuildIndex.java | 43 ++ .../management/api/RecompressSSTables.java | 58 +++ .../cassandra/management/api/Refresh.java | 43 ++ .../management/api/RefreshSizeEstimates.java | 33 ++ .../management/api/ReloadCIDRGroupsCache.java | 36 ++ .../management/api/ReloadLocalSchema.java | 32 ++ .../cassandra/management/api/ReloadSeeds.java | 48 ++ .../management/api/ReloadTriggers.java | 32 ++ .../management/api/RelocateSSTables.java | 55 ++ .../cassandra/management/api/RemoveNode.java | 54 ++ .../cassandra/management/api/Repair.java | 205 ++++++++ .../cassandra/management/api/RepairAdmin.java | 331 ++++++++++++ .../management/api/ReplayBatchlog.java | 42 ++ .../management/api/ResetFullQueryLog.java | 33 ++ .../management/api/ResetLocalSchema.java | 40 ++ .../management/api/ResumeHandoff.java | 32 ++ .../apache/cassandra/management/api/Ring.java | 184 +++++++ .../cassandra/management/api/Scrub.java | 83 +++ .../management/api/SetAuthCacheConfig.java | 114 +++++ .../api/SetBatchlogReplayThrottle.java | 37 ++ .../management/api/SetCacheCapacity.java | 45 ++ .../management/api/SetCacheKeysToSave.java | 45 ++ .../management/api/SetColumnIndexSize.java | 38 ++ .../api/SetCompactionThreshold.java | 50 ++ .../api/SetCompactionThroughput.java | 36 ++ .../management/api/SetConcurrency.java | 61 +++ .../api/SetConcurrentCompactors.java | 39 ++ .../api/SetConcurrentViewBuilders.java | 39 ++ .../management/api/SetDefaultKeyspaceRF.java | 36 ++ .../api/SetHintedHandoffThrottleInKB.java | 36 ++ .../cassandra/management/api/SetHostStat.java | 54 ++ .../management/api/SetHostStatWithPort.java | 56 ++ .../api/SetInterDCStreamThroughput.java | 54 ++ .../management/api/SetLoggingLevel.java | 103 ++++ .../management/api/SetMaxHintWindow.java | 37 ++ .../management/api/SetSnapshotThrottle.java | 36 ++ .../management/api/SetStreamThroughput.java | 54 ++ .../cassandra/management/api/SetTimeout.java | 54 ++ .../management/api/SetTraceProbability.java | 39 ++ .../apache/cassandra/management/api/Sjk.java | 484 ++++++++++++++++++ .../cassandra/management/api/Snapshot.java | 120 +++++ .../cassandra/management/api/Status.java | 192 +++++++ .../management/api/StatusAutoCompaction.java | 76 +++ .../management/api/StatusBackup.java | 35 ++ .../management/api/StatusBinary.java | 35 ++ .../management/api/StatusGossip.java | 35 ++ .../management/api/StatusHandoff.java | 38 ++ .../apache/cassandra/management/api/Stop.java | 50 ++ .../cassandra/management/api/StopDaemon.java | 42 ++ .../management/api/TableHistograms.java | 184 +++++++ .../cassandra/management/api/TableStats.java | 112 ++++ .../management/api/TopPartitions.java | 27 + .../cassandra/management/api/TpStats.java | 55 ++ .../management/api/TruncateHints.java | 41 ++ .../management/api/UpdateCIDRGroup.java | 57 +++ .../management/api/UpgradeSSTable.java | 83 +++ .../cassandra/management/api/Verify.java | 102 ++++ .../cassandra/management/api/Version.java | 40 ++ .../management/api/ViewBuildStatus.java | 86 ++++ 141 files changed, 9461 insertions(+) create mode 100644 src/java/org/apache/cassandra/management/api/BootstrapResume.java create mode 100644 src/java/org/apache/cassandra/management/api/CIDRFilteringStats.java create mode 100644 src/java/org/apache/cassandra/management/api/CMSAdmin.java create mode 100644 src/java/org/apache/cassandra/management/api/Cleanup.java create mode 100644 src/java/org/apache/cassandra/management/api/ClearSnapshot.java create mode 100644 src/java/org/apache/cassandra/management/api/ClientStats.java create mode 100644 src/java/org/apache/cassandra/management/api/CompactionHistory.java create mode 100644 src/java/org/apache/cassandra/management/api/CompactionStats.java create mode 100644 src/java/org/apache/cassandra/management/api/DataPaths.java create mode 100644 src/java/org/apache/cassandra/management/api/Decommission.java create mode 100644 src/java/org/apache/cassandra/management/api/DescribeCluster.java create mode 100644 src/java/org/apache/cassandra/management/api/DescribeRing.java create mode 100644 src/java/org/apache/cassandra/management/api/DisableAuditLog.java create mode 100644 src/java/org/apache/cassandra/management/api/DisableAutoCompaction.java create mode 100644 src/java/org/apache/cassandra/management/api/DisableBackup.java create mode 100644 src/java/org/apache/cassandra/management/api/DisableBinary.java create mode 100644 src/java/org/apache/cassandra/management/api/DisableFullQueryLog.java create mode 100644 src/java/org/apache/cassandra/management/api/DisableGossip.java create mode 100644 src/java/org/apache/cassandra/management/api/DisableHandoff.java create mode 100644 src/java/org/apache/cassandra/management/api/DisableHintsForDC.java create mode 100644 src/java/org/apache/cassandra/management/api/DisableOldProtocolVersions.java create mode 100644 src/java/org/apache/cassandra/management/api/Drain.java create mode 100644 src/java/org/apache/cassandra/management/api/DropCIDRGroup.java create mode 100644 src/java/org/apache/cassandra/management/api/EnableAuditLog.java create mode 100644 src/java/org/apache/cassandra/management/api/EnableAutoCompaction.java create mode 100644 src/java/org/apache/cassandra/management/api/EnableBackup.java create mode 100644 src/java/org/apache/cassandra/management/api/EnableBinary.java create mode 100644 src/java/org/apache/cassandra/management/api/EnableFullQueryLog.java create mode 100644 src/java/org/apache/cassandra/management/api/EnableGossip.java create mode 100644 src/java/org/apache/cassandra/management/api/EnableHandoff.java create mode 100644 src/java/org/apache/cassandra/management/api/EnableHintsForDC.java create mode 100644 src/java/org/apache/cassandra/management/api/EnableOldProtocolVersions.java create mode 100644 src/java/org/apache/cassandra/management/api/FailureDetectorInfo.java create mode 100644 src/java/org/apache/cassandra/management/api/Flush.java create mode 100644 src/java/org/apache/cassandra/management/api/GarbageCollect.java create mode 100644 src/java/org/apache/cassandra/management/api/GcStats.java create mode 100644 src/java/org/apache/cassandra/management/api/GetAuditLog.java create mode 100644 src/java/org/apache/cassandra/management/api/GetAuthCacheConfig.java create mode 100644 src/java/org/apache/cassandra/management/api/GetBatchlogReplayTrottle.java create mode 100644 src/java/org/apache/cassandra/management/api/GetCIDRGroupsOfIP.java create mode 100644 src/java/org/apache/cassandra/management/api/GetColumnIndexSize.java create mode 100644 src/java/org/apache/cassandra/management/api/GetCompactionThreshold.java create mode 100644 src/java/org/apache/cassandra/management/api/GetCompactionThroughput.java create mode 100644 src/java/org/apache/cassandra/management/api/GetConcurrency.java create mode 100644 src/java/org/apache/cassandra/management/api/GetConcurrentCompactors.java create mode 100644 src/java/org/apache/cassandra/management/api/GetConcurrentViewBuilders.java create mode 100644 src/java/org/apache/cassandra/management/api/GetDefaultKeyspaceRF.java create mode 100644 src/java/org/apache/cassandra/management/api/GetEndpoints.java create mode 100644 src/java/org/apache/cassandra/management/api/GetFullQueryLog.java create mode 100644 src/java/org/apache/cassandra/management/api/GetInterDCStreamThroughput.java create mode 100644 src/java/org/apache/cassandra/management/api/GetLoggingLevels.java create mode 100644 src/java/org/apache/cassandra/management/api/GetMaxHintWindow.java create mode 100644 src/java/org/apache/cassandra/management/api/GetSSTables.java create mode 100644 src/java/org/apache/cassandra/management/api/GetSeeds.java create mode 100644 src/java/org/apache/cassandra/management/api/GetSnapshotThrottle.java create mode 100644 src/java/org/apache/cassandra/management/api/GetStreamThroughput.java create mode 100644 src/java/org/apache/cassandra/management/api/GetTimeout.java create mode 100644 src/java/org/apache/cassandra/management/api/GetTraceProbability.java create mode 100644 src/java/org/apache/cassandra/management/api/GossipInfo.java create mode 100644 src/java/org/apache/cassandra/management/api/HostStat.java create mode 100644 src/java/org/apache/cassandra/management/api/HostStatWithPort.java create mode 100644 src/java/org/apache/cassandra/management/api/Import.java create mode 100644 src/java/org/apache/cassandra/management/api/Info.java create mode 100644 src/java/org/apache/cassandra/management/api/InvalidateCIDRPermissionsCache.java create mode 100644 src/java/org/apache/cassandra/management/api/InvalidateCounterCache.java create mode 100644 src/java/org/apache/cassandra/management/api/InvalidateCredentialsCache.java create mode 100644 src/java/org/apache/cassandra/management/api/InvalidateJmxPermissionsCache.java create mode 100644 src/java/org/apache/cassandra/management/api/InvalidateKeyCache.java create mode 100644 src/java/org/apache/cassandra/management/api/InvalidateNetworkPermissionsCache.java create mode 100644 src/java/org/apache/cassandra/management/api/InvalidatePermissionsCache.java create mode 100644 src/java/org/apache/cassandra/management/api/InvalidateRolesCache.java create mode 100644 src/java/org/apache/cassandra/management/api/InvalidateRowCache.java create mode 100644 src/java/org/apache/cassandra/management/api/Join.java create mode 100644 src/java/org/apache/cassandra/management/api/ListPendingHints.java create mode 100644 src/java/org/apache/cassandra/management/api/ListSnapshots.java create mode 100644 src/java/org/apache/cassandra/management/api/Move.java create mode 100644 src/java/org/apache/cassandra/management/api/NetStats.java create mode 100644 src/java/org/apache/cassandra/management/api/PauseHandoff.java create mode 100644 src/java/org/apache/cassandra/management/api/ProfileLoad.java create mode 100644 src/java/org/apache/cassandra/management/api/ProxyHistograms.java create mode 100644 src/java/org/apache/cassandra/management/api/RangeKeySample.java create mode 100644 src/java/org/apache/cassandra/management/api/Rebuild.java create mode 100644 src/java/org/apache/cassandra/management/api/RebuildIndex.java create mode 100644 src/java/org/apache/cassandra/management/api/RecompressSSTables.java create mode 100644 src/java/org/apache/cassandra/management/api/Refresh.java create mode 100644 src/java/org/apache/cassandra/management/api/RefreshSizeEstimates.java create mode 100644 src/java/org/apache/cassandra/management/api/ReloadCIDRGroupsCache.java create mode 100644 src/java/org/apache/cassandra/management/api/ReloadLocalSchema.java create mode 100644 src/java/org/apache/cassandra/management/api/ReloadSeeds.java create mode 100644 src/java/org/apache/cassandra/management/api/ReloadTriggers.java create mode 100644 src/java/org/apache/cassandra/management/api/RelocateSSTables.java create mode 100644 src/java/org/apache/cassandra/management/api/RemoveNode.java create mode 100644 src/java/org/apache/cassandra/management/api/Repair.java create mode 100644 src/java/org/apache/cassandra/management/api/RepairAdmin.java create mode 100644 src/java/org/apache/cassandra/management/api/ReplayBatchlog.java create mode 100644 src/java/org/apache/cassandra/management/api/ResetFullQueryLog.java create mode 100644 src/java/org/apache/cassandra/management/api/ResetLocalSchema.java create mode 100644 src/java/org/apache/cassandra/management/api/ResumeHandoff.java create mode 100644 src/java/org/apache/cassandra/management/api/Ring.java create mode 100644 src/java/org/apache/cassandra/management/api/Scrub.java create mode 100644 src/java/org/apache/cassandra/management/api/SetAuthCacheConfig.java create mode 100644 src/java/org/apache/cassandra/management/api/SetBatchlogReplayThrottle.java create mode 100644 src/java/org/apache/cassandra/management/api/SetCacheCapacity.java create mode 100644 src/java/org/apache/cassandra/management/api/SetCacheKeysToSave.java create mode 100644 src/java/org/apache/cassandra/management/api/SetColumnIndexSize.java create mode 100644 src/java/org/apache/cassandra/management/api/SetCompactionThreshold.java create mode 100644 src/java/org/apache/cassandra/management/api/SetCompactionThroughput.java create mode 100644 src/java/org/apache/cassandra/management/api/SetConcurrency.java create mode 100644 src/java/org/apache/cassandra/management/api/SetConcurrentCompactors.java create mode 100644 src/java/org/apache/cassandra/management/api/SetConcurrentViewBuilders.java create mode 100644 src/java/org/apache/cassandra/management/api/SetDefaultKeyspaceRF.java create mode 100644 src/java/org/apache/cassandra/management/api/SetHintedHandoffThrottleInKB.java create mode 100644 src/java/org/apache/cassandra/management/api/SetHostStat.java create mode 100644 src/java/org/apache/cassandra/management/api/SetHostStatWithPort.java create mode 100644 src/java/org/apache/cassandra/management/api/SetInterDCStreamThroughput.java create mode 100644 src/java/org/apache/cassandra/management/api/SetLoggingLevel.java create mode 100644 src/java/org/apache/cassandra/management/api/SetMaxHintWindow.java create mode 100644 src/java/org/apache/cassandra/management/api/SetSnapshotThrottle.java create mode 100644 src/java/org/apache/cassandra/management/api/SetStreamThroughput.java create mode 100644 src/java/org/apache/cassandra/management/api/SetTimeout.java create mode 100644 src/java/org/apache/cassandra/management/api/SetTraceProbability.java create mode 100644 src/java/org/apache/cassandra/management/api/Sjk.java create mode 100644 src/java/org/apache/cassandra/management/api/Snapshot.java create mode 100644 src/java/org/apache/cassandra/management/api/Status.java create mode 100644 src/java/org/apache/cassandra/management/api/StatusAutoCompaction.java create mode 100644 src/java/org/apache/cassandra/management/api/StatusBackup.java create mode 100644 src/java/org/apache/cassandra/management/api/StatusBinary.java create mode 100644 src/java/org/apache/cassandra/management/api/StatusGossip.java create mode 100644 src/java/org/apache/cassandra/management/api/StatusHandoff.java create mode 100644 src/java/org/apache/cassandra/management/api/Stop.java create mode 100644 src/java/org/apache/cassandra/management/api/StopDaemon.java create mode 100644 src/java/org/apache/cassandra/management/api/TableHistograms.java create mode 100644 src/java/org/apache/cassandra/management/api/TableStats.java create mode 100644 src/java/org/apache/cassandra/management/api/TopPartitions.java create mode 100644 src/java/org/apache/cassandra/management/api/TpStats.java create mode 100644 src/java/org/apache/cassandra/management/api/TruncateHints.java create mode 100644 src/java/org/apache/cassandra/management/api/UpdateCIDRGroup.java create mode 100644 src/java/org/apache/cassandra/management/api/UpgradeSSTable.java create mode 100644 src/java/org/apache/cassandra/management/api/Verify.java create mode 100644 src/java/org/apache/cassandra/management/api/Version.java create mode 100644 src/java/org/apache/cassandra/management/api/ViewBuildStatus.java 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/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/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/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 header = Lists.newArrayList("keyspace", "table", "min_repaired", "max_repaired"); + if (verbose) + header.add("detail"); + + List> rows = new ArrayList<>(stats.size() + 1); + rows.add(header); + + for (RepairStats stat : stats) + { + List row = Lists.newArrayList(stat.keyspace, + stat.table, + Long.toString(stat.minRepaired), + Long.toString(stat.maxRepaired)); + if (verbose) + { + row.add(Joiner.on(", ").join(Iterables.transform(stat.sections, RepairStats.Section::toString))); + } + rows.add(row); + } + + printTable(rows, out); + } + } + + @Command(name = "cleanup", description = "cleans up pending data from completed sessions. " + + "This happens automatically, but the command is provided " + + "for situations where it needs to be expedited." + + " Use --force to cancel compactions that are preventing promotion") + public static class CleanupDataCmd extends RepairAdmin + { + @Option(title = "force", name = {"-f", "--force"}, description = "Force a cleanup.") + private boolean force = 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; + out.println("Cleaning up data from completed sessions..."); + List compositeData = probe.getRepairServiceProxy().cleanupPending(schemaArgs, getRangeString(startToken, endToken), force); + + List summaries = new ArrayList<>(compositeData.size()); + compositeData.forEach(cd -> summaries.add(CleanupSummary.fromComposite(cd))); + + summaries.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", "successful sessions", "unsuccessful sessions"); + List> rows = new ArrayList<>(summaries.size() + 1); + rows.add(header); + + boolean hasFailures = false; + for (CleanupSummary summary : summaries) + { + List row = Lists.newArrayList(summary.keyspace, + summary.table, + Integer.toString(summary.successful.size()), + Integer.toString(summary.unsuccessful.size())); + + hasFailures |= !summary.unsuccessful.isEmpty(); + rows.add(row); + } + + if (hasFailures) + out.println("Some tables couldn't be cleaned up completely"); + + printTable(rows, out); + } + } + + @Command(name = "cancel", description = "cancel an incremental repair session." + + " Use --force to cancel from a node other than the repair coordinator" + + " Attempting to cancel FINALIZED or FAILED sessions is an error.") + public static class CancelCmd extends RepairAdmin + { + @Option(title = "force", name = {"-f", "--force"}, description = "Force a cancellation.") + private boolean force = false; + + @Option(title = "session", name = {"-s", "--session"}, description = "The session to cancel", required = true) + private String sessionToCancel; + + protected void execute(NodeProbe probe) + { + probe.getRepairServiceProxy().failSession(sessionToCancel, force); + } + } + + private static void printTable(List> rows, PrintStream out) + { + if (rows.isEmpty()) + return; + + // get max col widths + int[] widths = new int[rows.get(0).size()]; + for (List row : rows) + { + assert row.size() == widths.length; + for (int i = 0; i < widths.length; i++) + { + widths[i] = Math.max(widths[i], row.get(i).length()); + } + } + + List fmts = new ArrayList<>(widths.length); + for (int i = 0; i < widths.length; i++) + { + fmts.add("%-" + widths[i] + "s"); + } + + // print + for (List row : rows) + { + List formatted = new ArrayList<>(row.size()); + for (int i = 0; i < widths.length; i++) + { + formatted.add(String.format(fmts.get(i), row.get(i))); + } + out.println(Joiner.on(" | ").join(formatted)); + } + } + + static String getRangeString(String startToken, String endToken) + { + String rangeStr = null; + if (!startToken.isEmpty() || !endToken.isEmpty()) + rangeStr = startToken + ':' + endToken; + return rangeStr; + } +} diff --git a/src/java/org/apache/cassandra/management/api/ReplayBatchlog.java b/src/java/org/apache/cassandra/management/api/ReplayBatchlog.java new file mode 100644 index 000000000000..b0dd54d12cff --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/ReplayBatchlog.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.io.IOError; +import java.io.IOException; + +import io.airlift.airline.Command; +import org.apache.cassandra.tools.NodeProbe; +import org.apache.cassandra.tools.NodeTool; + +@Command(name = "replaybatchlog", description = "Kick off batchlog replay and wait for finish") +public class ReplayBatchlog extends NodeTool.NodeToolCmd +{ + protected void execute(NodeProbe probe) + { + try + { + probe.replayBatchlog(); + } + catch (IOException e) + { + throw new IOError(e); + } + } +} diff --git a/src/java/org/apache/cassandra/management/api/ResetFullQueryLog.java b/src/java/org/apache/cassandra/management/api/ResetFullQueryLog.java new file mode 100644 index 000000000000..57d100a1dca9 --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/ResetFullQueryLog.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 = "resetfullquerylog", description = "Stop the full query log and clean files in the configured full query log directory from cassandra.yaml as well as JMX") +public class ResetFullQueryLog extends NodeToolCmd +{ + @Override + public void execute(NodeProbe probe) + { + probe.resetFullQueryLogger(); + } +} diff --git a/src/java/org/apache/cassandra/management/api/ResetLocalSchema.java b/src/java/org/apache/cassandra/management/api/ResetLocalSchema.java new file mode 100644 index 000000000000..786f1dae41b6 --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/ResetLocalSchema.java @@ -0,0 +1,40 @@ +/* + * 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; + +@Command(name = "resetlocalschema", description = "Reset node's local schema and resync") +public class ResetLocalSchema extends NodeToolCmd +{ + @Override + public void execute(NodeProbe probe) + { + try + { + probe.resetLocalSchema(); + } catch (IOException e) + { + throw new RuntimeException(e); + } + } +} diff --git a/src/java/org/apache/cassandra/management/api/ResumeHandoff.java b/src/java/org/apache/cassandra/management/api/ResumeHandoff.java new file mode 100644 index 000000000000..5aaf1db84d44 --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/ResumeHandoff.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 = "resumehandoff", description = "Resume hints delivery process") +public class ResumeHandoff extends NodeToolCmd +{ + @Override + public void execute(NodeProbe probe) + { + probe.resumeHintsDelivery(); + } +} diff --git a/src/java/org/apache/cassandra/management/api/Ring.java b/src/java/org/apache/cassandra/management/api/Ring.java new file mode 100644 index 000000000000..7c9c168693e0 --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/Ring.java @@ -0,0 +1,184 @@ +/* + * 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.net.UnknownHostException; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import com.google.common.collect.LinkedHashMultimap; + +import io.airlift.airline.Arguments; +import io.airlift.airline.Command; +import io.airlift.airline.Option; +import org.apache.cassandra.locator.EndpointSnitchInfoMBean; +import org.apache.cassandra.tools.NodeProbe; +import org.apache.cassandra.tools.NodeTool; +import org.apache.cassandra.tools.NodeTool.NodeToolCmd; + +import static java.lang.String.format; + +@Command(name = "ring", description = "Print information about the token ring") +public class Ring extends NodeToolCmd +{ + @Arguments(description = "Specify a keyspace for accurate ownership information (topology awareness)") + private String keyspace = null; + + @Option(title = "resolve_ip", name = {"-r", "--resolve-ip"}, description = "Show node domain names instead of IPs") + private boolean resolveIp = false; + + private PrintStream out; + private EndpointSnitchInfoMBean epSnitchInfo; + private Collection liveNodes, deadNodes, joiningNodes, leavingNodes, movingNodes; + private Map loadMap; + + @Override + public void execute(NodeProbe probe) + { + out = probe.output().out; + liveNodes = probe.getLiveNodes(true); + deadNodes = probe.getUnreachableNodes(true); + joiningNodes = probe.getJoiningNodes(true); + leavingNodes = probe.getLeavingNodes(true); + movingNodes = probe.getMovingNodes(true); + loadMap = probe.getLoadMap(true); + epSnitchInfo = probe.getEndpointSnitchInfoProxy(); + + Map tokensToEndpoints = probe.getTokenToEndpointMap(true); + LinkedHashMultimap endpointsToTokens = LinkedHashMultimap.create(); + boolean haveVnodes = false; + for (Entry entry : tokensToEndpoints.entrySet()) + { + haveVnodes |= endpointsToTokens.containsKey(entry.getValue()); + endpointsToTokens.put(entry.getValue(), entry.getKey()); + } + + int maxAddressLength = Collections.max(endpointsToTokens.keys(), + Comparator.comparingInt(String::length)).length(); + + String formatPlaceholder = "%%-%ds %%-12s%%-7s%%-8s%%-16s%%-20s%%-44s%%n"; + String format = format(formatPlaceholder, maxAddressLength); + + StringBuilder errors = new StringBuilder(); + boolean showEffectiveOwnership = true; + + // Calculate per-token ownership of the ring + Map ownerships = null; + try + { + ownerships = probe.effectiveOwnershipWithPort(keyspace); + } + catch (IllegalStateException ex) + { + try + { + ownerships = probe.getOwnershipWithPort(); + errors.append("Note: ").append(ex.getMessage()).append("%n"); + showEffectiveOwnership = false; + } + catch (Exception e) + { + out.printf("%nError: %s%n", ex.getMessage()); + System.exit(1); + } + } + catch (IllegalArgumentException ex) + { + out.printf("%nError: %s%n", ex.getMessage()); + System.exit(1); + } + + out.println(); + for (Entry entry : NodeTool.getOwnershipByDcWithPort(probe, resolveIp, tokensToEndpoints, ownerships).entrySet()) + printDc(format, entry.getKey(), endpointsToTokens, entry.getValue(), showEffectiveOwnership); + + if (haveVnodes) + { + out.println(" Warning: \"nodetool ring\" is used to output all the tokens of a node."); + out.println(" To view status related info of a node use \"nodetool status\" instead.\n"); + } + + out.printf("%n " + errors); + } + + private void printDc(String format, String dc, + LinkedHashMultimap endpointsToTokens, + SetHostStatWithPort hoststats, boolean showEffectiveOwnership) + { + out.println("Datacenter: " + dc); + out.println("=========="); + + // get the total amount of replicas for this dc and the last token in this dc's ring + List tokens = new ArrayList<>(); + String lastToken = ""; + + for (HostStatWithPort stat : hoststats) + { + tokens.addAll(endpointsToTokens.get(stat.endpointWithPort.getHostAddressAndPort())); + lastToken = tokens.get(tokens.size() - 1); + } + + out.printf(format, "Address", "Rack", "Status", "State", "Load", "Owns", "Token"); + + if (hoststats.size() > 1) + out.printf(format, "", "", "", "", "", "", lastToken); + else + out.println(); + + for (HostStatWithPort stat : hoststats) + { + String endpoint = stat.endpointWithPort.getHostAddressAndPort(); + String rack; + try + { + rack = epSnitchInfo.getRack(endpoint); + } + catch (UnknownHostException e) + { + rack = "Unknown"; + } + + String status = liveNodes.contains(endpoint) + ? "Up" + : deadNodes.contains(endpoint) + ? "Down" + : "?"; + + String state = "Normal"; + + if (joiningNodes.contains(endpoint)) + state = "Joining"; + else if (leavingNodes.contains(endpoint)) + state = "Leaving"; + else if (movingNodes.contains(endpoint)) + state = "Moving"; + + String load = loadMap.getOrDefault(endpoint, "?"); + String owns = stat.owns != null && showEffectiveOwnership? new DecimalFormat("##0.00%").format(stat.owns) : "?"; + out.printf(format, stat.ipOrDns(printPort), rack, status, state, load, owns, stat.token); + } + out.println(); + } +} diff --git a/src/java/org/apache/cassandra/management/api/Scrub.java b/src/java/org/apache/cassandra/management/api/Scrub.java new file mode 100644 index 000000000000..c5fa4ad2a2a1 --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/Scrub.java @@ -0,0 +1,83 @@ +/* + * 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.StandaloneScrubber; + +@Command(name = "scrub", description = "Scrub (rebuild sstables for) one or more tables") +public class Scrub extends NodeToolCmd +{ + @Arguments(usage = "[ ...]", description = "The keyspace followed by one or many tables") + private List args = new ArrayList<>(); + + @Option(title = "disable_snapshot", + name = {"-ns", "--no-snapshot"}, + description = "Scrubbed CFs will be snapshotted first, if disableSnapshot is false. (default false)") + private boolean disableSnapshot = false; + + @Option(title = "skip_corrupted", + name = {"-s", "--skip-corrupted"}, + description = "Skip corrupted partitions even when scrubbing counter tables. (default false)") + private boolean skipCorrupted = false; + + @Option(title = "no_validate", + name = {"-n", "--no-validate"}, + description = "Do not validate columns using column validator") + private boolean noValidation = false; + + @Option(title = "reinsert_overflowed_ttl", + name = {"-r", "--reinsert-overflowed-ttl"}, + description = StandaloneScrubber.REINSERT_OVERFLOWED_TTL_OPTION_DESCRIPTION) + private boolean reinsertOverflowedTTL = false; + + @Option(title = "jobs", + name = {"-j", "--jobs"}, + description = "Number of sstables to scrub 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.scrub(probe.output().out, disableSnapshot, skipCorrupted, !noValidation, reinsertOverflowedTTL, jobs, keyspace, tableNames); + } + catch (IllegalArgumentException e) + { + throw e; + } + catch (Exception e) + { + throw new RuntimeException("Error occurred during scrubbing", e); + } + } + } +} diff --git a/src/java/org/apache/cassandra/management/api/SetAuthCacheConfig.java b/src/java/org/apache/cassandra/management/api/SetAuthCacheConfig.java new file mode 100644 index 000000000000..65701a67cc7a --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/SetAuthCacheConfig.java @@ -0,0 +1,114 @@ +/* + * 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.NodeToolCmd; + +import static com.google.common.base.Preconditions.checkArgument; + +@Command(name = "setauthcacheconfig", description = "Set configuration for Auth cache") +public class SetAuthCacheConfig extends NodeToolCmd +{ + @SuppressWarnings("unused") + @Option(title = "cache-name", + name = {"--cache-name"}, + description = "Name of Auth cache (required)", + required = true) + private String cacheName; + + @SuppressWarnings("unused") + @Option(title = "validity-period", + name = {"--validity-period"}, + description = "Validity period in milliseconds") + private Integer validityPeriod; + + @SuppressWarnings("unused") + @Option(title = "update-interval", + name = {"--update-interval"}, + description = "Update interval in milliseconds") + private Integer updateInterval; + + @SuppressWarnings("unused") + @Option(title = "max-entries", + name = {"--max-entries"}, + description = "Max entries") + private Integer maxEntries; + + @SuppressWarnings("unused") + @Option(title = "enable-active-update", + name = {"--enable-active-update"}, + description = "Enable active update") + private Boolean enableActiveUpdate; + + @SuppressWarnings("unused") + @Option(title = "disable-active-update", + name = {"--disable-active-update"}, + description = "Disable active update") + private Boolean disableActiveUpdate; + + @Override + public void execute(NodeProbe probe) + { + Boolean activeUpdate = getActiveUpdate(enableActiveUpdate, disableActiveUpdate); + + checkArgument(validityPeriod != null || updateInterval != null + || maxEntries != null || activeUpdate != null, + "At least one optional parameter need to be passed"); + + AuthCacheMBean authCacheMBean = probe.getAuthCacheMBean(cacheName); + + if (validityPeriod != null) + { + authCacheMBean.setValidity(validityPeriod); + probe.output().out.println("Changed Validity Period to " + validityPeriod); + } + + if (updateInterval != null) + { + authCacheMBean.setUpdateInterval(updateInterval); + probe.output().out.println("Changed Update Interval to " + updateInterval); + } + + if (maxEntries != null) + { + authCacheMBean.setMaxEntries(maxEntries); + probe.output().out.println("Changed Max Entries to " + maxEntries); + } + + if (activeUpdate != null) + { + authCacheMBean.setActiveUpdate(activeUpdate); + probe.output().out.println("Changed Active Update to " + activeUpdate); + } + } + + private Boolean getActiveUpdate(Boolean enableActiveUpdate, Boolean disableActiveUpdate) + { + if (enableActiveUpdate == null && disableActiveUpdate == null) + return null; + + if (enableActiveUpdate != null && disableActiveUpdate != null) + throw new IllegalArgumentException("enable-active-update and disable-active-update cannot be used together"); + + return Boolean.TRUE.equals(enableActiveUpdate) ? Boolean.TRUE : Boolean.FALSE; + } +} diff --git a/src/java/org/apache/cassandra/management/api/SetBatchlogReplayThrottle.java b/src/java/org/apache/cassandra/management/api/SetBatchlogReplayThrottle.java new file mode 100644 index 000000000000..52b54f79eed7 --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/SetBatchlogReplayThrottle.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 io.airlift.airline.Arguments; +import io.airlift.airline.Command; +import org.apache.cassandra.tools.NodeProbe; +import org.apache.cassandra.tools.NodeTool.NodeToolCmd; + +@Command(name = "setbatchlogreplaythrottle", description = "Set batchlog replay throttle in KB per second, or 0 to disable throttling. " + + "This will be reduced proportionally to the number of nodes in the cluster.") +public class SetBatchlogReplayThrottle extends NodeToolCmd +{ + @Arguments(title = "batchlog_replay_throttle", usage = "", description = "Value in KiB per second, 0 to disable throttling", required = true) + private Integer batchlogReplayThrottle = null; + + @Override + public void execute(NodeProbe probe) + { + probe.setBatchlogReplayThrottle(batchlogReplayThrottle); + } +} diff --git a/src/java/org/apache/cassandra/management/api/SetCacheCapacity.java b/src/java/org/apache/cassandra/management/api/SetCacheCapacity.java new file mode 100644 index 000000000000..f56637e919a9 --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/SetCacheCapacity.java @@ -0,0 +1,45 @@ +/* + * 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 = "setcachecapacity", description = "Set global key, row, and counter cache capacities (in MB units)") +public class SetCacheCapacity extends NodeToolCmd +{ + @Arguments(title = " ", + usage = " ", + description = "Key cache, row cache, and counter cache (in MB)", + required = true) + private List args = new ArrayList<>(); + + @Override + public void execute(NodeProbe probe) + { + checkArgument(args.size() == 3, "setcachecapacity requires key-cache-capacity, row-cache-capacity, and counter-cache-capacity args."); + probe.setCacheCapacities(args.get(0), args.get(1), args.get(2)); + } +} diff --git a/src/java/org/apache/cassandra/management/api/SetCacheKeysToSave.java b/src/java/org/apache/cassandra/management/api/SetCacheKeysToSave.java new file mode 100644 index 000000000000..5fd1a6455997 --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/SetCacheKeysToSave.java @@ -0,0 +1,45 @@ +/* + * 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 = "setcachekeystosave", description = "Set number of keys saved by each cache for faster post-restart warmup. 0 to disable") +public class SetCacheKeysToSave extends NodeToolCmd +{ + @Arguments(title = " ", + usage = " ", + description = "The number of keys saved by each cache. 0 to disable", + required = true) + private List args = new ArrayList<>(); + + @Override + public void execute(NodeProbe probe) + { + checkArgument(args.size() == 3, "setcachekeystosave requires key-cache-keys-to-save, row-cache-keys-to-save, and counter-cache-keys-to-save args."); + probe.setCacheKeysToSave(args.get(0), args.get(1), args.get(2)); + } +} diff --git a/src/java/org/apache/cassandra/management/api/SetColumnIndexSize.java b/src/java/org/apache/cassandra/management/api/SetColumnIndexSize.java new file mode 100644 index 000000000000..5c9bc882dbf8 --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/SetColumnIndexSize.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 io.airlift.airline.Arguments; +import io.airlift.airline.Command; +import org.apache.cassandra.tools.NodeProbe; +import org.apache.cassandra.tools.NodeTool.NodeToolCmd; + +@Command(name = "setcolumnindexsize", description = "Set the granularity of the collation index of rows within a partition in KiB") +public class SetColumnIndexSize extends NodeToolCmd +{ + @SuppressWarnings("UnusedDeclaration") + @Arguments(title = "column_index_size", usage = "", description = "Value in KiB", required = true) + private int columnIndexSizeInKiB; + + @Override + protected void execute(NodeProbe probe) + { + probe.setColumnIndexSize(columnIndexSizeInKiB); + } +} diff --git a/src/java/org/apache/cassandra/management/api/SetCompactionThreshold.java b/src/java/org/apache/cassandra/management/api/SetCompactionThreshold.java new file mode 100644 index 000000000000..2388c9214ad8 --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/SetCompactionThreshold.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; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.lang.Integer.parseInt; + +@Command(name = "setcompactionthreshold", description = "Set min and max compaction thresholds for a given table") +public class SetCompactionThreshold extends NodeToolCmd +{ + @Arguments(title = "
", usage = "
", description = "The keyspace, the table, min and max threshold", required = true) + private List args = new ArrayList<>(); + + @Override + public void execute(NodeProbe probe) + { + checkArgument(args.size() == 4, "setcompactionthreshold requires ks, cf, min, and max threshold args."); + + int minthreshold = parseInt(args.get(2)); + int maxthreshold = parseInt(args.get(3)); + checkArgument(minthreshold >= 0 && maxthreshold >= 0, "Thresholds must be positive integers"); + checkArgument(minthreshold <= maxthreshold, "Min threshold cannot be greater than max."); + checkArgument(minthreshold >= 2 || maxthreshold == 0, "Min threshold must be at least 2"); + + probe.setCompactionThreshold(args.get(0), args.get(1), minthreshold, maxthreshold); + } +} diff --git a/src/java/org/apache/cassandra/management/api/SetCompactionThroughput.java b/src/java/org/apache/cassandra/management/api/SetCompactionThroughput.java new file mode 100644 index 000000000000..9d494a70ec8c --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/SetCompactionThroughput.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.Arguments; +import io.airlift.airline.Command; +import org.apache.cassandra.tools.NodeProbe; +import org.apache.cassandra.tools.NodeTool.NodeToolCmd; + +@Command(name = "setcompactionthroughput", description = "Set the MiB/s throughput cap for compaction in the system, or 0 to disable throttling") +public class SetCompactionThroughput extends NodeToolCmd +{ + @Arguments(title = "compaction_throughput", usage = "", description = "Value in MiB, 0 to disable throttling", required = true) + private Integer compactionThroughput = null; + + @Override + public void execute(NodeProbe probe) + { + probe.setCompactionThroughput(compactionThroughput); + } +} diff --git a/src/java/org/apache/cassandra/management/api/SetConcurrency.java b/src/java/org/apache/cassandra/management/api/SetConcurrency.java new file mode 100644 index 000000000000..5389bd210347 --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/SetConcurrency.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.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 = "setconcurrency", description = "Set maximum concurrency for processing stage") +public class SetConcurrency extends NodeToolCmd +{ + @Arguments(title = " | ", + usage = " | ", + description = "Set concurrency for processing stage", + required = true) + private List args = new ArrayList<>(); + + @Override + public void execute(NodeProbe probe) + { + checkArgument(args.size() >= 2 && args.size() <= 3, "setconcurrency requires stage name, optional core pool size and maximum concurrency"); + + int corePoolSize = args.size() == 2 ? -1 : Integer.valueOf(args.get(1)); + int maximumPoolSize = args.size() == 2 ? Integer.valueOf(args.get(1)) : Integer.valueOf(args.get(2)); + + checkArgument(args.size() == 2 || corePoolSize >= 0, "Core pool size must be non-negative"); + checkArgument(maximumPoolSize >= 0, "Maximum pool size must be non-negative"); + + try + { + probe.setConcurrency(args.get(0), corePoolSize, maximumPoolSize); + } + catch (IllegalArgumentException e) + { + String message = e.getMessage() != null ? e.getMessage() : "invalid pool size"; + probe.output().out.println("Unable to set concurrency: " + message); + System.exit(1); + } + } +} diff --git a/src/java/org/apache/cassandra/management/api/SetConcurrentCompactors.java b/src/java/org/apache/cassandra/management/api/SetConcurrentCompactors.java new file mode 100644 index 000000000000..ec45fa1cf7fc --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/SetConcurrentCompactors.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 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 = "setconcurrentcompactors", description = "Set number of concurrent compactors in the system.") +public class SetConcurrentCompactors extends NodeTool.NodeToolCmd +{ + @Arguments(title = "concurrent_compactors", usage = "", description = "Number of concurrent compactors, greater than 0.", required = true) + private Integer concurrentCompactors = null; + + protected void execute(NodeProbe probe) + { + checkArgument(concurrentCompactors > 0, "concurrent_compactors should be great than 0."); + probe.setConcurrentCompactors(concurrentCompactors); + } +} diff --git a/src/java/org/apache/cassandra/management/api/SetConcurrentViewBuilders.java b/src/java/org/apache/cassandra/management/api/SetConcurrentViewBuilders.java new file mode 100644 index 000000000000..8c2a8d538e79 --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/SetConcurrentViewBuilders.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 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 = "setconcurrentviewbuilders", description = "Set the number of concurrent view builders in the system") +public class SetConcurrentViewBuilders extends NodeTool.NodeToolCmd +{ + @Arguments(title = "concurrent_view_builders", usage = "", description = "Number of concurrent view builders, greater than 0.", required = true) + private Integer concurrentViewBuilders = null; + + protected void execute(NodeProbe probe) + { + checkArgument(concurrentViewBuilders > 0, "concurrent_view_builders should be great than 0."); + probe.setConcurrentViewBuilders(concurrentViewBuilders); + } +} diff --git a/src/java/org/apache/cassandra/management/api/SetDefaultKeyspaceRF.java b/src/java/org/apache/cassandra/management/api/SetDefaultKeyspaceRF.java new file mode 100644 index 000000000000..9902bff84816 --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/SetDefaultKeyspaceRF.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.Arguments; +import io.airlift.airline.Command; +import org.apache.cassandra.tools.NodeProbe; +import org.apache.cassandra.tools.NodeTool; + +@Command(name = "setdefaultrf", description = "Sets default keyspace replication factor.") +public class SetDefaultKeyspaceRF extends NodeTool.NodeToolCmd +{ + @Arguments(title = "default_rf", usage = "", description = "Default replication factor", required = true) + private Integer defaultRF = null; + + protected void execute(NodeProbe probe) + { + probe.setDefaultKeyspaceReplicationFactor(defaultRF); + } +} diff --git a/src/java/org/apache/cassandra/management/api/SetHintedHandoffThrottleInKB.java b/src/java/org/apache/cassandra/management/api/SetHintedHandoffThrottleInKB.java new file mode 100644 index 000000000000..d145d9c55fb1 --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/SetHintedHandoffThrottleInKB.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.Arguments; +import io.airlift.airline.Command; +import org.apache.cassandra.tools.NodeProbe; +import org.apache.cassandra.tools.NodeTool.NodeToolCmd; + +@Command(name = "sethintedhandoffthrottlekb", description = "Set hinted handoff throttle in KiB per second, per delivery thread.") +public class SetHintedHandoffThrottleInKB extends NodeToolCmd +{ + @Arguments(title = "throttle_in_kb", usage = "", description = "Value in KiB per second", required = true) + private Integer throttleInKB = null; + + @Override + public void execute(NodeProbe probe) + { + probe.setHintedHandoffThrottleInKB(throttleInKB); + } +} diff --git a/src/java/org/apache/cassandra/management/api/SetHostStat.java b/src/java/org/apache/cassandra/management/api/SetHostStat.java new file mode 100644 index 000000000000..1cb09473ef48 --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/SetHostStat.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.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +public class SetHostStat implements Iterable +{ + final List hostStats = new ArrayList(); + final boolean resolveIp; + + public SetHostStat(boolean resolveIp) + { + this.resolveIp = resolveIp; + } + + public int size() + { + return hostStats.size(); + } + + @Override + public Iterator iterator() + { + return hostStats.iterator(); + } + + public void add(String token, String host, Map ownerships) throws UnknownHostException + { + InetAddress endpoint = InetAddress.getByName(host); + Float owns = ownerships.get(endpoint); + hostStats.add(new HostStat(token, endpoint, resolveIp, owns)); + } +} diff --git a/src/java/org/apache/cassandra/management/api/SetHostStatWithPort.java b/src/java/org/apache/cassandra/management/api/SetHostStatWithPort.java new file mode 100644 index 000000000000..dcdb40575301 --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/SetHostStatWithPort.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.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.cassandra.locator.InetAddressAndPort; + +public class SetHostStatWithPort implements Iterable +{ + final List hostStats = new ArrayList<>(); + final boolean resolveIp; + + public SetHostStatWithPort(boolean resolveIp) + { + this.resolveIp = resolveIp; + } + + public int size() + { + return hostStats.size(); + } + + @Override + public Iterator iterator() + { + return hostStats.iterator(); + } + + public void add(String token, String host, Map ownerships) throws UnknownHostException + { + InetAddressAndPort endpoint = InetAddressAndPort.getByName(host); + Float owns = ownerships.get(endpoint.getHostAddressAndPort()); + hostStats.add(new HostStatWithPort(token, endpoint, resolveIp, owns)); + } +} diff --git a/src/java/org/apache/cassandra/management/api/SetInterDCStreamThroughput.java b/src/java/org/apache/cassandra/management/api/SetInterDCStreamThroughput.java new file mode 100644 index 000000000000..35b93f7532aa --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/SetInterDCStreamThroughput.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 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 = "setinterdcstreamthroughput", description = "Set the throughput cap for inter-datacenter streaming and entire SSTable inter-datacenter streaming in the system, or 0 to disable throttling") +public class SetInterDCStreamThroughput extends NodeToolCmd +{ + @SuppressWarnings("UnusedDeclaration") + @Arguments(title = "inter_dc_stream_throughput", usage = "", description = "Value in megabits, 0 to disable throttling", required = true) + private int interDCStreamThroughput; + + @SuppressWarnings("UnusedDeclaration") + @Option(name = { "-e", "--entire-sstable-throughput" }, description = "Set entire SSTable streaming throughput in MiB/s") + private boolean setEntireSSTableThroughput; + + @SuppressWarnings("UnusedDeclaration") + @Option(name = { "-m", "--mib" }, description = "Set streaming throughput in MiB/s") + private boolean interDCStreamThroughputInMebibytes; + + @Override + public void execute(NodeProbe probe) + { + if (setEntireSSTableThroughput && interDCStreamThroughputInMebibytes) + throw new IllegalArgumentException("You cannot use -e and -m at the same time"); + + if (setEntireSSTableThroughput) + probe.setEntireSSTableInterDCStreamThroughput(interDCStreamThroughput); + else if (interDCStreamThroughputInMebibytes ) + probe.setInterDCStreamThroughputMiB(interDCStreamThroughput); + else + probe.setInterDCStreamThroughput(interDCStreamThroughput); + } +} diff --git a/src/java/org/apache/cassandra/management/api/SetLoggingLevel.java b/src/java/org/apache/cassandra/management/api/SetLoggingLevel.java new file mode 100644 index 000000000000..f0d1cde9d07d --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/SetLoggingLevel.java @@ -0,0 +1,103 @@ +/* + * 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.Collections; +import java.util.List; + +import com.google.common.collect.Lists; + +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 = "setlogginglevel", description = "Set the log level threshold for a given component or class. Will reset to the initial configuration if called with no parameters.") +public class SetLoggingLevel extends NodeToolCmd +{ + @Arguments(usage = " ", description = "The component or class to change the level for and the log level threshold to set. Will reset to initial level if omitted. " + + "Available components: bootstrap, compaction, repair, streaming, cql, ring") + private List args = new ArrayList<>(); + + @Override + public void execute(NodeProbe probe) + { + String target = args.size() >= 1 ? args.get(0) : EMPTY; + String level = args.size() == 2 ? args.get(1) : EMPTY; + + List classQualifiers = Collections.singletonList(target); + if (target.equals("bootstrap")) + { + classQualifiers = Lists.newArrayList( + "org.apache.cassandra.gms", + "org.apache.cassandra.hints", + "org.apache.cassandra.schema", + "org.apache.cassandra.service.StorageService", + "org.apache.cassandra.db.SystemKeyspace", + "org.apache.cassandra.batchlog.BatchlogManager", + "org.apache.cassandra.net.MessagingService"); + } + else if (target.equals("repair")) + { + classQualifiers = Lists.newArrayList( + "org.apache.cassandra.repair", + "org.apache.cassandra.db.compaction.CompactionManager", + "org.apache.cassandra.service.SnapshotVerbHandler"); + } + else if (target.equals("streaming")) + { + classQualifiers = Lists.newArrayList( + "org.apache.cassandra.streaming", + "org.apache.cassandra.dht.RangeStreamer"); + } + else if (target.equals("compaction")) + { + classQualifiers = Lists.newArrayList( + "org.apache.cassandra.db.compaction", + "org.apache.cassandra.db.ColumnFamilyStore", + "org.apache.cassandra.io.sstable.IndexSummaryRedistribution"); + } + else if (target.equals("cql")) + { + classQualifiers = Lists.newArrayList( + "org.apache.cassandra.cql3", + "org.apache.cassandra.auth", + "org.apache.cassandra.batchlog", + "org.apache.cassandra.net.ResponseVerbHandler", + "org.apache.cassandra.service.AbstractReadExecutor", + "org.apache.cassandra.service.AbstractWriteResponseHandler", + "org.apache.cassandra.service.paxos", + "org.apache.cassandra.service.ReadCallback", + "org.apache.cassandra.service.ResponseResolver"); + } + else if (target.equals("ring")) + { + classQualifiers = Lists.newArrayList( + "org.apache.cassandra.gms", + "org.apache.cassandra.service.PendingRangeCalculatorService", + "org.apache.cassandra.service.LoadBroadcaster", + "org.apache.cassandra.transport.Server"); + } + + for (String classQualifier : classQualifiers) + probe.setLoggingLevel(classQualifier, level); + } +} diff --git a/src/java/org/apache/cassandra/management/api/SetMaxHintWindow.java b/src/java/org/apache/cassandra/management/api/SetMaxHintWindow.java new file mode 100644 index 000000000000..254388749435 --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/SetMaxHintWindow.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 io.airlift.airline.Arguments; +import io.airlift.airline.Command; +import org.apache.cassandra.tools.NodeProbe; +import org.apache.cassandra.tools.NodeTool; + +@Command(name = "setmaxhintwindow", description = "Set the specified max hint window in ms") +public class SetMaxHintWindow extends NodeTool.NodeToolCmd +{ + @Arguments(title = "max_hint_window", usage = "", description = "Value of maxhintwindow in ms", required = true) + private Integer maxHintWindow = null; + + @Override + public void execute(NodeProbe probe) + { + probe.setMaxHintWindow(maxHintWindow); + } +} diff --git a/src/java/org/apache/cassandra/management/api/SetSnapshotThrottle.java b/src/java/org/apache/cassandra/management/api/SetSnapshotThrottle.java new file mode 100644 index 000000000000..e96f0a37e171 --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/SetSnapshotThrottle.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.Arguments; +import io.airlift.airline.Command; +import org.apache.cassandra.tools.NodeProbe; +import org.apache.cassandra.tools.NodeTool.NodeToolCmd; + +@Command(name = "setsnapshotthrottle", description = "Set the snapshot_links_per_second cap for snapshot and clearsnapshot throttling") +public class SetSnapshotThrottle extends NodeToolCmd +{ + @Arguments(title = "setsnapshotthrottle", usage = "", description = "Value represents hardlinks per second ( snapshot_links_per_second ) , 0 to disable throttling", required = true) + private Long snapshotThrottle = null; + + @Override + public void execute(NodeProbe probe) + { + probe.setSnapshotLinksPerSecond(snapshotThrottle); + } +} diff --git a/src/java/org/apache/cassandra/management/api/SetStreamThroughput.java b/src/java/org/apache/cassandra/management/api/SetStreamThroughput.java new file mode 100644 index 000000000000..ee4b62ea0786 --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/SetStreamThroughput.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 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 = "setstreamthroughput", description = "Set throughput cap for streaming and entire SSTable streaming in the system, or 0 to disable throttling") +public class SetStreamThroughput extends NodeToolCmd +{ + @SuppressWarnings("UnusedDeclaration") + @Arguments(title = "stream_throughput", usage = "", description = "Value in megabits, 0 to disable throttling", required = true) + private int streamThroughput; + + @SuppressWarnings("UnusedDeclaration") + @Option(name = { "-e", "--entire-sstable-throughput" }, description = "Set entire SSTable streaming throughput in MiB/s") + private boolean setEntireSSTableThroughput; + + @SuppressWarnings("UnusedDeclaration") + @Option(name = { "-m", "--mib" }, description = "Set streaming throughput in MiB/s") + private boolean streamThroughputInMebibytes; + + @Override + public void execute(NodeProbe probe) + { + if (setEntireSSTableThroughput && streamThroughputInMebibytes) + throw new IllegalArgumentException("You cannot use -e and -m at the same time"); + + if (setEntireSSTableThroughput) + probe.setEntireSSTableStreamThroughput(streamThroughput); + else if (streamThroughputInMebibytes ) + probe.setStreamThroughputMiB(streamThroughput); + else + probe.setStreamThroughput(streamThroughput); + } +} diff --git a/src/java/org/apache/cassandra/management/api/SetTimeout.java b/src/java/org/apache/cassandra/management/api/SetTimeout.java new file mode 100644 index 000000000000..cce4abb55307 --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/SetTimeout.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.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 = "settimeout", description = "Set the specified timeout in ms, or 0 to disable timeout") +public class SetTimeout extends NodeToolCmd +{ + @Arguments(usage = " ", description = "Timeout type followed by value in ms " + + "(0 disables socket streaming timeout). Type should be one of (" + GetTimeout.TIMEOUT_TYPES + ")", + required = true) + private List args = new ArrayList<>(); + + @Override + public void execute(NodeProbe probe) + { + checkArgument(args.size() == 2, "Timeout type followed by value in ms (0 disables socket streaming timeout)." + + " Type should be one of (" + GetTimeout.TIMEOUT_TYPES + ")"); + + try + { + String type = args.get(0); + long timeout = Long.parseLong(args.get(1)); + probe.setTimeout(type, timeout); + } catch (Exception e) + { + throw new IllegalArgumentException(e.getMessage()); + } + } +} diff --git a/src/java/org/apache/cassandra/management/api/SetTraceProbability.java b/src/java/org/apache/cassandra/management/api/SetTraceProbability.java new file mode 100644 index 000000000000..c67d02291729 --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/SetTraceProbability.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 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 = "settraceprobability", description = "Sets the probability for tracing any given request to value. 0 disables, 1 enables for all requests, 0 is the default") +public class SetTraceProbability extends NodeToolCmd +{ + @Arguments(title = "trace_probability", usage = "", description = "Trace probability between 0 and 1 (ex: 0.2)", required = true) + private Double traceProbability = null; + + @Override + public void execute(NodeProbe probe) + { + checkArgument(traceProbability >= 0 && traceProbability <= 1, "Trace probability must be between 0 and 1"); + probe.setTraceProbability(traceProbability); + } +} diff --git a/src/java/org/apache/cassandra/management/api/Sjk.java b/src/java/org/apache/cassandra/management/api/Sjk.java new file mode 100644 index 000000000000..1445c6f2032d --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/Sjk.java @@ -0,0 +1,484 @@ +/* + * 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 java.lang.reflect.Field; +import java.net.URL; +import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import javax.management.MBeanServerConnection; + +import com.beust.jcommander.JCommander; +import com.beust.jcommander.ParameterDescription; +import com.beust.jcommander.Parameterized; +import io.airlift.airline.Arguments; +import io.airlift.airline.Command; +import org.apache.cassandra.io.util.File; +import org.apache.cassandra.tools.NodeProbe; +import org.apache.cassandra.tools.NodeTool.NodeToolCmd; +import org.apache.cassandra.tools.Output; +import org.gridkit.jvmtool.JmxConnectionInfo; +import org.gridkit.jvmtool.cli.CommandLauncher; + +@Command(name = "sjk", description = "Run commands of 'Swiss Java Knife'. Run 'nodetool sjk --help' for more information.") +public class Sjk extends NodeToolCmd +{ + @Arguments(description = "Arguments passed as is to 'Swiss Java Knife'.") + private List args; + + private final Wrapper wrapper = new Wrapper(); + + @Override + public void runInternal() + { + wrapper.prepare(args != null ? args.toArray(new String[0]) : new String[]{"help"}, output.out, output.err); + + if (!wrapper.requiresMbeanServerConn()) + { + // SJK command does not require an MBeanServerConnection, so just invoke it + wrapper.run(null, output); + } + else + { + // invoke common nodetool handling to establish MBeanServerConnection + super.runInternal(); + } + } + + public void sequenceRun(NodeProbe probe) + { + wrapper.prepare(args != null ? args.toArray(new String[0]) : new String[]{"help"}, probe.output().out, probe.output().err); + if (!wrapper.run(probe, probe.output())) + probe.failed(); + } + + protected void execute(NodeProbe probe) + { + if (!wrapper.run(probe, probe.output())) + probe.failed(); + } + + /** + * Adopted copy of {@link org.gridkit.jvmtool.SJK} from https://github.com/aragozin/jvm-tools. + */ + public static class Wrapper extends CommandLauncher + { + boolean suppressSystemExit; + + private final Map commands = new HashMap<>(); + + private JCommander parser; + + private Runnable cmd; + + public void suppressSystemExit() + { + suppressSystemExit = true; + super.suppressSystemExit(); + } + + public boolean start(String[] args) + { + throw new UnsupportedOperationException(); + } + + public void prepare(String[] args, PrintStream out, PrintStream err) + { + try + { + + parser = new JCommander(this); + + addCommands(); + + fixCommands(); + + try + { + parser.parse(args); + } + catch (Exception e) + { + failAndPrintUsage(e.toString()); + } + + if (isHelp()) + { + String cmd = parser.getParsedCommand(); + if (cmd == null) + { + parser.usage(); + } + else + { + parser.usage(cmd); + } + } + else if (isListCommands()) + { + for (String cmd : commands.keySet()) + { + out.println(String.format("%8s - %s", cmd, parser.getCommandDescription(cmd))); + } + } + else + { + + cmd = commands.get(parser.getParsedCommand()); + + if (cmd == null) + { + failAndPrintUsage(); + } + } + } + catch (CommandAbortedError error) + { + for (String m : error.messages) + { + err.println(m); + } + if (isVerbose() && error.getCause() != null) + { + error.getCause().printStackTrace(err); + } + if (error.printUsage && parser != null) + { + printUsage(parser, out, parser.getParsedCommand()); + } + } + catch (Throwable e) + { + e.printStackTrace(err); + } + } + + void printUsage(JCommander parser, PrintStream out, String optionalCommand) + { + StringBuilder sb = new StringBuilder(); + if (optionalCommand != null) + parser.usage(sb, optionalCommand); + else + parser.usage(sb); + out.println(sb.toString()); + } + + public boolean run(final NodeProbe probe, final Output output) + { + PrintStream out = output.out; + PrintStream err = output.err; + try + { + setJmxConnInfo(probe); + + if (cmd != null) + cmd.run(); + + return true; + } + catch (CommandAbortedError error) + { + for (String m : error.messages) + { + err.println(m); + } + if (isVerbose() && error.getCause() != null) + { + error.getCause().printStackTrace(err); + } + if (error.printUsage && parser != null) + { + printUsage(parser, out, parser.getParsedCommand()); + } + return true; + } + catch (Throwable e) + { + e.printStackTrace(err); + } + + // abnormal termination + return false; + } + + private void setJmxConnInfo(final NodeProbe probe) throws IllegalAccessException + { + Field f = jmxConnectionInfoField(cmd); + if (f != null) + { + f.setAccessible(true); + f.set(cmd, new JmxConnectionInfo(this) + { + public MBeanServerConnection getMServer() + { + return probe.getMbeanServerConn(); + } + }); + } + f = pidField(cmd); + if (f != null) + { + long pid = probe.getPid(); + + f.setAccessible(true); + if (f.getType() == int.class) + f.setInt(cmd, (int) pid); + if (f.getType() == long.class) + f.setLong(cmd, pid); + if (f.getType() == String.class) + f.set(cmd, Long.toString(pid)); + } + } + + private boolean isHelp() + { + try + { + Field f = CommandLauncher.class.getDeclaredField("help"); + f.setAccessible(true); + return f.getBoolean(this); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + private boolean isListCommands() + { + try + { + Field f = CommandLauncher.class.getDeclaredField("listCommands"); + f.setAccessible(true); + return f.getBoolean(this); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + protected List getCommandPackages() + { + return Collections.singletonList("org.gridkit.jvmtool.cmd"); + } + + private void addCommands() throws InstantiationException, IllegalAccessException + { + for (String pack : getCommandPackages()) + { + for (Class c : findClasses(pack)) + { + if (CmdRef.class.isAssignableFrom(c)) + { + CmdRef cmd = (CmdRef) c.newInstance(); + String cmdName = cmd.getCommandName(); + Runnable cmdTask = cmd.newCommand(this); + if (commands.containsKey(cmdName)) + { + fail("Ambiguous implementation for '" + cmdName + '\''); + } + commands.put(cmdName, cmdTask); + parser.addCommand(cmdName, cmdTask); + } + } + } + } + + private void fixCommands() throws Exception + { + List fields = Arrays.asList(JCommander.class.getDeclaredField("m_fields"), + JCommander.class.getDeclaredField("m_requiredFields")); + for (Field f : fields) + f.setAccessible(true); + + for (JCommander cmdr : parser.getCommands().values()) + { + for (Field field : fields) { + Map fieldsMap = (Map) field.get(cmdr); + for (Iterator> iPar = fieldsMap.entrySet().iterator(); iPar.hasNext(); ) + { + Map.Entry par = iPar.next(); + switch (par.getKey().getName()) + { + // JmxConnectionInfo fields + case "pid": + case "sockAddr": + case "user": + case "password": + // + case "verbose": + case "help": + case "listCommands": + iPar.remove(); + break; + } + } + } + } + } + + boolean requiresMbeanServerConn() + { + return jmxConnectionInfoField(cmd) != null || pidField(cmd) != null; + } + + private static Field jmxConnectionInfoField(Runnable cmd) + { + if (cmd == null) + return null; + + for (Field f : cmd.getClass().getDeclaredFields()) + { + if (f.getType() == JmxConnectionInfo.class) + { + return f; + } + } + return null; + } + + private static Field pidField(Runnable cmd) + { + if (cmd == null) + return null; + + for (Field f : cmd.getClass().getDeclaredFields()) + { + if ("pid".equals(f.getName()) && + (f.getType() == int.class || f.getType() == long.class || f.getType() == String.class)) + { + return f; + } + } + return null; + } + + private static List> findClasses(String packageName) + { + // TODO this will probably fail with JPMS/Jigsaw + + List> result = new ArrayList<>(); + try + { + String path = packageName.replace('.', '/'); + for (String f : findFiles(path)) + { + if (f.endsWith(".class") && f.indexOf('$') < 0) + { + f = f.substring(0, f.length() - ".class".length()); + f = f.replace('/', '.'); + result.add(Class.forName(f)); + } + } + return result; + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + static List findFiles(String path) throws IOException + { + List result = new ArrayList<>(); + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + Enumeration en = cl.getResources(path); + while (en.hasMoreElements()) + { + URL u = en.nextElement(); + listFiles(result, u, path); + } + return result; + } + + static void listFiles(List results, URL packageURL, String path) throws IOException + { + if (packageURL.getProtocol().equals("jar")) + { + String jarFileName; + Enumeration jarEntries; + String entryName; + + // build jar file name, then loop through zipped entries + jarFileName = URLDecoder.decode(packageURL.getFile(), "UTF-8"); + jarFileName = jarFileName.substring(5, jarFileName.indexOf('!')); + try (JarFile jf = new JarFile(jarFileName)) + { + jarEntries = jf.entries(); + while (jarEntries.hasMoreElements()) + { + entryName = jarEntries.nextElement().getName(); + if (entryName.startsWith(path)) + { + results.add(entryName); + } + } + } + } + else + { + // loop through files in classpath + File dir = new File(packageURL.getFile()); + String cp = dir.canonicalPath(); + File root = dir; + while (true) + { + if (cp.equals(new File(root, path).canonicalPath())) + { + break; + } + root = root.parent(); + } + listFiles(results, root, dir); + } + } + + static void listFiles(List names, File root, File dir) + { + String rootPath = root.absolutePath(); + if (dir.exists() && dir.isDirectory()) + { + for (File file : dir.tryList()) + { + if (file.isDirectory()) + { + listFiles(names, root, file); + } + else + { + String name = file.absolutePath().substring(rootPath.length() + 1); + name = name.replace('\\', '/'); + names.add(name); + } + } + } + } + } +} diff --git a/src/java/org/apache/cassandra/management/api/Snapshot.java b/src/java/org/apache/cassandra/management/api/Snapshot.java new file mode 100644 index 000000000000..8ff4d5f6fe68 --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/Snapshot.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.api; + +import java.io.IOException; +import java.io.PrintStream; +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.io.util.File; +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.cassandra.utils.Clock.Global.currentTimeMillis; +import static org.apache.commons.lang3.StringUtils.join; + +@Command(name = "snapshot", description = "Take a snapshot of specified keyspaces or a snapshot of the specified table") +public class Snapshot extends NodeToolCmd +{ + @Arguments(usage = "[]", description = "List of keyspaces. By default, all keyspaces") + private List keyspaces = new ArrayList<>(); + + @Option(title = "table", name = {"-cf", "--column-family", "--table"}, description = "The table name (you must specify one and only one keyspace for using this option)") + private String table = null; + + @Option(title = "tag", name = {"-t", "--tag"}, description = "The name of the snapshot") + private String snapshotName = Long.toString(currentTimeMillis()); + + @Option(title = "ktlist", name = { "-kt", "--kt-list", "-kc", "--kc.list" }, description = "The list of Keyspace.table to take snapshot.(you must not specify only keyspace)") + private String ktList = null; + + @Option(title = "skip-flush", name = {"-sf", "--skip-flush"}, description = "Do not flush memtables before snapshotting (snapshot will not contain unflushed data)") + private boolean skipFlush = false; + + @Option(title = "ttl", name = {"--ttl"}, description = "Specify a TTL of created snapshot") + private String ttl = null; + + @Override + public void execute(NodeProbe probe) + { + PrintStream out = probe.output().out; + try + { + StringBuilder sb = new StringBuilder(); + + sb.append("Requested creating snapshot(s) for "); + + Map options = new HashMap(); + options.put("skipFlush", Boolean.toString(skipFlush)); + if (null != ttl) { + DurationSpec.LongNanosecondsBound d = new DurationSpec.LongNanosecondsBound(ttl); + options.put("ttl", d.toString()); + } + + if (!snapshotName.isEmpty() && snapshotName.contains(File.pathSeparator())) + { + throw new IOException("Snapshot name cannot contain " + File.pathSeparator()); + } + // Create a separate path for kclist to avoid breaking of already existing scripts + if (null != ktList && !ktList.isEmpty()) + { + ktList = ktList.replace(" ", ""); + if (keyspaces.isEmpty() && null == table) + sb.append("[").append(ktList).append("]"); + else + { + throw new IOException( + "When specifying the Keyspace table list (using -kt,--kt-list,-kc,--kc.list), you must not also specify keyspaces to snapshot"); + } + if (!snapshotName.isEmpty()) + sb.append(" with snapshot name [").append(snapshotName).append("]"); + sb.append(" and options ").append(options.toString()); + out.println(sb.toString()); + probe.takeMultipleTableSnapshot(snapshotName, options, ktList.split(",")); + out.println("Snapshot directory: " + snapshotName); + } + else + { + if (keyspaces.isEmpty()) + sb.append("[all keyspaces]"); + else + sb.append("[").append(join(keyspaces, ", ")).append("]"); + + if (!snapshotName.isEmpty()) + sb.append(" with snapshot name [").append(snapshotName).append("]"); + sb.append(" and options ").append(options.toString()); + out.println(sb.toString()); + + probe.takeSnapshot(snapshotName, table, options, toArray(keyspaces, String.class)); + out.println("Snapshot directory: " + snapshotName); + } + } + catch (IOException e) + { + throw new RuntimeException("Error during taking a snapshot", e); + } + } +} diff --git a/src/java/org/apache/cassandra/management/api/Status.java b/src/java/org/apache/cassandra/management/api/Status.java new file mode 100644 index 000000000000..3a843a217ab4 --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/Status.java @@ -0,0 +1,192 @@ +/* + * 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.net.UnknownHostException; +import java.text.DecimalFormat; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.SortedMap; + +import com.google.common.collect.ArrayListMultimap; + +import io.airlift.airline.Arguments; +import io.airlift.airline.Command; +import io.airlift.airline.Option; +import org.apache.cassandra.locator.EndpointSnitchInfoMBean; +import org.apache.cassandra.tools.NodeProbe; +import org.apache.cassandra.tools.NodeTool; +import org.apache.cassandra.tools.NodeTool.NodeToolCmd; +import org.apache.cassandra.tools.nodetool.formatter.TableBuilder; + +@SuppressWarnings("UseOfSystemOutOrSystemErr") +@Command(name = "status", description = "Print cluster information (state, load, IDs, ...)") +public class Status extends NodeToolCmd +{ + @Arguments(usage = "[]", description = "The keyspace name") + private String keyspace = null; + + @Option(title = "resolve_ip", name = {"-r", "--resolve-ip"}, description = "Show node domain names instead of IPs") + private boolean resolveIp = false; + + private boolean isTokenPerNode = true; + private Collection joiningNodes, leavingNodes, movingNodes, liveNodes, unreachableNodes; + private Map loadMap, hostIDMap; + private EndpointSnitchInfoMBean epSnitchInfo; + + @Override + public void execute(NodeProbe probe) + { + PrintStream out = probe.output().out; + joiningNodes = probe.getJoiningNodes(true); + leavingNodes = probe.getLeavingNodes(true); + movingNodes = probe.getMovingNodes(true); + loadMap = probe.getLoadMap(true); + Map tokensToEndpoints = probe.getTokenToEndpointMap(true); + liveNodes = probe.getLiveNodes(true); + unreachableNodes = probe.getUnreachableNodes(true); + hostIDMap = probe.getHostIdMap(true); + epSnitchInfo = probe.getEndpointSnitchInfoProxy(); + + StringBuilder errors = new StringBuilder(); + TableBuilder.SharedTable sharedTable = new TableBuilder.SharedTable(" "); + + Map ownerships = null; + boolean hasEffectiveOwns = false; + try + { + ownerships = probe.effectiveOwnershipWithPort(keyspace); + hasEffectiveOwns = true; + } + catch (IllegalStateException e) + { + try + { + ownerships = probe.getOwnershipWithPort(); + errors.append("Note: ").append(e.getMessage()).append("%n"); + } + catch (Exception ex) + { + out.printf("%nError: %s%n", ex.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); + + // More tokens than nodes (aka vnodes)? + if (dcs.size() < tokensToEndpoints.size()) + isTokenPerNode = false; + + // Datacenters + for (Map.Entry dc : dcs.entrySet()) + { + TableBuilder tableBuilder = sharedTable.next(); + addNodesHeader(hasEffectiveOwns, tableBuilder); + + ArrayListMultimap hostToTokens = ArrayListMultimap.create(); + for (HostStatWithPort stat : dc.getValue()) + hostToTokens.put(stat.endpointWithPort.getHostAddressAndPort(), stat); + + for (String endpoint : hostToTokens.keySet()) + { + Float owns = ownerships.get(endpoint); + List tokens = hostToTokens.get(endpoint); + addNode(endpoint, owns, tokens.get(0), tokens.size(), hasEffectiveOwns, tableBuilder); + } + } + + Iterator results = sharedTable.complete().iterator(); + boolean first = true; + for (Map.Entry dc : dcs.entrySet()) + { + if (!first) { + out.println(); + } + first = false; + String dcHeader = String.format("Datacenter: %s%n", dc.getKey()); + out.print(dcHeader); + for (int i = 0; i < (dcHeader.length() - 1); i++) out.print('='); + out.println(); + + // Legend + out.println("Status=Up/Down"); + out.println("|/ State=Normal/Leaving/Joining/Moving"); + TableBuilder dcTable = results.next(); + dcTable.printTo(out); + } + + out.printf("%n" + errors); + } + + private void addNodesHeader(boolean hasEffectiveOwns, TableBuilder tableBuilder) + { + String owns = hasEffectiveOwns ? "Owns (effective)" : "Owns"; + + if (isTokenPerNode) + tableBuilder.add("--", "Address", "Load", owns, "Host ID", "Token", "Rack"); + else + tableBuilder.add("--", "Address", "Load", "Tokens", owns, "Host ID", "Rack"); + } + + private void addNode(String endpoint, Float owns, HostStatWithPort hostStat, int size, boolean hasEffectiveOwns, + TableBuilder tableBuilder) + { + String status, state, load, strOwns, hostID, rack, epDns; + if (liveNodes.contains(endpoint)) status = "U"; + else if (unreachableNodes.contains(endpoint)) status = "D"; + else status = "?"; + if (joiningNodes.contains(endpoint)) state = "J"; + else if (leavingNodes.contains(endpoint)) state = "L"; + else if (movingNodes.contains(endpoint)) state = "M"; + else state = "N"; + + String statusAndState = status.concat(state); + load = loadMap.getOrDefault(endpoint, "?"); + strOwns = owns != null && hasEffectiveOwns ? new DecimalFormat("##0.0%").format(owns) : "?"; + hostID = hostIDMap.get(endpoint); + + try + { + rack = epSnitchInfo.getRack(endpoint); + } + catch (UnknownHostException e) + { + throw new RuntimeException(e); + } + + epDns = hostStat.ipOrDns(printPort); + if (isTokenPerNode) + { + tableBuilder.add(statusAndState, epDns, load, strOwns, hostID, hostStat.token, rack); + } + else + { + tableBuilder.add(statusAndState, epDns, load, String.valueOf(size), strOwns, hostID, rack); + } + } + +} diff --git a/src/java/org/apache/cassandra/management/api/StatusAutoCompaction.java b/src/java/org/apache/cassandra/management/api/StatusAutoCompaction.java new file mode 100644 index 000000000000..78702c986af5 --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/StatusAutoCompaction.java @@ -0,0 +1,76 @@ +/* + * 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 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.NodeToolCmd; +import org.apache.cassandra.tools.nodetool.formatter.TableBuilder; + +@Command(name = "statusautocompaction", description = "status of autocompaction of the given keyspace and table") +public class StatusAutoCompaction extends NodeToolCmd +{ + @Arguments(usage = "[ ...]", description = "The keyspace followed by one or many tables") + private List args = new ArrayList<>(); + + @Option(title = "show_all", name = { "-a", "--all" }, description = "Show auto compaction status for each keyspace/table") + private boolean showAll = false; + + @Override + public void execute(NodeProbe probe) + { + List keyspaces = parseOptionalKeyspace(args, probe); + String[] tableNames = parseOptionalTables(args); + + boolean allDisabled = true; + boolean allEnabled = true; + TableBuilder table = new TableBuilder(); + table.add("Keyspace", "Table", "Status"); + try + { + for (String keyspace : keyspaces) + { + Map statuses = probe.getAutoCompactionDisabled(keyspace, tableNames); + for (Map.Entry status : statuses.entrySet()) + { + String tableName = status.getKey(); + boolean disabled = status.getValue(); + allDisabled &= disabled; + allEnabled &= !disabled; + table.add(keyspace, tableName, !disabled ? "running" : "not running"); + } + } + if (showAll) + table.printTo(probe.output().out); + else + probe.output().out.println(allEnabled ? "running" : + allDisabled ? "not running" : "partially running"); + } + catch (IOException e) + { + throw new RuntimeException("Error occurred during status-auto-compaction", e); + } + } +} diff --git a/src/java/org/apache/cassandra/management/api/StatusBackup.java b/src/java/org/apache/cassandra/management/api/StatusBackup.java new file mode 100644 index 000000000000..f0a8c38f223d --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/StatusBackup.java @@ -0,0 +1,35 @@ +/* + * 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 = "statusbackup", description = "Status of incremental backup") +public class StatusBackup extends NodeToolCmd +{ + @Override + public void execute(NodeProbe probe) + { + probe.output().out.println( + probe.isIncrementalBackupsEnabled() + ? "running" + : "not running"); + } +} diff --git a/src/java/org/apache/cassandra/management/api/StatusBinary.java b/src/java/org/apache/cassandra/management/api/StatusBinary.java new file mode 100644 index 000000000000..b8dd6930e20d --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/StatusBinary.java @@ -0,0 +1,35 @@ +/* + * 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 = "statusbinary", description = "Status of native transport (binary protocol)") +public class StatusBinary extends NodeToolCmd +{ + @Override + public void execute(NodeProbe probe) + { + probe.output().out.println( + probe.isNativeTransportRunning() + ? "running" + : "not running"); + } +} diff --git a/src/java/org/apache/cassandra/management/api/StatusGossip.java b/src/java/org/apache/cassandra/management/api/StatusGossip.java new file mode 100644 index 000000000000..c92dd3dd4a98 --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/StatusGossip.java @@ -0,0 +1,35 @@ +/* + * 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 = "statusgossip", description = "Status of gossip") +public class StatusGossip extends NodeToolCmd +{ + @Override + public void execute(NodeProbe probe) + { + probe.output().out.println( + probe.isGossipRunning() + ? "running" + : "not running"); + } +} diff --git a/src/java/org/apache/cassandra/management/api/StatusHandoff.java b/src/java/org/apache/cassandra/management/api/StatusHandoff.java new file mode 100644 index 000000000000..cc54ae77e440 --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/StatusHandoff.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 io.airlift.airline.Command; +import org.apache.cassandra.tools.NodeProbe; +import org.apache.cassandra.tools.NodeTool.NodeToolCmd; + +@Command(name = "statushandoff", description = "Status of storing future hints on the current node") +public class StatusHandoff extends NodeToolCmd +{ + @Override + public void execute(NodeProbe probe) + { + probe.output().out.println(String.format("Hinted handoff is %s", + probe.isHandoffEnabled() + ? "running" + : "not running")); + + for (String dc : probe.getHintedHandoffDisabledDCs()) + probe.output().out.println(String.format("Data center %s is disabled", dc)); + } +} diff --git a/src/java/org/apache/cassandra/management/api/Stop.java b/src/java/org/apache/cassandra/management/api/Stop.java new file mode 100644 index 000000000000..04f9cfb63f85 --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/Stop.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 io.airlift.airline.Arguments; +import io.airlift.airline.Command; +import io.airlift.airline.Option; +import org.apache.cassandra.db.compaction.OperationType; +import org.apache.cassandra.tools.NodeProbe; +import org.apache.cassandra.tools.NodeTool.NodeToolCmd; + +@Command(name = "stop", description = "Stop compaction") +public class Stop extends NodeToolCmd +{ + @Arguments(title = "compaction_type", + usage = "", + description = "Supported types are COMPACTION, VALIDATION, CLEANUP, SCRUB, UPGRADE_SSTABLES, INDEX_BUILD, TOMBSTONE_COMPACTION, ANTICOMPACTION, VERIFY, VIEW_BUILD, INDEX_SUMMARY, RELOCATE, GARBAGE_COLLECT", + required = false) + private OperationType compactionType = OperationType.UNKNOWN; + + @Option(title = "compactionId", + name = {"-id", "--compaction-id"}, + description = "Use -id to stop a compaction by the specified id. Ids can be found in the transaction log files whose name starts with compaction_, located in the table transactions folder.", + required = false) + private String compactionId = ""; + + @Override + public void execute(NodeProbe probe) + { + if (!compactionId.isEmpty()) + probe.stopById(compactionId); + else + probe.stop(compactionType.name()); + } +} diff --git a/src/java/org/apache/cassandra/management/api/StopDaemon.java b/src/java/org/apache/cassandra/management/api/StopDaemon.java new file mode 100644 index 000000000000..587da0c8fbc1 --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/StopDaemon.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 io.airlift.airline.Command; +import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.tools.NodeProbe; +import org.apache.cassandra.tools.NodeTool.NodeToolCmd; +import org.apache.cassandra.utils.JVMStabilityInspector; + +@Command(name = "stopdaemon", description = "Stop cassandra daemon") +public class StopDaemon extends NodeToolCmd +{ + @Override + public void execute(NodeProbe probe) + { + try + { + DatabaseDescriptor.toolInitialization(); + probe.stopCassandraDaemon(); + } catch (Exception e) + { + JVMStabilityInspector.inspectThrowable(e); + // ignored + } + } +} diff --git a/src/java/org/apache/cassandra/management/api/TableHistograms.java b/src/java/org/apache/cassandra/management/api/TableHistograms.java new file mode 100644 index 000000000000..752dbf1f3af5 --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/TableHistograms.java @@ -0,0 +1,184 @@ +/* + * 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.Iterator; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import org.apache.commons.lang3.ArrayUtils; + +import io.airlift.airline.Arguments; +import io.airlift.airline.Command; +import org.apache.cassandra.db.ColumnFamilyStoreMBean; +import org.apache.cassandra.metrics.CassandraMetricsRegistry; +import org.apache.cassandra.tools.NodeProbe; +import org.apache.cassandra.tools.NodeTool.NodeToolCmd; +import org.apache.cassandra.utils.EstimatedHistogram; +import org.apache.cassandra.utils.Pair; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.lang.String.format; + +@Command(name = "tablehistograms", description = "Print statistic histograms for a given table") +public class TableHistograms extends NodeToolCmd +{ + @Arguments(usage = "[
| ]", description = "The keyspace and table name") + private List args = new ArrayList<>(); + + @Override + public void execute(NodeProbe probe) + { + PrintStream out = probe.output().out; + Multimap tablesList = HashMultimap.create(); + + // a > mapping for verification or as reference if none provided + Multimap allTables = HashMultimap.create(); + Iterator> tableMBeans = probe.getColumnFamilyStoreMBeanProxies(); + while (tableMBeans.hasNext()) + { + Map.Entry entry = tableMBeans.next(); + allTables.put(entry.getKey(), entry.getValue().getTableName()); + } + + if (args.size() == 2 && args.stream().noneMatch(arg -> arg.contains("."))) + { + tablesList.put(args.get(0), args.get(1)); + } + else if (args.size() == 1) + { + Pair ksTbPair = parseTheKsTbPair(args.get(0)); + tablesList.put(ksTbPair.left, ksTbPair.right); + } + else if (args.size() == 0) + { + // use all tables + tablesList = allTables; + } + else + { + throw new IllegalArgumentException("tablehistograms requires
or format argument."); + } + + // verify that all tables to list exist + for (String keyspace : tablesList.keys()) + { + for (String table : tablesList.get(keyspace)) + { + if (!allTables.containsEntry(keyspace, table)) + throw new IllegalArgumentException("Unknown table " + keyspace + '.' + table); + } + } + + for (String keyspace : tablesList.keys().elementSet()) + { + for (String table : tablesList.get(keyspace)) + { + // calculate percentile of row size and column count + long[] estimatedPartitionSize = (long[]) probe.getColumnFamilyMetric(keyspace, table, "EstimatedPartitionSizeHistogram"); + long[] estimatedColumnCount = (long[]) probe.getColumnFamilyMetric(keyspace, table, "EstimatedColumnCountHistogram"); + + // build arrays to store percentile values + double[] estimatedRowSizePercentiles = new double[7]; + double[] estimatedColumnCountPercentiles = new double[7]; + double[] offsetPercentiles = new double[]{0.5, 0.75, 0.95, 0.98, 0.99}; + + if (ArrayUtils.isEmpty(estimatedPartitionSize) || ArrayUtils.isEmpty(estimatedColumnCount)) + { + out.println("No SSTables exists, unable to calculate 'Partition Size' and 'Cell Count' percentiles"); + + for (int i = 0; i < 7; i++) + { + estimatedRowSizePercentiles[i] = Double.NaN; + estimatedColumnCountPercentiles[i] = Double.NaN; + } + } + else + { + EstimatedHistogram partitionSizeHist = new EstimatedHistogram(estimatedPartitionSize); + EstimatedHistogram columnCountHist = new EstimatedHistogram(estimatedColumnCount); + + if (partitionSizeHist.isOverflowed()) + { + out.println(String.format("Row sizes are larger than %s, unable to calculate percentiles", partitionSizeHist.getLargestBucketOffset())); + for (int i = 0; i < offsetPercentiles.length; i++) + estimatedRowSizePercentiles[i] = Double.NaN; + } + else + { + for (int i = 0; i < offsetPercentiles.length; i++) + estimatedRowSizePercentiles[i] = partitionSizeHist.percentile(offsetPercentiles[i]); + } + + if (columnCountHist.isOverflowed()) + { + out.println(String.format("Column counts are larger than %s, unable to calculate percentiles", columnCountHist.getLargestBucketOffset())); + for (int i = 0; i < estimatedColumnCountPercentiles.length; i++) + estimatedColumnCountPercentiles[i] = Double.NaN; + } + else + { + for (int i = 0; i < offsetPercentiles.length; i++) + estimatedColumnCountPercentiles[i] = columnCountHist.percentile(offsetPercentiles[i]); + } + + // min value + estimatedRowSizePercentiles[5] = partitionSizeHist.min(); + estimatedColumnCountPercentiles[5] = columnCountHist.min(); + // max value + estimatedRowSizePercentiles[6] = partitionSizeHist.max(); + estimatedColumnCountPercentiles[6] = columnCountHist.max(); + } + + String[] percentiles = new String[]{"50%", "75%", "95%", "98%", "99%", "Min", "Max"}; + Double[] readLatency = probe.metricPercentilesAsArray((CassandraMetricsRegistry.JmxTimerMBean) probe.getColumnFamilyMetric(keyspace, table, "ReadLatency")); + Double[] writeLatency = probe.metricPercentilesAsArray((CassandraMetricsRegistry.JmxTimerMBean) probe.getColumnFamilyMetric(keyspace, table, "WriteLatency")); + Double[] sstablesPerRead = probe.metricPercentilesAsArray((CassandraMetricsRegistry.JmxHistogramMBean) probe.getColumnFamilyMetric(keyspace, table, "SSTablesPerReadHistogram")); + + out.println(format("%s/%s histograms", keyspace, table)); + out.println(format("%-10s%18s%18s%18s%18s%18s", + "Percentile", "Read Latency", "Write Latency", "SSTables", "Partition Size", "Cell Count")); + out.println(format("%-10s%18s%18s%18s%18s%18s", + "", "(micros)", "(micros)", "", "(bytes)", "")); + + for (int i = 0; i < percentiles.length; i++) + { + out.println(format("%-10s%18.2f%18.2f%18.2f%18.0f%18.0f", + percentiles[i], + readLatency[i], + writeLatency[i], + sstablesPerRead[i], + estimatedRowSizePercentiles[i], + estimatedColumnCountPercentiles[i])); + } + out.println(); + } + } + } + + private Pair parseTheKsTbPair(String ksAndTb) + { + String[] input = args.get(0).split("\\."); + checkArgument(input.length == 2, "tablehistograms requires keyspace and table name arguments"); + return Pair.create(input[0], input[1]); + } +} diff --git a/src/java/org/apache/cassandra/management/api/TableStats.java b/src/java/org/apache/cassandra/management/api/TableStats.java new file mode 100644 index 000000000000..334b9cf7fbc7 --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/TableStats.java @@ -0,0 +1,112 @@ +/* + * 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.Arrays; +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.StatsHolder; +import org.apache.cassandra.tools.nodetool.stats.StatsPrinter; +import org.apache.cassandra.tools.nodetool.stats.StatsTableComparator; +import org.apache.cassandra.tools.nodetool.stats.TableStatsHolder; +import org.apache.cassandra.tools.nodetool.stats.TableStatsPrinter; + +@Command(name = "tablestats", description = "Print statistics on tables") +public class TableStats extends NodeToolCmd +{ + @Arguments(usage = "[...]", description = "List of tables (or keyspace) names") + private List tableNames = new ArrayList<>(); + + @Option(name = "-i", description = "Ignore the list of tables and display the remaining tables") + private boolean ignore = false; + + @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 = "format", + name = {"-F", "--format"}, + description = "Output format (json, yaml)") + private String outputFormat = ""; + + @Option(title = "sort_key", + name = {"-s", "--sort"}, + description = "Sort tables by specified sort key (average_live_cells_per_slice_last_five_minutes, " + + "average_tombstones_per_slice_last_five_minutes, bloom_filter_false_positives, " + + "bloom_filter_false_ratio, bloom_filter_off_heap_memory_used, bloom_filter_space_used, " + + "compacted_partition_maximum_bytes, compacted_partition_mean_bytes, " + + "compacted_partition_minimum_bytes, compression_metadata_off_heap_memory_used, " + + "full_name, index_summary_off_heap_memory_used, local_read_count, " + + "local_read_latency_ms, local_write_latency_ms, " + + "maximum_live_cells_per_slice_last_five_minutes, " + + "maximum_tombstones_per_slice_last_five_minutes, memtable_cell_count, memtable_data_size, " + + "memtable_off_heap_memory_used, memtable_switch_count, number_of_partitions_estimate, " + + "off_heap_memory_used_total, pending_flushes, percent_repaired, read_latency, reads, " + + "space_used_by_snapshots_total, space_used_live, space_used_total, " + + "sstable_compression_ratio, sstable_count, table_name, write_latency, writes, " + + "max_sstable_size, local_read_write_ratio, twcs_max_duration)") + private String sortKey = ""; + + @Option(title = "top", + name = {"-t", "--top"}, + description = "Show only the top K tables for the sort key (specify the number K of tables to be shown") + private int top = 0; + + @Option(title = "sstable_location_check", + name = {"-l", "--sstable-location-check"}, + description = "Check whether or not the SSTables are in the correct location.") + private boolean locationCheck = false; + + @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."); + } + + if (!sortKey.isEmpty() && !Arrays.asList(StatsTableComparator.supportedSortKeys).contains(sortKey)) + { + throw new IllegalArgumentException(String.format("argument for sort must be one of: %s", + String.join(", ", StatsTableComparator.supportedSortKeys))); + } + + if (top > 0 && sortKey.isEmpty()) + { + throw new IllegalArgumentException("cannot filter top K tables without specifying a sort key."); + } + + if (top < 0) + { + throw new IllegalArgumentException("argument for top must be a positive integer."); + } + + StatsHolder holder = new TableStatsHolder(probe, humanReadable, ignore, tableNames, sortKey, top, locationCheck); + // print out the keyspace and table statistics + StatsPrinter printer = TableStatsPrinter.from(outputFormat, !sortKey.isEmpty()); + printer.print(holder, probe.output().out); + } + +} diff --git a/src/java/org/apache/cassandra/management/api/TopPartitions.java b/src/java/org/apache/cassandra/management/api/TopPartitions.java new file mode 100644 index 000000000000..972de15c591b --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/TopPartitions.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.api; + +import io.airlift.airline.Command; + +@Command(name = "toppartitions", description = "Sample and print the most active partitions") +/** @deprecated See CASSANDRA-14436 */ +@Deprecated(since = "4.0") +public class TopPartitions extends ProfileLoad +{ +} diff --git a/src/java/org/apache/cassandra/management/api/TpStats.java b/src/java/org/apache/cassandra/management/api/TpStats.java new file mode 100644 index 000000000000..416aa6e5aaad --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/TpStats.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 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.StatsHolder; +import org.apache.cassandra.tools.nodetool.stats.StatsPrinter; +import org.apache.cassandra.tools.nodetool.stats.TpStatsHolder; +import org.apache.cassandra.tools.nodetool.stats.TpStatsPrinter; + + +@Command(name = "tpstats", description = "Print usage statistics of thread pools") +public class TpStats extends NodeToolCmd +{ + @Option(title = "format", + name = {"-F", "--format"}, + description = "Output format (json, yaml)") + private String outputFormat = ""; + + @Option(title = "verbose", + name = {"-v", "--verbose"}, + description = "Display detailed metrics about thread pool's sizes") + private boolean verbose = false; + + @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 TpStatsHolder(probe); + StatsPrinter printer = TpStatsPrinter.from(outputFormat); + printer.print(data, probe.output().out, verbose); + } +} diff --git a/src/java/org/apache/cassandra/management/api/TruncateHints.java b/src/java/org/apache/cassandra/management/api/TruncateHints.java new file mode 100644 index 000000000000..b26822751c81 --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/TruncateHints.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 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 = "truncatehints", description = "Truncate all hints on the local node, or truncate hints for the endpoint(s) specified.") +public class TruncateHints extends NodeToolCmd +{ + @Arguments(usage = "[endpoint ... ]", description = "Endpoint address(es) to delete hints for, either ip address (\"127.0.0.1\") or hostname") + private String endpoint = EMPTY; + + @Override + public void execute(NodeProbe probe) + { + if (endpoint.isEmpty()) + probe.truncateHints(); + else + probe.truncateHints(endpoint); + } +} diff --git a/src/java/org/apache/cassandra/management/api/UpdateCIDRGroup.java b/src/java/org/apache/cassandra/management/api/UpdateCIDRGroup.java new file mode 100644 index 000000000000..5f8fa29a371d --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/UpdateCIDRGroup.java @@ -0,0 +1,57 @@ +/* + * 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 insert/update a CIDR group and associated mapping in the table {@link AuthKeyspace#CIDR_GROUPS} + */ +@Command(name = "updatecidrgroup", description = "Insert/Update a cidr group") +public class UpdateCIDRGroup extends NodeToolCmd +{ + @Arguments(usage = "[ ...]", description = "Requires a cidr group name, followed by one or more CIDRs separated by space") + private List args = new ArrayList<>(); + + @Override + public void execute(NodeProbe probe) + { + checkArgument(args.size() > 1, "updatecidrgroup command requires a cidr group name and atleast one CIDR"); + + String cidrGroupName = args.get(0); + + try + { + probe.updateCidrGroup(cidrGroupName, new ArrayList<>(args.subList(1, args.size()))); + } + catch (IllegalArgumentException e) + { + // IllegalArgumentException message going to stdout, so throw different type of exception to land in stderr + throw new RuntimeException(e); + } + } +} diff --git a/src/java/org/apache/cassandra/management/api/UpgradeSSTable.java b/src/java/org/apache/cassandra/management/api/UpgradeSSTable.java new file mode 100644 index 000000000000..9297b49cd585 --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/UpgradeSSTable.java @@ -0,0 +1,83 @@ +/* + * 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.exception.ExceptionUtils; + +import io.airlift.airline.Arguments; +import io.airlift.airline.Command; +import io.airlift.airline.Option; +import org.apache.cassandra.db.compaction.CompactionInterruptedException; +import org.apache.cassandra.tools.NodeProbe; +import org.apache.cassandra.tools.NodeTool.NodeToolCmd; + +@Command(name = "upgradesstables", description = "Rewrite sstables (for the requested tables) that are not on the current version (thus upgrading them to said current version)") +public class UpgradeSSTable extends NodeToolCmd +{ + @Arguments(usage = "[ ...]", description = "The keyspace followed by one or many tables") + private List args = new ArrayList<>(); + + @Option(title = "include_all", + name = {"-a", "--include-all-sstables"}, + description = "Use -a to include all sstables, even those already on the current version") + private boolean includeAll = false; + + @Option(title = "max_timestamp", + name = {"-t", "--max-timestamp"}, + description = "Use -t to compact only SSTables that have local creation time _older_ than the given timestamp") + private long maxSSTableTimestamp = Long.MAX_VALUE; + + @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) + { + for (int retries = 0; retries < 5; retries++) + { + try + { + if (retries > 0) + Thread.sleep(500); + probe.upgradeSSTables(probe.output().out, keyspace, !includeAll, maxSSTableTimestamp, jobs, tableNames); + break; + } + catch (RuntimeException cie) + { + // Spin retry. See CASSANDRA-18635 + if (ExceptionUtils.indexOfThrowable(cie, CompactionInterruptedException.class) != -1 && retries == 4) + throw (cie); + } + catch (Exception e) + { + throw new RuntimeException("Error occurred during enabling auto-compaction", e); + } + } + } + } +} diff --git a/src/java/org/apache/cassandra/management/api/Verify.java b/src/java/org/apache/cassandra/management/api/Verify.java new file mode 100644 index 000000000000..be8ff941134d --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/Verify.java @@ -0,0 +1,102 @@ +/* + * 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 io.airlift.airline.Option; +import org.apache.cassandra.tools.NodeProbe; +import org.apache.cassandra.tools.NodeTool.NodeToolCmd; + +@Command(name = "verify", description = "Verify (check data checksum for) one or more tables") +public class Verify extends NodeToolCmd +{ + @Arguments(usage = "[ ...]", description = "The keyspace followed by one or many tables") + private List args = new ArrayList<>(); + + @Option(title = "extended_verify", + name = {"-e", "--extended-verify"}, + description = "Verify each cell data, beyond simply checking sstable checksums") + private boolean extendedVerify = false; + + @Option(title = "check_version", + name = {"-c", "--check-version"}, + description = "Also check that all sstables are the latest version") + private boolean checkVersion = false; + + @Option(title = "override-disable", + name = {"-f", "--force"}, + description = "Override disabling of verify tool - see CASSANDRA-9947 for caveats") + private boolean overrideDisable = false; + + @Option(title = "dfp", + name = {"-d", "--dfp"}, + description = "Invoke the disk failure policy if a corrupt sstable is found") + private boolean diskFailurePolicy = false; + + @Option(title = "repair_status_change", + name = {"-r", "--rsc"}, + description = "Mutate the repair status on corrupt sstables") + private boolean mutateRepairStatus = false; + + @Option(title = "check_owns_tokens", + name = {"-t", "--check-tokens"}, + description = "Verify that all tokens in sstables are owned by this node") + private boolean checkOwnsTokens = false; + + @Option(title = "quick", + name = {"-q", "--quick"}, + description = "Do a quick check - avoid reading all data to verify checksums") + private boolean quick = false; + + @Override + public void execute(NodeProbe probe) + { + PrintStream out = probe.output().out; + if (!overrideDisable) + { + out.println("verify is disabled unless a [-f|--force] override flag is provided. See CASSANDRA-9947 and CASSANDRA-17017 for details."); + System.exit(1); + } + + List keyspaces = parseOptionalKeyspace(args, probe); + String[] tableNames = parseOptionalTables(args); + + if (checkOwnsTokens && !extendedVerify) + { + out.println("Token verification requires --extended-verify"); + // if System.exit gets removed, make sure to update org.apache.cassandra.distributed.test.NodeToolTest.testNodetoolSystemExit + System.exit(1); + } + + for (String keyspace : keyspaces) + { + try + { + probe.verify(out, extendedVerify, checkVersion, diskFailurePolicy, mutateRepairStatus, checkOwnsTokens, quick, keyspace, tableNames); + } catch (Exception e) + { + throw new RuntimeException("Error occurred during verifying", e); + } + } + } +} diff --git a/src/java/org/apache/cassandra/management/api/Version.java b/src/java/org/apache/cassandra/management/api/Version.java new file mode 100644 index 000000000000..463d4ecfda7f --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/Version.java @@ -0,0 +1,40 @@ +/* + * 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 = "version", description = "Print cassandra version") +public class Version extends NodeToolCmd +{ + @Option(title = "verbose", + name = {"-v", "--verbose"}, + description = "Include additional information") + private boolean verbose = false; + + @Override + public void execute(NodeProbe probe) + { + probe.output().out.println("ReleaseVersion: " + probe.getReleaseVersion()); + if (verbose) + probe.output().out.println("GitSHA: " + probe.getGitSHA()); + } +} diff --git a/src/java/org/apache/cassandra/management/api/ViewBuildStatus.java b/src/java/org/apache/cassandra/management/api/ViewBuildStatus.java new file mode 100644 index 000000000000..f9854bc7cb90 --- /dev/null +++ b/src/java/org/apache/cassandra/management/api/ViewBuildStatus.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 java.io.PrintStream; +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; +import org.apache.cassandra.tools.nodetool.formatter.TableBuilder; + +import static com.google.common.base.Preconditions.checkArgument; + +@Command(name = "viewbuildstatus", description = "Show progress of a materialized view build") +public class ViewBuildStatus extends NodeTool.NodeToolCmd +{ + private final static String SUCCESS = "SUCCESS"; + + @Arguments(usage = " | ", description = "The keyspace and view name") + private List args = new ArrayList<>(); + + protected void execute(NodeProbe probe) + { + PrintStream out = probe.output().out; + String keyspace = null, view = null; + if (args.size() == 2) + { + keyspace = args.get(0); + view = args.get(1); + } + else if (args.size() == 1) + { + String[] input = args.get(0).split("\\."); + checkArgument(input.length == 2, "viewbuildstatus requires keyspace and view name arguments"); + keyspace = input[0]; + view = input[1]; + } + else + { + checkArgument(false, "viewbuildstatus requires keyspace and view name arguments"); + } + + Map buildStatus = probe.getViewBuildStatuses(keyspace, view); + boolean failed = false; + TableBuilder builder = new TableBuilder(); + + builder.add("Host", "Info"); + for (Map.Entry status : buildStatus.entrySet()) + { + if (!status.getValue().equals(SUCCESS)) { + failed = true; + } + builder.add(status.getKey(), status.getValue()); + } + + if (failed) { + out.println(String.format("%s.%s has not finished building; node status is below.", keyspace, view)); + out.println(); + builder.printTo(out); + System.exit(1); + } else { + out.println(String.format("%s.%s has finished building", keyspace, view)); + System.exit(0); + } + } +}