From c990b265a0781dc567e4149a0f6b377053b7192e Mon Sep 17 00:00:00 2001 From: nizhikov Date: Tue, 28 Nov 2023 18:46:20 +0300 Subject: [PATCH 1/9] KAFKA-14589 ConsumerGroupCommand options and case classes rewritten in java --- .../org/apache/kafka/common/utils/Utils.java | 10 + .../ConsumerGroupCommandOptions.java | 263 ++++++++++++++++++ .../kafka/tools/consumergroup/GroupState.java | 35 +++ .../consumergroup/MemberAssignmentState.java | 42 +++ .../PartitionAssignmentState.java | 50 ++++ 5 files changed, 400 insertions(+) create mode 100644 tools/src/main/java/org/apache/kafka/tools/consumergroup/ConsumerGroupCommandOptions.java create mode 100644 tools/src/main/java/org/apache/kafka/tools/consumergroup/GroupState.java create mode 100644 tools/src/main/java/org/apache/kafka/tools/consumergroup/MemberAssignmentState.java create mode 100644 tools/src/main/java/org/apache/kafka/tools/consumergroup/PartitionAssignmentState.java diff --git a/clients/src/main/java/org/apache/kafka/common/utils/Utils.java b/clients/src/main/java/org/apache/kafka/common/utils/Utils.java index 6a0913d3c2da1..0a6461910a632 100644 --- a/clients/src/main/java/org/apache/kafka/common/utils/Utils.java +++ b/clients/src/main/java/org/apache/kafka/common/utils/Utils.java @@ -593,6 +593,16 @@ public static String join(T[] strs, String separator) { return join(Arrays.asList(strs), separator); } + /** + * Create a string representation of a collection joined by ", ". + * @param collection The list of items + * @return The string representation. + */ + public static String join(Collection collection) { + Objects.requireNonNull(collection); + return mkString(collection.stream(), "", "", ", "); + } + /** * Create a string representation of a collection joined by the given separator * @param collection The list of items diff --git a/tools/src/main/java/org/apache/kafka/tools/consumergroup/ConsumerGroupCommandOptions.java b/tools/src/main/java/org/apache/kafka/tools/consumergroup/ConsumerGroupCommandOptions.java new file mode 100644 index 0000000000000..8ab65746e7b06 --- /dev/null +++ b/tools/src/main/java/org/apache/kafka/tools/consumergroup/ConsumerGroupCommandOptions.java @@ -0,0 +1,263 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.kafka.tools.consumergroup; + +import joptsimple.OptionSpec; +import org.apache.kafka.common.utils.Utils; +import org.apache.kafka.server.util.CommandDefaultOptions; +import org.apache.kafka.server.util.CommandLineUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class ConsumerGroupCommandOptions extends CommandDefaultOptions { + public static final Logger LOGGER = LoggerFactory.getLogger(ConsumerGroupCommandOptions.class); + + public static final String BOOTSTRAP_SERVER_DOC = "REQUIRED: The server(s) to connect to."; + public static final String GROUP_DOC = "The consumer group we wish to act on."; + public static final String TOPIC_DOC = "The topic whose consumer group information should be deleted or topic whose should be included in the reset offset process. " + + "In `reset-offsets` case, partitions can be specified using this format: `topic1:0,1,2`, where 0,1,2 are the partition to be included in the process. " + + "Reset-offsets also supports multiple topic inputs."; + public static final String ALL_TOPICS_DOC = "Consider all topics assigned to a group in the `reset-offsets` process."; + public static final String LIST_DOC = "List all consumer groups."; + public static final String DESCRIBE_DOC = "Describe consumer group and list offset lag (number of messages not yet processed) related to given group."; + public static final String ALL_GROUPS_DOC = "Apply to all consumer groups."; + public static final String NL = System.getProperty("line.separator"); + public static final String DELETE_DOC = "Pass in groups to delete topic partition offsets and ownership information " + + "over the entire consumer group. For instance --group g1 --group g2"; + public static final String TIMEOUT_MS_DOC = "The timeout that can be set for some use cases. For example, it can be used when describing the group " + + "to specify the maximum amount of time in milliseconds to wait before the group stabilizes (when the group is just created, " + + "or is going through some changes)."; + public static final String COMMAND_CONFIG_DOC = "Property file containing configs to be passed to Admin Client and Consumer."; + public static final String RESET_OFFSETS_DOC = "Reset offsets of consumer group. Supports one consumer group at the time, and instances should be inactive" + NL + + "Has 2 execution options: --dry-run (the default) to plan which offsets to reset, and --execute to update the offsets. " + + "Additionally, the --export option is used to export the results to a CSV format." + NL + + "You must choose one of the following reset specifications: --to-datetime, --by-duration, --to-earliest, " + + "--to-latest, --shift-by, --from-file, --to-current, --to-offset." + NL + + "To define the scope use --all-topics or --topic. One scope must be specified unless you use '--from-file'."; + public static final String DRY_RUN_DOC = "Only show results without executing changes on Consumer Groups. Supported operations: reset-offsets."; + public static final String EXECUTE_DOC = "Execute operation. Supported operations: reset-offsets."; + public static final String EXPORT_DOC = "Export operation execution to a CSV file. Supported operations: reset-offsets."; + public static final String RESET_TO_OFFSET_DOC = "Reset offsets to a specific offset."; + public static final String RESET_FROM_FILE_DOC = "Reset offsets to values defined in CSV file."; + public static final String RESET_TO_DATETIME_DOC = "Reset offsets to offset from datetime. Format: 'YYYY-MM-DDTHH:mm:SS.sss'"; + public static final String RESET_BY_DURATION_DOC = "Reset offsets to offset by duration from current timestamp. Format: 'PnDTnHnMnS'"; + public static final String RESET_TO_EARLIEST_DOC = "Reset offsets to earliest offset."; + public static final String RESET_TO_LATEST_DOC = "Reset offsets to latest offset."; + public static final String RESET_TO_CURRENT_DOC = "Reset offsets to current offset."; + public static final String RESET_SHIFT_BY_DOC = "Reset offsets shifting current offset by 'n', where 'n' can be positive or negative."; + public static final String MEMBERS_DOC = "Describe members of the group. This option may be used with '--describe' and '--bootstrap-server' options only." + NL + + "Example: --bootstrap-server localhost:9092 --describe --group group1 --members"; + public static final String VERBOSE_DOC = "Provide additional information, if any, when describing the group. This option may be used " + + "with '--offsets'/'--members'/'--state' and '--bootstrap-server' options only." + NL + "Example: --bootstrap-server localhost:9092 --describe --group group1 --members --verbose"; + public static final String OFFSETS_DOC = "Describe the group and list all topic partitions in the group along with their offset lag. " + + "This is the default sub-action of and may be used with '--describe' and '--bootstrap-server' options only." + NL + + "Example: --bootstrap-server localhost:9092 --describe --group group1 --offsets"; + public static final String STATE_DOC = "When specified with '--describe', includes the state of the group." + NL + + "Example: --bootstrap-server localhost:9092 --describe --group group1 --state" + NL + + "When specified with '--list', it displays the state of all groups. It can also be used to list groups with specific states." + NL + + "Example: --bootstrap-server localhost:9092 --list --state stable,empty" + NL + + "This option may be used with '--describe', '--list' and '--bootstrap-server' options only."; + public static final String DELETE_OFFSETS_DOC = "Delete offsets of consumer group. Supports one consumer group at the time, and multiple topics."; + + public final OptionSpec bootstrapServerOpt; + public final OptionSpec groupOpt; + public final OptionSpec topicOpt; + public final OptionSpec allTopicsOpt; + public final OptionSpec listOpt; + public final OptionSpec describeOpt; + public final OptionSpec allGroupsOpt; + public final OptionSpec deleteOpt; + public final OptionSpec timeoutMsOpt; + public final OptionSpec commandConfigOpt; + public final OptionSpec resetOffsetsOpt; + public final OptionSpec deleteOffsetsOpt; + public final OptionSpec dryRunOpt; + public final OptionSpec executeOpt; + public final OptionSpec exportOpt; + public final OptionSpec resetToOffsetOpt; + public final OptionSpec resetFromFileOpt; + public final OptionSpec resetToDatetimeOpt; + public final OptionSpec resetByDurationOpt; + public final OptionSpec resetToEarliestOpt; + public final OptionSpec resetToLatestOpt; + public final OptionSpec resetToCurrentOpt; + public final OptionSpec resetShiftByOpt; + public final OptionSpec membersOpt; + public final OptionSpec verboseOpt; + public final OptionSpec offsetsOpt; + public final OptionSpec stateOpt; + + public final Set> allGroupSelectionScopeOpts; + public final Set> allConsumerGroupLevelOpts; + public final Set> allResetOffsetScenarioOpts; + public final Set> allDeleteOffsetsOpts; + + public ConsumerGroupCommandOptions(String[] args) { + super(args); + + bootstrapServerOpt = parser.accepts("bootstrap-server", BOOTSTRAP_SERVER_DOC) + .withRequiredArg() + .describedAs("server to connect to") + .ofType(String.class); + groupOpt = parser.accepts("group", GROUP_DOC) + .withRequiredArg() + .describedAs("consumer group") + .ofType(String.class); + topicOpt = parser.accepts("topic", TOPIC_DOC) + .withRequiredArg() + .describedAs("topic") + .ofType(String.class); + allTopicsOpt = parser.accepts("all-topics", ALL_TOPICS_DOC); + listOpt = parser.accepts("list", LIST_DOC); + describeOpt = parser.accepts("describe", DESCRIBE_DOC); + allGroupsOpt = parser.accepts("all-groups", ALL_GROUPS_DOC); + deleteOpt = parser.accepts("delete", DELETE_DOC); + timeoutMsOpt = parser.accepts("timeout", TIMEOUT_MS_DOC) + .withRequiredArg() + .describedAs("timeout (ms)") + .ofType(Long.class) + .defaultsTo(5000L); + commandConfigOpt = parser.accepts("command-config", COMMAND_CONFIG_DOC) + .withRequiredArg() + .describedAs("command config property file") + .ofType(String.class); + resetOffsetsOpt = parser.accepts("reset-offsets", RESET_OFFSETS_DOC); + deleteOffsetsOpt = parser.accepts("delete-offsets", DELETE_OFFSETS_DOC); + dryRunOpt = parser.accepts("dry-run", DRY_RUN_DOC); + executeOpt = parser.accepts("execute", EXECUTE_DOC); + exportOpt = parser.accepts("export", EXPORT_DOC); + resetToOffsetOpt = parser.accepts("to-offset", RESET_TO_OFFSET_DOC) + .withRequiredArg() + .describedAs("offset") + .ofType(Long.class); + resetFromFileOpt = parser.accepts("from-file", RESET_FROM_FILE_DOC) + .withRequiredArg() + .describedAs("path to CSV file") + .ofType(String.class); + resetToDatetimeOpt = parser.accepts("to-datetime", RESET_TO_DATETIME_DOC) + .withRequiredArg() + .describedAs("datetime") + .ofType(String.class); + resetByDurationOpt = parser.accepts("by-duration", RESET_BY_DURATION_DOC) + .withRequiredArg() + .describedAs("duration") + .ofType(String.class); + resetToEarliestOpt = parser.accepts("to-earliest", RESET_TO_EARLIEST_DOC); + resetToLatestOpt = parser.accepts("to-latest", RESET_TO_LATEST_DOC); + resetToCurrentOpt = parser.accepts("to-current", RESET_TO_CURRENT_DOC); + resetShiftByOpt = parser.accepts("shift-by", RESET_SHIFT_BY_DOC) + .withRequiredArg() + .describedAs("number-of-offsets") + .ofType(Long.class); + membersOpt = parser.accepts("members", MEMBERS_DOC) + .availableIf(describeOpt); + verboseOpt = parser.accepts("verbose", VERBOSE_DOC) + .availableIf(describeOpt); + offsetsOpt = parser.accepts("offsets", OFFSETS_DOC) + .availableIf(describeOpt); + stateOpt = parser.accepts("state", STATE_DOC) + .availableIf(describeOpt, listOpt) + .withOptionalArg() + .ofType(String.class); + + allGroupSelectionScopeOpts = new HashSet<>(Arrays.asList(groupOpt, allGroupsOpt)); + allConsumerGroupLevelOpts = new HashSet<>(Arrays.asList(listOpt, describeOpt, deleteOpt, resetOffsetsOpt)); + allResetOffsetScenarioOpts = new HashSet<>(Arrays.asList(resetToOffsetOpt, resetShiftByOpt, + resetToDatetimeOpt, resetByDurationOpt, resetToEarliestOpt, resetToLatestOpt, resetToCurrentOpt, resetFromFileOpt)); + allDeleteOffsetsOpts = new HashSet<>(Arrays.asList(groupOpt, topicOpt)); + + options = parser.parse(args); + } + + @SuppressWarnings({"CyclomaticComplexity", "NPathComplexity"}) + public void checkArgs() { + CommandLineUtils.checkRequiredArgs(parser, options, bootstrapServerOpt); + + if (options.has(describeOpt)) { + if (!options.has(groupOpt) && !options.has(allGroupsOpt)) + CommandLineUtils.printUsageAndExit(parser, + "Option $describeOpt takes one of these options: " + Utils.join(allGroupSelectionScopeOpts)); + List> mutuallyExclusiveOpts = Arrays.asList(membersOpt, offsetsOpt, stateOpt); + if (mutuallyExclusiveOpts.stream().mapToInt(o -> options.has(o) ? 1 : 0).sum() > 1) { + CommandLineUtils.printUsageAndExit(parser, + "Option " + describeOpt + " takes at most one of these options: " + Utils.join(mutuallyExclusiveOpts)); + } + if (options.has(stateOpt) && options.valueOf(stateOpt) != null) + CommandLineUtils.printUsageAndExit(parser, + "Option " + describeOpt + " does not take a value for " + stateOpt); + } else { + if (options.has(timeoutMsOpt)) + LOGGER.debug("Option " + timeoutMsOpt + " is applicable only when " + describeOpt + " is used."); + } + + if (options.has(deleteOpt)) { + if (!options.has(groupOpt) && !options.has(allGroupsOpt)) + CommandLineUtils.printUsageAndExit(parser, + "Option " + deleteOpt + " takes one of these options: " + Utils.join(allGroupSelectionScopeOpts)); + if (options.has(topicOpt)) + CommandLineUtils.printUsageAndExit(parser, "The consumer does not support topic-specific offset " + + "deletion from a consumer group."); + } + + if (options.has(deleteOffsetsOpt)) { + if (!options.has(groupOpt) || !options.has(topicOpt)) + CommandLineUtils.printUsageAndExit(parser, + "Option " + deleteOffsetsOpt + " takes the following options: " + Utils.join(allDeleteOffsetsOpts)); + } + + if (options.has(resetOffsetsOpt)) { + if (options.has(dryRunOpt) && options.has(executeOpt)) + CommandLineUtils.printUsageAndExit(parser, "Option " + resetOffsetsOpt + " only accepts one of " + executeOpt + " and " + dryRunOpt); + + if (!options.has(dryRunOpt) && !options.has(executeOpt)) { + System.err.println("WARN: No action will be performed as the --execute option is missing." + + "In a future major release, the default behavior of this command will be to prompt the user before " + + "executing the reset rather than doing a dry run. You should add the --dry-run option explicitly " + + "if you are scripting this command and want to keep the current default behavior without prompting."); + } + + if (!options.has(groupOpt) && !options.has(allGroupsOpt)) + CommandLineUtils.printUsageAndExit(parser, + "Option " + resetOffsetsOpt + " takes one of these options: " + Utils.join(allGroupSelectionScopeOpts)); + CommandLineUtils.checkInvalidArgs(parser, options, resetToOffsetOpt, minus(allResetOffsetScenarioOpts, resetToOffsetOpt)); + CommandLineUtils.checkInvalidArgs(parser, options, resetToDatetimeOpt, minus(allResetOffsetScenarioOpts, resetToDatetimeOpt)); + CommandLineUtils.checkInvalidArgs(parser, options, resetByDurationOpt, minus(allResetOffsetScenarioOpts, resetByDurationOpt)); + CommandLineUtils.checkInvalidArgs(parser, options, resetToEarliestOpt, minus(allResetOffsetScenarioOpts, resetToEarliestOpt)); + CommandLineUtils.checkInvalidArgs(parser, options, resetToLatestOpt, minus(allResetOffsetScenarioOpts, resetToLatestOpt)); + CommandLineUtils.checkInvalidArgs(parser, options, resetToCurrentOpt, minus(allResetOffsetScenarioOpts, resetToCurrentOpt)); + CommandLineUtils.checkInvalidArgs(parser, options, resetShiftByOpt, minus(allResetOffsetScenarioOpts, resetShiftByOpt)); + CommandLineUtils.checkInvalidArgs(parser, options, resetFromFileOpt, minus(allResetOffsetScenarioOpts, resetFromFileOpt)); + } + + CommandLineUtils.checkInvalidArgs(parser, options, groupOpt, minus(allGroupSelectionScopeOpts, groupOpt)); + CommandLineUtils.checkInvalidArgs(parser, options, groupOpt, minus(allConsumerGroupLevelOpts, describeOpt, deleteOpt, resetOffsetsOpt)); + CommandLineUtils.checkInvalidArgs(parser, options, topicOpt, minus(allConsumerGroupLevelOpts, deleteOpt, resetOffsetsOpt)); + } + + @SuppressWarnings("unchecked") + private static Set minus(Set set, T...toRemove) { + Set res = new HashSet<>(set); + for (T t : toRemove) + res.remove(t); + return res; + } +} diff --git a/tools/src/main/java/org/apache/kafka/tools/consumergroup/GroupState.java b/tools/src/main/java/org/apache/kafka/tools/consumergroup/GroupState.java new file mode 100644 index 0000000000000..f90257c87a17b --- /dev/null +++ b/tools/src/main/java/org/apache/kafka/tools/consumergroup/GroupState.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.kafka.tools.consumergroup; + +import org.apache.kafka.common.Node; + +class GroupState { + public final String group; + public final Node coordinator; + public final String assignmentStrategy; + public final String state; + public final int numMembers; + + public GroupState(String group, Node coordinator, String assignmentStrategy, String state, int numMembers) { + this.group = group; + this.coordinator = coordinator; + this.assignmentStrategy = assignmentStrategy; + this.state = state; + this.numMembers = numMembers; + } +} diff --git a/tools/src/main/java/org/apache/kafka/tools/consumergroup/MemberAssignmentState.java b/tools/src/main/java/org/apache/kafka/tools/consumergroup/MemberAssignmentState.java new file mode 100644 index 0000000000000..77f413d07fafa --- /dev/null +++ b/tools/src/main/java/org/apache/kafka/tools/consumergroup/MemberAssignmentState.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.kafka.tools.consumergroup; + +import org.apache.kafka.common.TopicPartition; + +import java.util.List; + +class MemberAssignmentState { + public final String group; + public final String consumerId; + public final String host; + public final String clientId; + public final String groupInstanceId; + public final int numPartitions; + public final List assignment; + + public MemberAssignmentState(String group, String consumerId, String host, String clientId, String groupInstanceId, + int numPartitions, List assignment) { + this.group = group; + this.consumerId = consumerId; + this.host = host; + this.clientId = clientId; + this.groupInstanceId = groupInstanceId; + this.numPartitions = numPartitions; + this.assignment = assignment; + } +} diff --git a/tools/src/main/java/org/apache/kafka/tools/consumergroup/PartitionAssignmentState.java b/tools/src/main/java/org/apache/kafka/tools/consumergroup/PartitionAssignmentState.java new file mode 100644 index 0000000000000..27d1f976b9aab --- /dev/null +++ b/tools/src/main/java/org/apache/kafka/tools/consumergroup/PartitionAssignmentState.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.kafka.tools.consumergroup; + +import org.apache.kafka.common.Node; + +import java.util.Optional; + +class PartitionAssignmentState { + public final String group; + public final Optional coordinator; + public final Optional topic; + public final Optional partition; + public final Optional offset; + public final Optional lag; + public final Optional consumerId; + public final Optional host; + public final Optional clientId; + public final Optional logEndOffset; + + public PartitionAssignmentState(String group, Optional coordinator, Optional topic, + Optional partition, Optional offset, Optional lag, + Optional consumerId, Optional host, Optional clientId, + Optional logEndOffset) { + this.group = group; + this.coordinator = coordinator; + this.topic = topic; + this.partition = partition; + this.offset = offset; + this.lag = lag; + this.consumerId = consumerId; + this.host = host; + this.clientId = clientId; + this.logEndOffset = logEndOffset; + } +} \ No newline at end of file From d62882ddddca1b67493a6322adfc085dded7fd7e Mon Sep 17 00:00:00 2001 From: Nikolay Date: Wed, 6 Dec 2023 22:24:02 +0300 Subject: [PATCH 2/9] Update clients/src/main/java/org/apache/kafka/common/utils/Utils.java Co-authored-by: Taras Ledkov --- clients/src/main/java/org/apache/kafka/common/utils/Utils.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/clients/src/main/java/org/apache/kafka/common/utils/Utils.java b/clients/src/main/java/org/apache/kafka/common/utils/Utils.java index 44dec563d4364..f5500aa2f2b39 100644 --- a/clients/src/main/java/org/apache/kafka/common/utils/Utils.java +++ b/clients/src/main/java/org/apache/kafka/common/utils/Utils.java @@ -599,8 +599,7 @@ public static String join(T[] strs, String separator) { * @return The string representation. */ public static String join(Collection collection) { - Objects.requireNonNull(collection); - return mkString(collection.stream(), "", "", ", "); + return join(collection, ", "); } /** From 295d45e4d94c7aaf49764d775fb34f13c10c2c75 Mon Sep 17 00:00:00 2001 From: Nikolay Date: Thu, 7 Dec 2023 12:32:49 +0300 Subject: [PATCH 3/9] Update tools/src/main/java/org/apache/kafka/tools/consumergroup/ConsumerGroupCommandOptions.java Co-authored-by: Taras Ledkov --- .../kafka/tools/consumergroup/ConsumerGroupCommandOptions.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/src/main/java/org/apache/kafka/tools/consumergroup/ConsumerGroupCommandOptions.java b/tools/src/main/java/org/apache/kafka/tools/consumergroup/ConsumerGroupCommandOptions.java index 8ab65746e7b06..6ada71a55b789 100644 --- a/tools/src/main/java/org/apache/kafka/tools/consumergroup/ConsumerGroupCommandOptions.java +++ b/tools/src/main/java/org/apache/kafka/tools/consumergroup/ConsumerGroupCommandOptions.java @@ -40,7 +40,7 @@ public class ConsumerGroupCommandOptions extends CommandDefaultOptions { public static final String LIST_DOC = "List all consumer groups."; public static final String DESCRIBE_DOC = "Describe consumer group and list offset lag (number of messages not yet processed) related to given group."; public static final String ALL_GROUPS_DOC = "Apply to all consumer groups."; - public static final String NL = System.getProperty("line.separator"); + public static final String NL = System.lineSeparator(); public static final String DELETE_DOC = "Pass in groups to delete topic partition offsets and ownership information " + "over the entire consumer group. For instance --group g1 --group g2"; public static final String TIMEOUT_MS_DOC = "The timeout that can be set for some use cases. For example, it can be used when describing the group " + From 2e81fa931cfed9138661d5603f81b0d38ba7a466 Mon Sep 17 00:00:00 2001 From: nizhikov Date: Thu, 18 Jan 2024 00:49:14 +0300 Subject: [PATCH 4/9] KAFKA-14589 Code review fixes --- .../org/apache/kafka/common/utils/Utils.java | 14 ++++++++++++ .../ConsumerGroupCommandOptions.java | 22 +++++++------------ .../PartitionAssignmentState.java | 2 +- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/clients/src/main/java/org/apache/kafka/common/utils/Utils.java b/clients/src/main/java/org/apache/kafka/common/utils/Utils.java index 8ac1ceed8503f..84aeb91df8d74 100644 --- a/clients/src/main/java/org/apache/kafka/common/utils/Utils.java +++ b/clients/src/main/java/org/apache/kafka/common/utils/Utils.java @@ -1501,6 +1501,20 @@ public static Set diff(final Supplier> constructor, final Set l return result; } + /** + * @param set Source set. + * @param toRemove Elements to remove. + * @return {@code set} copy without {@code toRemove} elements. + * @param Element type. + */ + @SuppressWarnings("unchecked") + public static Set minus(Set set, T...toRemove) { + Set res = new HashSet<>(set); + for (T t : toRemove) + res.remove(t); + return res; + } + public static Map filterMap(final Map map, final Predicate> filterPredicate) { return map.entrySet().stream().filter(filterPredicate).collect(Collectors.toMap(Entry::getKey, Entry::getValue)); } diff --git a/tools/src/main/java/org/apache/kafka/tools/consumergroup/ConsumerGroupCommandOptions.java b/tools/src/main/java/org/apache/kafka/tools/consumergroup/ConsumerGroupCommandOptions.java index 6ada71a55b789..6b0b6056d191d 100644 --- a/tools/src/main/java/org/apache/kafka/tools/consumergroup/ConsumerGroupCommandOptions.java +++ b/tools/src/main/java/org/apache/kafka/tools/consumergroup/ConsumerGroupCommandOptions.java @@ -17,7 +17,6 @@ package org.apache.kafka.tools.consumergroup; import joptsimple.OptionSpec; -import org.apache.kafka.common.utils.Utils; import org.apache.kafka.server.util.CommandDefaultOptions; import org.apache.kafka.server.util.CommandLineUtils; import org.slf4j.Logger; @@ -28,6 +27,9 @@ import java.util.List; import java.util.Set; +import static org.apache.kafka.common.utils.Utils.join; +import static org.apache.kafka.common.utils.Utils.minus; + public class ConsumerGroupCommandOptions extends CommandDefaultOptions { public static final Logger LOGGER = LoggerFactory.getLogger(ConsumerGroupCommandOptions.class); @@ -195,11 +197,11 @@ public void checkArgs() { if (options.has(describeOpt)) { if (!options.has(groupOpt) && !options.has(allGroupsOpt)) CommandLineUtils.printUsageAndExit(parser, - "Option $describeOpt takes one of these options: " + Utils.join(allGroupSelectionScopeOpts)); + "Option " + describeOpt + " takes one of these options: " + join(allGroupSelectionScopeOpts)); List> mutuallyExclusiveOpts = Arrays.asList(membersOpt, offsetsOpt, stateOpt); if (mutuallyExclusiveOpts.stream().mapToInt(o -> options.has(o) ? 1 : 0).sum() > 1) { CommandLineUtils.printUsageAndExit(parser, - "Option " + describeOpt + " takes at most one of these options: " + Utils.join(mutuallyExclusiveOpts)); + "Option " + describeOpt + " takes at most one of these options: " + join(mutuallyExclusiveOpts)); } if (options.has(stateOpt) && options.valueOf(stateOpt) != null) CommandLineUtils.printUsageAndExit(parser, @@ -212,7 +214,7 @@ public void checkArgs() { if (options.has(deleteOpt)) { if (!options.has(groupOpt) && !options.has(allGroupsOpt)) CommandLineUtils.printUsageAndExit(parser, - "Option " + deleteOpt + " takes one of these options: " + Utils.join(allGroupSelectionScopeOpts)); + "Option " + deleteOpt + " takes one of these options: " + join(allGroupSelectionScopeOpts)); if (options.has(topicOpt)) CommandLineUtils.printUsageAndExit(parser, "The consumer does not support topic-specific offset " + "deletion from a consumer group."); @@ -221,7 +223,7 @@ public void checkArgs() { if (options.has(deleteOffsetsOpt)) { if (!options.has(groupOpt) || !options.has(topicOpt)) CommandLineUtils.printUsageAndExit(parser, - "Option " + deleteOffsetsOpt + " takes the following options: " + Utils.join(allDeleteOffsetsOpts)); + "Option " + deleteOffsetsOpt + " takes the following options: " + join(allDeleteOffsetsOpts)); } if (options.has(resetOffsetsOpt)) { @@ -237,7 +239,7 @@ public void checkArgs() { if (!options.has(groupOpt) && !options.has(allGroupsOpt)) CommandLineUtils.printUsageAndExit(parser, - "Option " + resetOffsetsOpt + " takes one of these options: " + Utils.join(allGroupSelectionScopeOpts)); + "Option " + resetOffsetsOpt + " takes one of these options: " + join(allGroupSelectionScopeOpts)); CommandLineUtils.checkInvalidArgs(parser, options, resetToOffsetOpt, minus(allResetOffsetScenarioOpts, resetToOffsetOpt)); CommandLineUtils.checkInvalidArgs(parser, options, resetToDatetimeOpt, minus(allResetOffsetScenarioOpts, resetToDatetimeOpt)); CommandLineUtils.checkInvalidArgs(parser, options, resetByDurationOpt, minus(allResetOffsetScenarioOpts, resetByDurationOpt)); @@ -252,12 +254,4 @@ public void checkArgs() { CommandLineUtils.checkInvalidArgs(parser, options, groupOpt, minus(allConsumerGroupLevelOpts, describeOpt, deleteOpt, resetOffsetsOpt)); CommandLineUtils.checkInvalidArgs(parser, options, topicOpt, minus(allConsumerGroupLevelOpts, deleteOpt, resetOffsetsOpt)); } - - @SuppressWarnings("unchecked") - private static Set minus(Set set, T...toRemove) { - Set res = new HashSet<>(set); - for (T t : toRemove) - res.remove(t); - return res; - } } diff --git a/tools/src/main/java/org/apache/kafka/tools/consumergroup/PartitionAssignmentState.java b/tools/src/main/java/org/apache/kafka/tools/consumergroup/PartitionAssignmentState.java index 27d1f976b9aab..918aaf1cd9508 100644 --- a/tools/src/main/java/org/apache/kafka/tools/consumergroup/PartitionAssignmentState.java +++ b/tools/src/main/java/org/apache/kafka/tools/consumergroup/PartitionAssignmentState.java @@ -47,4 +47,4 @@ public PartitionAssignmentState(String group, Optional coordinator, Option this.clientId = clientId; this.logEndOffset = logEndOffset; } -} \ No newline at end of file +} From 70a0a4fc691c4504d94145ee0ea63b7582b20efb Mon Sep 17 00:00:00 2001 From: nizhikov Date: Thu, 18 Jan 2024 01:54:49 +0300 Subject: [PATCH 5/9] KAFKA-14589 Rename partition --- .../group}/ConsumerGroupCommandOptions.java | 2 +- .../tools/{consumergroup => consumer/group}/GroupState.java | 2 +- .../group}/MemberAssignmentState.java | 2 +- .../group}/PartitionAssignmentState.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename tools/src/main/java/org/apache/kafka/tools/{consumergroup => consumer/group}/ConsumerGroupCommandOptions.java (99%) rename tools/src/main/java/org/apache/kafka/tools/{consumergroup => consumer/group}/GroupState.java (96%) rename tools/src/main/java/org/apache/kafka/tools/{consumergroup => consumer/group}/MemberAssignmentState.java (97%) rename tools/src/main/java/org/apache/kafka/tools/{consumergroup => consumer/group}/PartitionAssignmentState.java (97%) diff --git a/tools/src/main/java/org/apache/kafka/tools/consumergroup/ConsumerGroupCommandOptions.java b/tools/src/main/java/org/apache/kafka/tools/consumer/group/ConsumerGroupCommandOptions.java similarity index 99% rename from tools/src/main/java/org/apache/kafka/tools/consumergroup/ConsumerGroupCommandOptions.java rename to tools/src/main/java/org/apache/kafka/tools/consumer/group/ConsumerGroupCommandOptions.java index 6b0b6056d191d..462b0f25a34ec 100644 --- a/tools/src/main/java/org/apache/kafka/tools/consumergroup/ConsumerGroupCommandOptions.java +++ b/tools/src/main/java/org/apache/kafka/tools/consumer/group/ConsumerGroupCommandOptions.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.kafka.tools.consumergroup; +package org.apache.kafka.tools.consumer.group; import joptsimple.OptionSpec; import org.apache.kafka.server.util.CommandDefaultOptions; diff --git a/tools/src/main/java/org/apache/kafka/tools/consumergroup/GroupState.java b/tools/src/main/java/org/apache/kafka/tools/consumer/group/GroupState.java similarity index 96% rename from tools/src/main/java/org/apache/kafka/tools/consumergroup/GroupState.java rename to tools/src/main/java/org/apache/kafka/tools/consumer/group/GroupState.java index f90257c87a17b..c160f5acadf4f 100644 --- a/tools/src/main/java/org/apache/kafka/tools/consumergroup/GroupState.java +++ b/tools/src/main/java/org/apache/kafka/tools/consumer/group/GroupState.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.kafka.tools.consumergroup; +package org.apache.kafka.tools.consumer.group; import org.apache.kafka.common.Node; diff --git a/tools/src/main/java/org/apache/kafka/tools/consumergroup/MemberAssignmentState.java b/tools/src/main/java/org/apache/kafka/tools/consumer/group/MemberAssignmentState.java similarity index 97% rename from tools/src/main/java/org/apache/kafka/tools/consumergroup/MemberAssignmentState.java rename to tools/src/main/java/org/apache/kafka/tools/consumer/group/MemberAssignmentState.java index 77f413d07fafa..040cb1c741ec0 100644 --- a/tools/src/main/java/org/apache/kafka/tools/consumergroup/MemberAssignmentState.java +++ b/tools/src/main/java/org/apache/kafka/tools/consumer/group/MemberAssignmentState.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.kafka.tools.consumergroup; +package org.apache.kafka.tools.consumer.group; import org.apache.kafka.common.TopicPartition; diff --git a/tools/src/main/java/org/apache/kafka/tools/consumergroup/PartitionAssignmentState.java b/tools/src/main/java/org/apache/kafka/tools/consumer/group/PartitionAssignmentState.java similarity index 97% rename from tools/src/main/java/org/apache/kafka/tools/consumergroup/PartitionAssignmentState.java rename to tools/src/main/java/org/apache/kafka/tools/consumer/group/PartitionAssignmentState.java index 918aaf1cd9508..eec449ed557ed 100644 --- a/tools/src/main/java/org/apache/kafka/tools/consumergroup/PartitionAssignmentState.java +++ b/tools/src/main/java/org/apache/kafka/tools/consumer/group/PartitionAssignmentState.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.kafka.tools.consumergroup; +package org.apache.kafka.tools.consumer.group; import org.apache.kafka.common.Node; From 54986b54255e0e0e316fab3f6d7bba0cf03de316 Mon Sep 17 00:00:00 2001 From: nizhikov Date: Thu, 18 Jan 2024 19:22:38 +0300 Subject: [PATCH 6/9] KAFKA-14589 Review fixes --- .../org/apache/kafka/common/utils/Utils.java | 23 ------------------- .../org/apache/kafka/tools/ToolsUtils.java | 15 ++++++++++++ .../group/ConsumerGroupCommandOptions.java | 12 +++++----- 3 files changed, 21 insertions(+), 29 deletions(-) diff --git a/clients/src/main/java/org/apache/kafka/common/utils/Utils.java b/clients/src/main/java/org/apache/kafka/common/utils/Utils.java index 84aeb91df8d74..c316b7a181662 100644 --- a/clients/src/main/java/org/apache/kafka/common/utils/Utils.java +++ b/clients/src/main/java/org/apache/kafka/common/utils/Utils.java @@ -594,15 +594,6 @@ public static String join(T[] strs, String separator) { return join(Arrays.asList(strs), separator); } - /** - * Create a string representation of a collection joined by ", ". - * @param collection The list of items - * @return The string representation. - */ - public static String join(Collection collection) { - return join(collection, ", "); - } - /** * Create a string representation of a collection joined by the given separator * @param collection The list of items @@ -1501,20 +1492,6 @@ public static Set diff(final Supplier> constructor, final Set l return result; } - /** - * @param set Source set. - * @param toRemove Elements to remove. - * @return {@code set} copy without {@code toRemove} elements. - * @param Element type. - */ - @SuppressWarnings("unchecked") - public static Set minus(Set set, T...toRemove) { - Set res = new HashSet<>(set); - for (T t : toRemove) - res.remove(t); - return res; - } - public static Map filterMap(final Map map, final Predicate> filterPredicate) { return map.entrySet().stream().filter(filterPredicate).collect(Collectors.toMap(Entry::getKey, Entry::getValue)); } diff --git a/tools/src/main/java/org/apache/kafka/tools/ToolsUtils.java b/tools/src/main/java/org/apache/kafka/tools/ToolsUtils.java index a3eb4ab4bcb6b..9c6a7a0d1ccb6 100644 --- a/tools/src/main/java/org/apache/kafka/tools/ToolsUtils.java +++ b/tools/src/main/java/org/apache/kafka/tools/ToolsUtils.java @@ -140,4 +140,19 @@ public static Set duplicates(List s) { }); return duplicates; } + + /** + * @param set Source set. + * @param toRemove Elements to remove. + * @return {@code set} copy without {@code toRemove} elements. + * @param Element type. + */ + @SuppressWarnings("unchecked") + public static Set minus(Set set, T...toRemove) { + Set res = new HashSet<>(set); + for (T t : toRemove) + res.remove(t); + return res; + } + } diff --git a/tools/src/main/java/org/apache/kafka/tools/consumer/group/ConsumerGroupCommandOptions.java b/tools/src/main/java/org/apache/kafka/tools/consumer/group/ConsumerGroupCommandOptions.java index 462b0f25a34ec..0448cc4f12e63 100644 --- a/tools/src/main/java/org/apache/kafka/tools/consumer/group/ConsumerGroupCommandOptions.java +++ b/tools/src/main/java/org/apache/kafka/tools/consumer/group/ConsumerGroupCommandOptions.java @@ -28,7 +28,7 @@ import java.util.Set; import static org.apache.kafka.common.utils.Utils.join; -import static org.apache.kafka.common.utils.Utils.minus; +import static org.apache.kafka.tools.ToolsUtils.minus; public class ConsumerGroupCommandOptions extends CommandDefaultOptions { public static final Logger LOGGER = LoggerFactory.getLogger(ConsumerGroupCommandOptions.class); @@ -197,11 +197,11 @@ public void checkArgs() { if (options.has(describeOpt)) { if (!options.has(groupOpt) && !options.has(allGroupsOpt)) CommandLineUtils.printUsageAndExit(parser, - "Option " + describeOpt + " takes one of these options: " + join(allGroupSelectionScopeOpts)); + "Option " + describeOpt + " takes one of these options: " + join(allGroupSelectionScopeOpts, ", ")); List> mutuallyExclusiveOpts = Arrays.asList(membersOpt, offsetsOpt, stateOpt); if (mutuallyExclusiveOpts.stream().mapToInt(o -> options.has(o) ? 1 : 0).sum() > 1) { CommandLineUtils.printUsageAndExit(parser, - "Option " + describeOpt + " takes at most one of these options: " + join(mutuallyExclusiveOpts)); + "Option " + describeOpt + " takes at most one of these options: " + join(mutuallyExclusiveOpts, ", ")); } if (options.has(stateOpt) && options.valueOf(stateOpt) != null) CommandLineUtils.printUsageAndExit(parser, @@ -214,7 +214,7 @@ public void checkArgs() { if (options.has(deleteOpt)) { if (!options.has(groupOpt) && !options.has(allGroupsOpt)) CommandLineUtils.printUsageAndExit(parser, - "Option " + deleteOpt + " takes one of these options: " + join(allGroupSelectionScopeOpts)); + "Option " + deleteOpt + " takes one of these options: " + join(allGroupSelectionScopeOpts, ", ")); if (options.has(topicOpt)) CommandLineUtils.printUsageAndExit(parser, "The consumer does not support topic-specific offset " + "deletion from a consumer group."); @@ -223,7 +223,7 @@ public void checkArgs() { if (options.has(deleteOffsetsOpt)) { if (!options.has(groupOpt) || !options.has(topicOpt)) CommandLineUtils.printUsageAndExit(parser, - "Option " + deleteOffsetsOpt + " takes the following options: " + join(allDeleteOffsetsOpts)); + "Option " + deleteOffsetsOpt + " takes the following options: " + join(allDeleteOffsetsOpts, ", ")); } if (options.has(resetOffsetsOpt)) { @@ -239,7 +239,7 @@ public void checkArgs() { if (!options.has(groupOpt) && !options.has(allGroupsOpt)) CommandLineUtils.printUsageAndExit(parser, - "Option " + resetOffsetsOpt + " takes one of these options: " + join(allGroupSelectionScopeOpts)); + "Option " + resetOffsetsOpt + " takes one of these options: " + join(allGroupSelectionScopeOpts, ", ")); CommandLineUtils.checkInvalidArgs(parser, options, resetToOffsetOpt, minus(allResetOffsetScenarioOpts, resetToOffsetOpt)); CommandLineUtils.checkInvalidArgs(parser, options, resetToDatetimeOpt, minus(allResetOffsetScenarioOpts, resetToDatetimeOpt)); CommandLineUtils.checkInvalidArgs(parser, options, resetByDurationOpt, minus(allResetOffsetScenarioOpts, resetByDurationOpt)); From 40e8f6d44252e89de379206053742370713b585d Mon Sep 17 00:00:00 2001 From: nizhikov Date: Thu, 18 Jan 2024 19:27:47 +0300 Subject: [PATCH 7/9] KAFKA-14589 Review fixes --- checkstyle/import-control.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/checkstyle/import-control.xml b/checkstyle/import-control.xml index 3316b593b167e..c39075677b22c 100644 --- a/checkstyle/import-control.xml +++ b/checkstyle/import-control.xml @@ -327,6 +327,10 @@ + + + + From c957587580e36237a756199fcf9fa27d6226971d Mon Sep 17 00:00:00 2001 From: "n.izhikov" Date: Mon, 22 Jan 2024 18:45:26 +0300 Subject: [PATCH 8/9] KAFKA-14589 Review fixes --- .../consumer/group/PartitionAssignmentState.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tools/src/main/java/org/apache/kafka/tools/consumer/group/PartitionAssignmentState.java b/tools/src/main/java/org/apache/kafka/tools/consumer/group/PartitionAssignmentState.java index eec449ed557ed..396032f0a0c29 100644 --- a/tools/src/main/java/org/apache/kafka/tools/consumer/group/PartitionAssignmentState.java +++ b/tools/src/main/java/org/apache/kafka/tools/consumer/group/PartitionAssignmentState.java @@ -19,23 +19,25 @@ import org.apache.kafka.common.Node; import java.util.Optional; +import java.util.OptionalInt; +import java.util.OptionalLong; class PartitionAssignmentState { public final String group; public final Optional coordinator; public final Optional topic; - public final Optional partition; - public final Optional offset; - public final Optional lag; + public final OptionalInt partition; + public final OptionalLong offset; + public final OptionalLong lag; public final Optional consumerId; public final Optional host; public final Optional clientId; - public final Optional logEndOffset; + public final OptionalLong logEndOffset; public PartitionAssignmentState(String group, Optional coordinator, Optional topic, - Optional partition, Optional offset, Optional lag, + OptionalInt partition, OptionalLong offset, OptionalLong lag, Optional consumerId, Optional host, Optional clientId, - Optional logEndOffset) { + OptionalLong logEndOffset) { this.group = group; this.coordinator = coordinator; this.topic = topic; From 82058f49783ba4734ddb45d7be41efdeec2e2a60 Mon Sep 17 00:00:00 2001 From: "n.izhikov" Date: Mon, 22 Jan 2024 21:42:49 +0300 Subject: [PATCH 9/9] KAFKA-14589 Review fixes --- .../consumer/group/ConsumerGroupCommandOptions.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tools/src/main/java/org/apache/kafka/tools/consumer/group/ConsumerGroupCommandOptions.java b/tools/src/main/java/org/apache/kafka/tools/consumer/group/ConsumerGroupCommandOptions.java index 0448cc4f12e63..045d296444dfb 100644 --- a/tools/src/main/java/org/apache/kafka/tools/consumer/group/ConsumerGroupCommandOptions.java +++ b/tools/src/main/java/org/apache/kafka/tools/consumer/group/ConsumerGroupCommandOptions.java @@ -240,14 +240,14 @@ public void checkArgs() { if (!options.has(groupOpt) && !options.has(allGroupsOpt)) CommandLineUtils.printUsageAndExit(parser, "Option " + resetOffsetsOpt + " takes one of these options: " + join(allGroupSelectionScopeOpts, ", ")); - CommandLineUtils.checkInvalidArgs(parser, options, resetToOffsetOpt, minus(allResetOffsetScenarioOpts, resetToOffsetOpt)); + CommandLineUtils.checkInvalidArgs(parser, options, resetToOffsetOpt, minus(allResetOffsetScenarioOpts, resetToOffsetOpt)); CommandLineUtils.checkInvalidArgs(parser, options, resetToDatetimeOpt, minus(allResetOffsetScenarioOpts, resetToDatetimeOpt)); CommandLineUtils.checkInvalidArgs(parser, options, resetByDurationOpt, minus(allResetOffsetScenarioOpts, resetByDurationOpt)); CommandLineUtils.checkInvalidArgs(parser, options, resetToEarliestOpt, minus(allResetOffsetScenarioOpts, resetToEarliestOpt)); - CommandLineUtils.checkInvalidArgs(parser, options, resetToLatestOpt, minus(allResetOffsetScenarioOpts, resetToLatestOpt)); - CommandLineUtils.checkInvalidArgs(parser, options, resetToCurrentOpt, minus(allResetOffsetScenarioOpts, resetToCurrentOpt)); - CommandLineUtils.checkInvalidArgs(parser, options, resetShiftByOpt, minus(allResetOffsetScenarioOpts, resetShiftByOpt)); - CommandLineUtils.checkInvalidArgs(parser, options, resetFromFileOpt, minus(allResetOffsetScenarioOpts, resetFromFileOpt)); + CommandLineUtils.checkInvalidArgs(parser, options, resetToLatestOpt, minus(allResetOffsetScenarioOpts, resetToLatestOpt)); + CommandLineUtils.checkInvalidArgs(parser, options, resetToCurrentOpt, minus(allResetOffsetScenarioOpts, resetToCurrentOpt)); + CommandLineUtils.checkInvalidArgs(parser, options, resetShiftByOpt, minus(allResetOffsetScenarioOpts, resetShiftByOpt)); + CommandLineUtils.checkInvalidArgs(parser, options, resetFromFileOpt, minus(allResetOffsetScenarioOpts, resetFromFileOpt)); } CommandLineUtils.checkInvalidArgs(parser, options, groupOpt, minus(allGroupSelectionScopeOpts, groupOpt));