diff --git a/src/java/org/apache/cassandra/tools/NodeTool.java b/src/java/org/apache/cassandra/tools/NodeTool.java
index 005e5304ed..7f538d57d7 100644
--- a/src/java/org/apache/cassandra/tools/NodeTool.java
+++ b/src/java/org/apache/cassandra/tools/NodeTool.java
@@ -23,8 +23,10 @@ import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Date;
import java.util.List;
+import java.util.Set;
import javax.inject.Inject;
import javax.management.InstanceNotFoundException;
@@ -46,6 +48,8 @@ import picocli.CommandLine;
import static com.google.common.base.Throwables.getStackTraceAsString;
import static org.apache.cassandra.io.util.File.WriteMode.APPEND;
+import static org.apache.cassandra.tools.nodetool.PrintPortMixin.PRINT_PORT_LONG;
+import static org.apache.cassandra.tools.nodetool.PrintPortMixin.PRINT_PORT_SHORT;
import static org.apache.cassandra.utils.LocalizeString.toUpperCaseLocalized;
public class NodeTool
@@ -57,6 +61,20 @@ public class NodeTool
private static final String HISTORYFILE = "nodetool.history";
+ /**
+ * Set of subcommand names that accept the {@code -pp/--print-port} options.
+ * These are the commands that declare the option via @Mixin and thus require
+ * relocating the option for backward compatibility in order to be recognized
+ * by picocli when specified before the subcommand name.
+ *
+ * Both calls such as {@code ./nodetool --print-port status}, and
+ * {@code ./nodetool status --print-port} should work as expected.
+ */
+ private static final Set COMMANDS_SUPPORTING_PRINT_PORT_OPTION = Set.of("status", "ring", "netstats",
+ "gossipinfo", "getendpoints",
+ "failuredetector", "describering",
+ "describecluster");
+
private final INodeProbeFactory nodeProbeFactory;
private final Output output;
@@ -112,7 +130,7 @@ public class NodeTool
.setPosixClusteredShortOptionsAllowed(false);
printHistory(args);
- return commandLine.execute(args);
+ return commandLine.execute(relocatePrintPortOptionsForBackwardCompatibility(args));
}
catch (ConfigurationException e)
{
@@ -220,6 +238,66 @@ public class NodeTool
output.err.println(getStackTraceAsString(e));
}
+ /**
+ * Rewrites global {@code -pp/--print-port} options that have been moved
+ * to subcommands via @Mixin for backward compatibility. When a user types:
+ *
+ * nodetool -pp status
+ * nodetool --print-port status -r
+ *
+ * this method rewrites them to:
+ *
+ * nodetool status -pp
+ * nodetool status -r --print-port
+ *
+ * so that picocli assigns the option to the subcommand that declares it.
+ *
+ * Options that appear after the subcommand name are left untouched:
+ *
+ * nodetool status -pp -> unchanged
+ * nodetool status -pp -r -> unchanged
+ *
+ */
+ static String[] relocatePrintPortOptionsForBackwardCompatibility(String[] args)
+ {
+ if (args == null || args.length < 2)
+ return args;
+
+ Set relocatable = Set.of(PRINT_PORT_SHORT, PRINT_PORT_LONG);
+
+ int subcommandIdx = -1;
+ for (int i = 0; i < args.length; i++)
+ {
+ if (COMMANDS_SUPPORTING_PRINT_PORT_OPTION.contains(args[i]))
+ {
+ subcommandIdx = i;
+ break;
+ }
+ }
+
+ if (subcommandIdx < 0)
+ return args;
+
+ List before = new ArrayList<>();
+ List toRelocate = new ArrayList<>();
+ for (int i = 0; i < subcommandIdx; i++)
+ {
+ if (relocatable.contains(args[i]))
+ toRelocate.add(args[i]);
+ else
+ before.add(args[i]);
+ }
+
+ if (toRelocate.isEmpty())
+ return args;
+
+ List result = new ArrayList<>(before);
+ result.addAll(Arrays.asList(args).subList(subcommandIdx, args.length));
+ result.addAll(toRelocate);
+
+ return result.toArray(new String[0]);
+ }
+
private enum CliLayout
{
AIRLINE,
diff --git a/src/java/org/apache/cassandra/tools/nodetool/DescribeCluster.java b/src/java/org/apache/cassandra/tools/nodetool/DescribeCluster.java
index a045dfa605..448d62a94a 100644
--- a/src/java/org/apache/cassandra/tools/nodetool/DescribeCluster.java
+++ b/src/java/org/apache/cassandra/tools/nodetool/DescribeCluster.java
@@ -31,14 +31,18 @@ import org.apache.cassandra.locator.LocationInfoMBean;
import org.apache.cassandra.tools.NodeProbe;
import picocli.CommandLine.Command;
+import picocli.CommandLine.Mixin;
@Command(name = "describecluster", description = "Print the name, snitch, partitioner and schema version of a cluster")
-public class DescribeCluster extends WithPortDisplayAbstractCommand
+public class DescribeCluster extends AbstractCommand
{
private boolean resolveIp = false;
private String keyspace = null;
private Collection joiningNodes, leavingNodes, movingNodes, liveNodes, unreachableNodes;
+ @Mixin
+ private PrintPortMixin printPortMixin = new PrintPortMixin();
+
@Override
public void execute(NodeProbe probe)
{
@@ -63,7 +67,7 @@ public class DescribeCluster extends WithPortDisplayAbstractCommand
// display schema version for each node
out.println("\tSchema versions:");
- Map> schemaVersions = printPort ? probe.getSpProxy().getSchemaVersionsWithPort() : probe.getSpProxy().getSchemaVersions();
+ Map> schemaVersions = printPortMixin.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());
diff --git a/src/java/org/apache/cassandra/tools/nodetool/DescribeRing.java b/src/java/org/apache/cassandra/tools/nodetool/DescribeRing.java
index fd2ca386ad..21c943578a 100644
--- a/src/java/org/apache/cassandra/tools/nodetool/DescribeRing.java
+++ b/src/java/org/apache/cassandra/tools/nodetool/DescribeRing.java
@@ -23,16 +23,20 @@ import java.io.PrintStream;
import org.apache.cassandra.tools.NodeProbe;
import picocli.CommandLine.Command;
+import picocli.CommandLine.Mixin;
import picocli.CommandLine.Parameters;
import static org.apache.commons.lang3.StringUtils.EMPTY;
@Command(name = "describering", description = "Shows the token ranges info of a given keyspace")
-public class DescribeRing extends WithPortDisplayAbstractCommand
+public class DescribeRing extends AbstractCommand
{
@Parameters(description = "The keyspace name", arity = "1")
private String keyspace = EMPTY;
+ @Mixin
+ private PrintPortMixin printPortMixin = new PrintPortMixin();
+
@Override
public void execute(NodeProbe probe)
{
@@ -41,7 +45,7 @@ public class DescribeRing extends WithPortDisplayAbstractCommand
out.println("TokenRange: ");
try
{
- for (String tokenRangeString : probe.describeRing(keyspace, printPort))
+ for (String tokenRangeString : probe.describeRing(keyspace, printPortMixin.printPort))
{
out.println("\t" + tokenRangeString);
}
diff --git a/src/java/org/apache/cassandra/tools/nodetool/FailureDetectorInfo.java b/src/java/org/apache/cassandra/tools/nodetool/FailureDetectorInfo.java
index f24feccc01..addbe5a8d0 100644
--- a/src/java/org/apache/cassandra/tools/nodetool/FailureDetectorInfo.java
+++ b/src/java/org/apache/cassandra/tools/nodetool/FailureDetectorInfo.java
@@ -25,14 +25,18 @@ import javax.management.openmbean.TabularData;
import org.apache.cassandra.tools.NodeProbe;
import picocli.CommandLine.Command;
+import picocli.CommandLine.Mixin;
@Command(name = "failuredetector", description = "Shows the failure detector information for the cluster")
-public class FailureDetectorInfo extends WithPortDisplayAbstractCommand
+public class FailureDetectorInfo extends AbstractCommand
{
+ @Mixin
+ private PrintPortMixin printPortMixin = new PrintPortMixin();
+
@Override
public void execute(NodeProbe probe)
{
- TabularData data = probe.getFailureDetectorPhilValues(printPort);
+ TabularData data = probe.getFailureDetectorPhilValues(printPortMixin.printPort);
probe.output().out.printf("%10s,%16s%n", "Endpoint", "Phi");
for (Object o : data.keySet())
{
diff --git a/src/java/org/apache/cassandra/tools/nodetool/GetEndpoints.java b/src/java/org/apache/cassandra/tools/nodetool/GetEndpoints.java
index b83a06e217..3f30680951 100644
--- a/src/java/org/apache/cassandra/tools/nodetool/GetEndpoints.java
+++ b/src/java/org/apache/cassandra/tools/nodetool/GetEndpoints.java
@@ -25,13 +25,14 @@ import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.nodetool.layout.CassandraUsage;
import picocli.CommandLine.Command;
+import picocli.CommandLine.Mixin;
import picocli.CommandLine.Parameters;
import static com.google.common.base.Preconditions.checkArgument;
import static org.apache.cassandra.tools.nodetool.CommandUtils.concatArgs;
@Command(name = "getendpoints", description = "Print the end points that owns the key")
-public class GetEndpoints extends WithPortDisplayAbstractCommand
+public class GetEndpoints extends AbstractCommand
{
@CassandraUsage(usage = "
", description = "The keyspace, the table, and the partition key for which we need to find the endpoint")
private List args = new ArrayList<>();
@@ -45,6 +46,9 @@ public class GetEndpoints extends WithPortDisplayAbstractCommand
@Parameters(index = "2", arity = "0..1", description = "The partition key for which we need to find the endpoint")
private String key;
+ @Mixin
+ private PrintPortMixin printPortMixin = new PrintPortMixin();
+
@Override
public void execute(NodeProbe probe)
{
@@ -55,7 +59,7 @@ public class GetEndpoints extends WithPortDisplayAbstractCommand
String table = args.get(1);
String key = args.get(2);
- if (printPort)
+ if (printPortMixin.printPort)
{
for (String endpoint : probe.getEndpointsWithPort(ks, table, key))
{
diff --git a/src/java/org/apache/cassandra/tools/nodetool/GossipInfo.java b/src/java/org/apache/cassandra/tools/nodetool/GossipInfo.java
index a75b3b7abd..94ae46b9ad 100644
--- a/src/java/org/apache/cassandra/tools/nodetool/GossipInfo.java
+++ b/src/java/org/apache/cassandra/tools/nodetool/GossipInfo.java
@@ -20,17 +20,21 @@ package org.apache.cassandra.tools.nodetool;
import org.apache.cassandra.tools.NodeProbe;
import picocli.CommandLine.Command;
+import picocli.CommandLine.Mixin;
import picocli.CommandLine.Option;
@Command(name = "gossipinfo", description = "Shows the gossip information for the cluster")
-public class GossipInfo extends WithPortDisplayAbstractCommand
+public class GossipInfo extends AbstractCommand
{
@Option(paramLabel = "resolve_ip", names = { "-r", "--resolve-ip" }, description = "Show node domain names instead of IPs")
private boolean resolveIp = false;
+ @Mixin
+ private PrintPortMixin printPortMixin = new PrintPortMixin();
+
@Override
public void execute(NodeProbe probe)
{
- probe.output().out.println(probe.getGossipInfo(printPort, resolveIp));
+ probe.output().out.println(probe.getGossipInfo(printPortMixin.printPort, resolveIp));
}
}
diff --git a/src/java/org/apache/cassandra/tools/nodetool/NetStats.java b/src/java/org/apache/cassandra/tools/nodetool/NetStats.java
index 0399030315..7ed689c1ed 100644
--- a/src/java/org/apache/cassandra/tools/nodetool/NetStats.java
+++ b/src/java/org/apache/cassandra/tools/nodetool/NetStats.java
@@ -31,16 +31,20 @@ import org.apache.cassandra.streaming.StreamState;
import org.apache.cassandra.tools.NodeProbe;
import picocli.CommandLine.Command;
+import picocli.CommandLine.Mixin;
import picocli.CommandLine.Option;
@Command(name = "netstats", description = "Print network information on provided host (connecting node by default)")
-public class NetStats extends WithPortDisplayAbstractCommand
+public class NetStats extends AbstractCommand
{
@Option(paramLabel = "human_readable",
names = { "-H", "--human-readable" },
description = "Display bytes in human readable form, i.e. KiB, MiB, GiB, TiB")
private boolean humanReadable = false;
+ @Mixin
+ private PrintPortMixin printPortMixin = new PrintPortMixin();
+
@Override
public void execute(NodeProbe probe)
{
@@ -54,11 +58,11 @@ public class NetStats extends WithPortDisplayAbstractCommand
out.printf("%s %s%n", status.streamOperation.getDescription(), status.planId.toString());
for (SessionInfo info : status.sessions)
{
- out.printf(" %s", InetAddressAndPort.toString(info.peer, printPort));
+ out.printf(" %s", InetAddressAndPort.toString(info.peer, printPortMixin.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(" (using %s)", InetAddressAndPort.toString(info.connecting, printPortMixin.printPort));
}
out.printf("%n");
if (!info.receivingSummaries.isEmpty())
@@ -142,7 +146,7 @@ public class NetStats extends WithPortDisplayAbstractCommand
for (ProgressInfo progress : info.getReceivingFiles())
{
- out.printf(" %s%n", progress.toString(printPort));
+ out.printf(" %s%n", progress.toString(printPortMixin.printPort));
}
}
@@ -166,7 +170,7 @@ public class NetStats extends WithPortDisplayAbstractCommand
for (ProgressInfo progress : info.getSendingFiles())
{
- out.printf(" %s%n", progress.toString(printPort));
+ out.printf(" %s%n", progress.toString(printPortMixin.printPort));
}
}
}
diff --git a/src/java/org/apache/cassandra/tools/nodetool/NodetoolCommand.java b/src/java/org/apache/cassandra/tools/nodetool/NodetoolCommand.java
index 33292b2483..af1e835d91 100644
--- a/src/java/org/apache/cassandra/tools/nodetool/NodetoolCommand.java
+++ b/src/java/org/apache/cassandra/tools/nodetool/NodetoolCommand.java
@@ -20,7 +20,6 @@ package org.apache.cassandra.tools.nodetool;
import picocli.CommandLine.Command;
import picocli.CommandLine.Model.CommandSpec;
-import picocli.CommandLine.Option;
import picocli.CommandLine.Spec;
import static org.apache.cassandra.tools.nodetool.Help.printTopCommandUsage;
@@ -61,6 +60,7 @@ import static org.apache.cassandra.tools.nodetool.Help.printTopCommandUsage;
AccordAdmin.class,
AlterTopology.class,
Assassinate.class,
+ AsyncProfileCommandGroup.class,
AutoRepairStatus.class,
Bootstrap.class,
CIDRFilteringStats.class,
@@ -71,6 +71,7 @@ import static org.apache.cassandra.tools.nodetool.Help.printTopCommandUsage;
Compact.class,
CompactionHistory.class,
CompactionStats.class,
+ CompressionDictionaryCommandGroup.class,
ConsensusMigrationAdmin.class,
DataPaths.class,
Decommission.Abort.class,
@@ -149,7 +150,6 @@ import static org.apache.cassandra.tools.nodetool.Help.printTopCommandUsage;
NetStats.class,
PauseHandoff.class,
ProfileLoad.class,
- AsyncProfileCommandGroup.class,
ProxyHistograms.class,
RangeKeySample.class,
Rebuild.class,
@@ -208,7 +208,6 @@ import static org.apache.cassandra.tools.nodetool.Help.printTopCommandUsage;
TableStats.class,
TopPartitions.class,
TpStats.class,
- CompressionDictionaryCommandGroup.class,
TruncateHints.class,
UpdateCIDRGroup.class,
UpgradeSSTable.class,
@@ -220,12 +219,6 @@ public class NodetoolCommand implements Runnable
@Spec
public CommandSpec spec;
- // TODO CASSANDRA-20790 this option is used only in several commands and should not be the global option.
- // It should be pushed down to specific commands to clean up the global hierarchy, while maintaining backwards compatibility.
- // Calls such as './nodetool --print-port subcommand', and './nodetool subcommand --print-port' should work as expected.
- @Option(names = { "-pp", "--print-port" }, description = "Operate in 4.0 mode with hosts disambiguated by port number")
- public boolean printPort = false;
-
public void run()
{
printTopCommandUsage(spec.commandLine(),
diff --git a/src/java/org/apache/cassandra/tools/nodetool/WithPortDisplayAbstractCommand.java b/src/java/org/apache/cassandra/tools/nodetool/PrintPortMixin.java
similarity index 62%
rename from src/java/org/apache/cassandra/tools/nodetool/WithPortDisplayAbstractCommand.java
rename to src/java/org/apache/cassandra/tools/nodetool/PrintPortMixin.java
index fe67f126f3..22daf43bd0 100644
--- a/src/java/org/apache/cassandra/tools/nodetool/WithPortDisplayAbstractCommand.java
+++ b/src/java/org/apache/cassandra/tools/nodetool/PrintPortMixin.java
@@ -18,23 +18,14 @@
package org.apache.cassandra.tools.nodetool;
-import picocli.CommandLine.ParentCommand;
+import picocli.CommandLine;
-/**
- * Abstract class for commands that display endpoints that can be disambiguated by port number. (e.g. gossipinfo, describecluster etc.).
- */
-abstract class WithPortDisplayAbstractCommand extends AbstractCommand
+public class PrintPortMixin
{
- @ParentCommand
- private NodetoolCommand parent;
+ public static final String PRINT_PORT_SHORT = "-pp";
+ public static final String PRINT_PORT_LONG = "--print-port";
- /** See {@link NodetoolCommand#printPort} option. */
- protected boolean printPort;
-
- @Override
- protected boolean shouldConnect()
- {
- printPort = parent.printPort;
- return true;
- }
+ @CommandLine.Option(names = { PRINT_PORT_SHORT, PRINT_PORT_LONG },
+ description = "Operate in 4.0 mode with hosts disambiguated by port number")
+ public boolean printPort = false;
}
diff --git a/src/java/org/apache/cassandra/tools/nodetool/RemoveNode.java b/src/java/org/apache/cassandra/tools/nodetool/RemoveNode.java
index f827a37888..9ab4cc2550 100644
--- a/src/java/org/apache/cassandra/tools/nodetool/RemoveNode.java
+++ b/src/java/org/apache/cassandra/tools/nodetool/RemoveNode.java
@@ -20,9 +20,9 @@ package org.apache.cassandra.tools.nodetool;
import org.apache.cassandra.tools.NodeProbe;
import picocli.CommandLine.Command;
+import picocli.CommandLine.Mixin;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
-import picocli.CommandLine.ParentCommand;
import static com.google.common.base.Preconditions.checkArgument;
@@ -31,9 +31,6 @@ import static com.google.common.base.Preconditions.checkArgument;
subcommands = { RemoveNode.Status.class })
public class RemoveNode extends AbstractCommand
{
- @ParentCommand
- public NodetoolCommand parent;
-
@Parameters(paramLabel = "nodeId", description = "The ID of the node to remove", arity = "0..1")
private String nodeId;
@@ -64,13 +61,13 @@ public class RemoveNode extends AbstractCommand
@Command(name = "status", description = "Show status of the current node removal operation")
public static class Status extends AbstractCommand
{
- @ParentCommand
- private RemoveNode parent;
+ @Mixin
+ private PrintPortMixin printPortMixin = new PrintPortMixin();
@Override
public void execute(NodeProbe probe)
{
- probe.output().out.println("RemovalStatus: " + probe.getRemovalStatus(parent.parent.printPort));
+ probe.output().out.println("RemovalStatus: " + probe.getRemovalStatus(printPortMixin.printPort));
}
}
}
diff --git a/src/java/org/apache/cassandra/tools/nodetool/Ring.java b/src/java/org/apache/cassandra/tools/nodetool/Ring.java
index 15964525da..6e8a59a8d4 100644
--- a/src/java/org/apache/cassandra/tools/nodetool/Ring.java
+++ b/src/java/org/apache/cassandra/tools/nodetool/Ring.java
@@ -34,13 +34,14 @@ import org.apache.cassandra.locator.EndpointSnitchInfoMBean;
import org.apache.cassandra.tools.NodeProbe;
import picocli.CommandLine.Command;
+import picocli.CommandLine.Mixin;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import static java.lang.String.format;
@Command(name = "ring", description = "Print information about the token ring")
-public class Ring extends WithPortDisplayAbstractCommand
+public class Ring extends AbstractCommand
{
@Parameters(description = "Specify a keyspace for accurate ownership information (topology awareness)", index = "0", arity = "0..1")
private String keyspace = null;
@@ -48,6 +49,9 @@ public class Ring extends WithPortDisplayAbstractCommand
@Option(paramLabel = "resolve_ip", names = { "-r", "--resolve-ip" }, description = "Show node domain names instead of IPs")
private boolean resolveIp = false;
+ @Mixin
+ private PrintPortMixin printPortMixin = new PrintPortMixin();
+
private PrintStream out;
private EndpointSnitchInfoMBean epSnitchInfo;
private Collection liveNodes, deadNodes, joiningNodes, leavingNodes, movingNodes;
@@ -176,7 +180,7 @@ public class Ring extends WithPortDisplayAbstractCommand
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.printf(format, stat.ipOrDns(printPortMixin.printPort), rack, status, state, load, owns, stat.token);
}
out.println();
}
diff --git a/src/java/org/apache/cassandra/tools/nodetool/Status.java b/src/java/org/apache/cassandra/tools/nodetool/Status.java
index a0312aeed8..17083bc7a5 100644
--- a/src/java/org/apache/cassandra/tools/nodetool/Status.java
+++ b/src/java/org/apache/cassandra/tools/nodetool/Status.java
@@ -44,13 +44,14 @@ import org.apache.cassandra.tools.nodetool.formatter.TableBuilder;
import org.apache.cassandra.utils.LocalizeString;
import picocli.CommandLine.Command;
+import picocli.CommandLine.Mixin;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import static java.util.stream.Collectors.toMap;
@Command(name = "status", description = "Print cluster information (state, load, IDs, ...)")
-public class Status extends WithPortDisplayAbstractCommand
+public class Status extends AbstractCommand
{
@Parameters(description = "The keyspace name", arity = "0..1")
private String keyspace = null;
@@ -71,6 +72,9 @@ public class Status extends WithPortDisplayAbstractCommand
description = "Sorting order: 'asc' for ascending, 'desc' for descending.")
private SortOrder sortOrder = null;
+ @Mixin
+ private PrintPortMixin printPortMixin = new PrintPortMixin();
+
private boolean isTokenPerNode = true;
private Collection joiningNodes, leavingNodes, movingNodes, liveNodes, unreachableNodes;
private Map loadMap, hostIDMap;
@@ -167,7 +171,7 @@ public class Status extends WithPortDisplayAbstractCommand
List tokens = hostToTokens.get(endpoint);
HostStatWithPort hostStatWithPort = tokens.get(0);
- String epDns = hostStatWithPort.ipOrDns(printPort);
+ String epDns = hostStatWithPort.ipOrDns(printPortMixin.printPort);
List