timoninmaxim commented on code in PR #10675:
URL: https://github.com/apache/ignite/pull/10675#discussion_r1221733917


##########
modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/CommandInvoker.java:
##########
@@ -0,0 +1,323 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.commandline;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.internal.client.GridClient;
+import org.apache.ignite.internal.client.GridClientBeforeNodeStart;
+import org.apache.ignite.internal.client.GridClientCompute;
+import org.apache.ignite.internal.client.GridClientConfiguration;
+import org.apache.ignite.internal.client.GridClientDisconnectedException;
+import org.apache.ignite.internal.client.GridClientException;
+import org.apache.ignite.internal.client.GridClientFactory;
+import org.apache.ignite.internal.client.GridClientNode;
+import org.apache.ignite.internal.dto.IgniteDataTransferObject;
+import org.apache.ignite.internal.management.api.BeforeNodeStartCommand;
+import org.apache.ignite.internal.management.api.Command;
+import org.apache.ignite.internal.management.api.ComputeCommand;
+import org.apache.ignite.internal.management.api.LocalCommand;
+import org.apache.ignite.internal.management.api.PreparableCommand;
+import org.apache.ignite.internal.util.IgniteUtils;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.internal.util.typedef.T3;
+import org.apache.ignite.internal.visor.VisorTaskArgument;
+import org.apache.ignite.lang.IgniteBiTuple;
+
+import static java.util.Collections.singleton;
+import static java.util.stream.Collectors.toMap;
+import static org.apache.ignite.internal.commandline.CommandHandler.DFLT_HOST;
+
+/**
+ * Adapter of new management API command for legacy {@code control.sh} 
execution flow.
+ */
+public class CommandInvoker<A extends IgniteDataTransferObject> {
+    /** Command to execute. */
+    private final Command<A, ?> cmd;
+
+    /** Parsed argument. */
+    private final A arg;
+
+    /** Client configuration. */
+    private GridClientConfiguration clientCfg;
+
+    /** @param cmd Command to execute. */
+    public CommandInvoker(Command<A, ?> cmd, A arg, GridClientConfiguration 
clientCfg) {
+        this.cmd = cmd;
+        this.arg = arg;
+        this.clientCfg = clientCfg;
+    }
+
+    /**
+     * Actual command execution with verbose mode if needed.
+     * Implement it if your command supports verbose mode.
+     *
+     * @param logger Logger to use.
+     * @param verbose Use verbose mode or not
+     * @return Result of operation (mostly usable for tests).
+     * @throws Exception If error occur.
+     */
+    public <R> R invoke(IgniteLogger logger, boolean verbose) throws Exception 
{

Review Comment:
   Abbreviation should be used: logger -> log.



##########
modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/CommandInvoker.java:
##########
@@ -0,0 +1,323 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.commandline;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.internal.client.GridClient;
+import org.apache.ignite.internal.client.GridClientBeforeNodeStart;
+import org.apache.ignite.internal.client.GridClientCompute;
+import org.apache.ignite.internal.client.GridClientConfiguration;
+import org.apache.ignite.internal.client.GridClientDisconnectedException;
+import org.apache.ignite.internal.client.GridClientException;
+import org.apache.ignite.internal.client.GridClientFactory;
+import org.apache.ignite.internal.client.GridClientNode;
+import org.apache.ignite.internal.dto.IgniteDataTransferObject;
+import org.apache.ignite.internal.management.api.BeforeNodeStartCommand;
+import org.apache.ignite.internal.management.api.Command;
+import org.apache.ignite.internal.management.api.ComputeCommand;
+import org.apache.ignite.internal.management.api.LocalCommand;
+import org.apache.ignite.internal.management.api.PreparableCommand;
+import org.apache.ignite.internal.util.IgniteUtils;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.internal.util.typedef.T3;
+import org.apache.ignite.internal.visor.VisorTaskArgument;
+import org.apache.ignite.lang.IgniteBiTuple;
+
+import static java.util.Collections.singleton;
+import static java.util.stream.Collectors.toMap;
+import static org.apache.ignite.internal.commandline.CommandHandler.DFLT_HOST;
+
+/**
+ * Adapter of new management API command for legacy {@code control.sh} 
execution flow.
+ */
+public class CommandInvoker<A extends IgniteDataTransferObject> {
+    /** Command to execute. */
+    private final Command<A, ?> cmd;
+
+    /** Parsed argument. */
+    private final A arg;
+
+    /** Client configuration. */
+    private GridClientConfiguration clientCfg;
+
+    /** @param cmd Command to execute. */
+    public CommandInvoker(Command<A, ?> cmd, A arg, GridClientConfiguration 
clientCfg) {
+        this.cmd = cmd;
+        this.arg = arg;
+        this.clientCfg = clientCfg;
+    }
+
+    /**
+     * Actual command execution with verbose mode if needed.
+     * Implement it if your command supports verbose mode.
+     *
+     * @param logger Logger to use.
+     * @param verbose Use verbose mode or not
+     * @return Result of operation (mostly usable for tests).
+     * @throws Exception If error occur.
+     */
+    public <R> R invoke(IgniteLogger logger, boolean verbose) throws Exception 
{
+        try (GridClient client = startClient(clientCfg)) {
+            String deprecationMsg = cmd.deprecationMessage(arg);
+
+            if (deprecationMsg != null)
+                logger.warning(deprecationMsg);
+
+            R res;
+
+            if (cmd instanceof LocalCommand)
+                res = ((LocalCommand<A, R>)cmd).execute(client, arg, 
logger::info);
+            else if (cmd instanceof ComputeCommand) {
+                GridClientCompute compute = client.compute();
+
+                Map<UUID, GridClientNode> nodes = compute.nodes().stream()
+                    .collect(toMap(GridClientNode::nodeId, n -> n));
+
+                ComputeCommand<A, R> cmd = (ComputeCommand<A, R>)this.cmd;
+
+                Collection<UUID> cmdNodes = cmd.nodes(
+                    nodes.values()
+                        .stream()
+                        .collect(toMap(GridClientNode::nodeId, n -> new 
T3<>(n.isClient(), n.consistentId(), n.order()))),
+                    arg
+                );
+
+                if (cmdNodes == null)
+                    cmdNodes = singleton(defaultNode(client, 
clientCfg).nodeId());
+
+                for (UUID id : cmdNodes) {
+                    if (!nodes.containsKey(id))
+                        throw new IllegalArgumentException("Node with id=" + 
id + " not found.");
+                }
+
+                Collection<GridClientNode> connectable = F.viewReadOnly(
+                    cmdNodes,
+                    nodes::get,
+                    id -> nodes.get(id).connectable()
+                );
+
+                if (!F.isEmpty(connectable))
+                    compute = compute.projection(connectable);
+
+                res = compute.execute(cmd.taskClass().getName(), new 
VisorTaskArgument<>(cmdNodes, arg, false));
+
+                cmd.printResult(arg, res, logger::info);
+            }
+            else
+                throw new IllegalArgumentException("Unknown command type: " + 
cmd);
+
+            return res;
+        }
+        catch (Throwable e) {
+            logger.error("Failed to perform operation.");
+            logger.error(CommandLogger.errorMessage(e));
+
+            throw e;
+        }
+    }
+
+    /** */
+    public boolean prepare(IgniteLogger logger) throws Exception {
+        if (!(cmd instanceof PreparableCommand))
+            return true;
+
+        try (GridClient client = startClient(clientCfg)) {
+            return ((PreparableCommand<A, ?>)cmd).prepare(client, arg, 
logger::info);
+        }
+    }
+
+    /**
+     * @return Message text to show user for. If null it means that 
confirmantion is not needed.
+     * @throws Exception If error occur.

Review Comment:
   No Exception throws actually



##########
modules/core/src/main/java/org/apache/ignite/internal/management/api/Command.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.ignite.internal.management.api;
+
+import org.apache.ignite.internal.dto.IgniteDataTransferObject;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Management command interface.<p>
+ * Implementations represent single action to manage Ignite cluster.<p>
+ *
+ * Name of the command that is expected from caller derived from actual 
command class name.<br>
+ * <ul>
+ *     <li><b>Name format:</b> All words divided by capital letters except 
"Command" suffix will form hierarchical command name.</li>
+ *     <li><b>Example:</b> {@code MyUsefullCommand} is name of command so 
{@code control.sh --my-usefull param1 param2}
+ *     expected from user.</li>
+ * </ul>
+ *
+ * Other protocols must expose command similarly. Rest API must expect {@code 
/api-root/my-usefull?param1=value1&param2=value2} URI.
+ *
+ * @param <A> Argument type.
+ * @param <R> Result type.
+ */
+public interface Command<A extends IgniteDataTransferObject, R> {
+    /** */
+    public static String CMD_NAME_POSTFIX = "Command";

Review Comment:
   Can be package-private final.



##########
modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/ArgumentParser.java:
##########
@@ -0,0 +1,508 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.ignite.internal.commandline;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.IgniteSystemProperties;
+import org.apache.ignite.internal.client.GridClientConfiguration;
+import org.apache.ignite.internal.commandline.argument.parser.CLIArgument;
+import 
org.apache.ignite.internal.commandline.argument.parser.CLIArgumentParser;
+import org.apache.ignite.internal.dto.IgniteDataTransferObject;
+import org.apache.ignite.internal.management.api.Argument;
+import org.apache.ignite.internal.management.api.ArgumentGroup;
+import org.apache.ignite.internal.management.api.BeforeNodeStartCommand;
+import org.apache.ignite.internal.management.api.CliSubcommandsWithPrefix;
+import org.apache.ignite.internal.management.api.Command;
+import org.apache.ignite.internal.management.api.CommandsRegistry;
+import org.apache.ignite.internal.management.api.ComputeCommand;
+import org.apache.ignite.internal.management.api.HelpCommand;
+import org.apache.ignite.internal.management.api.LocalCommand;
+import org.apache.ignite.internal.management.api.Positional;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.lang.IgniteBiTuple;
+import org.apache.ignite.lang.IgniteExperimental;
+import org.apache.ignite.ssl.SslContextFactory;
+
+import static 
org.apache.ignite.IgniteSystemProperties.IGNITE_ENABLE_EXPERIMENTAL_COMMAND;
+import static 
org.apache.ignite.internal.client.GridClientConfiguration.DFLT_PING_INTERVAL;
+import static 
org.apache.ignite.internal.client.GridClientConfiguration.DFLT_PING_TIMEOUT;
+import static org.apache.ignite.internal.commandline.CommandHandler.DFLT_HOST;
+import static org.apache.ignite.internal.commandline.CommandHandler.DFLT_PORT;
+import static 
org.apache.ignite.internal.commandline.CommandHandler.UTILITY_NAME;
+import static 
org.apache.ignite.internal.commandline.argument.parser.CLIArgument.optionalArg;
+import static 
org.apache.ignite.internal.management.api.CommandUtils.CMD_WORDS_DELIM;
+import static 
org.apache.ignite.internal.management.api.CommandUtils.PARAMETER_PREFIX;
+import static 
org.apache.ignite.internal.management.api.CommandUtils.PARAM_WORDS_DELIM;
+import static 
org.apache.ignite.internal.management.api.CommandUtils.asOptional;
+import static 
org.apache.ignite.internal.management.api.CommandUtils.fromFormattedCommandName;
+import static org.apache.ignite.internal.management.api.CommandUtils.isBoolean;
+import static 
org.apache.ignite.internal.management.api.CommandUtils.parameterExample;
+import static 
org.apache.ignite.internal.management.api.CommandUtils.toFormattedCommandName;
+import static 
org.apache.ignite.internal.management.api.CommandUtils.toFormattedFieldName;
+import static 
org.apache.ignite.internal.management.api.CommandUtils.toFormattedNames;
+import static 
org.apache.ignite.internal.management.api.CommandUtils.visitCommandParams;
+import static org.apache.ignite.ssl.SslContextFactory.DFLT_SSL_PROTOCOL;
+
+/**
+ * Argument parser.
+ * Also would parse high-level command and delegate parsing for its argument 
to the command.
+ */
+public class ArgumentParser {
+    /** */
+    private final IgniteLogger logger;

Review Comment:
   logger -> log



##########
modules/core/src/main/java/org/apache/ignite/internal/management/api/ComputeCommand.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.ignite.internal.management.api;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.UUID;
+import java.util.function.Consumer;
+import org.apache.ignite.compute.ComputeTask;
+import org.apache.ignite.internal.dto.IgniteDataTransferObject;
+import org.apache.ignite.internal.util.typedef.T3;
+import org.apache.ignite.internal.visor.VisorTaskArgument;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Command that executed with some compute task.
+ */
+public interface ComputeCommand<A extends IgniteDataTransferObject, R> extends 
Command<A, R> {
+    /** @return Task class. */
+    public Class<? extends ComputeTask<VisorTaskArgument<A>, R>> taskClass();
+
+    /**
+     * Prints command result to the user.
+     * @param arg Argument.
+     * @param res Result.
+     * @param printer Implementation specific printer.
+     */
+    public default void printResult(A arg, R res, Consumer<String> printer) {
+        // No-op.
+    }
+
+    /**
+     * @param nodes Live nodes. Key is node ID, Boolean is client flag, Object 
is consistent id, Long is node order.
+     * @param arg Argument.
+     * @return nodes to execute command on, {@code null} means default node 
must be used.
+     */
+    public default @Nullable Collection<UUID> nodes(Map<UUID, T3<Boolean, 
Object, Long>> nodes, A arg) {

Review Comment:
   T3 -> GridClientNode?



##########
modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/CommandInvoker.java:
##########
@@ -0,0 +1,323 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.commandline;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.internal.client.GridClient;
+import org.apache.ignite.internal.client.GridClientBeforeNodeStart;
+import org.apache.ignite.internal.client.GridClientCompute;
+import org.apache.ignite.internal.client.GridClientConfiguration;
+import org.apache.ignite.internal.client.GridClientDisconnectedException;
+import org.apache.ignite.internal.client.GridClientException;
+import org.apache.ignite.internal.client.GridClientFactory;
+import org.apache.ignite.internal.client.GridClientNode;
+import org.apache.ignite.internal.dto.IgniteDataTransferObject;
+import org.apache.ignite.internal.management.api.BeforeNodeStartCommand;
+import org.apache.ignite.internal.management.api.Command;
+import org.apache.ignite.internal.management.api.ComputeCommand;
+import org.apache.ignite.internal.management.api.LocalCommand;
+import org.apache.ignite.internal.management.api.PreparableCommand;
+import org.apache.ignite.internal.util.IgniteUtils;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.internal.util.typedef.T3;
+import org.apache.ignite.internal.visor.VisorTaskArgument;
+import org.apache.ignite.lang.IgniteBiTuple;
+
+import static java.util.Collections.singleton;
+import static java.util.stream.Collectors.toMap;
+import static org.apache.ignite.internal.commandline.CommandHandler.DFLT_HOST;
+
+/**
+ * Adapter of new management API command for legacy {@code control.sh} 
execution flow.
+ */
+public class CommandInvoker<A extends IgniteDataTransferObject> {
+    /** Command to execute. */
+    private final Command<A, ?> cmd;
+
+    /** Parsed argument. */
+    private final A arg;
+
+    /** Client configuration. */
+    private GridClientConfiguration clientCfg;
+
+    /** @param cmd Command to execute. */
+    public CommandInvoker(Command<A, ?> cmd, A arg, GridClientConfiguration 
clientCfg) {
+        this.cmd = cmd;
+        this.arg = arg;
+        this.clientCfg = clientCfg;
+    }
+
+    /**
+     * Actual command execution with verbose mode if needed.
+     * Implement it if your command supports verbose mode.
+     *
+     * @param logger Logger to use.
+     * @param verbose Use verbose mode or not
+     * @return Result of operation (mostly usable for tests).
+     * @throws Exception If error occur.
+     */
+    public <R> R invoke(IgniteLogger logger, boolean verbose) throws Exception 
{
+        try (GridClient client = startClient(clientCfg)) {
+            String deprecationMsg = cmd.deprecationMessage(arg);
+
+            if (deprecationMsg != null)
+                logger.warning(deprecationMsg);
+
+            R res;
+
+            if (cmd instanceof LocalCommand)
+                res = ((LocalCommand<A, R>)cmd).execute(client, arg, 
logger::info);
+            else if (cmd instanceof ComputeCommand) {
+                GridClientCompute compute = client.compute();
+
+                Map<UUID, GridClientNode> nodes = compute.nodes().stream()
+                    .collect(toMap(GridClientNode::nodeId, n -> n));
+
+                ComputeCommand<A, R> cmd = (ComputeCommand<A, R>)this.cmd;
+
+                Collection<UUID> cmdNodes = cmd.nodes(
+                    nodes.values()
+                        .stream()
+                        .collect(toMap(GridClientNode::nodeId, n -> new 
T3<>(n.isClient(), n.consistentId(), n.order()))),
+                    arg
+                );
+
+                if (cmdNodes == null)
+                    cmdNodes = singleton(defaultNode(client, 
clientCfg).nodeId());
+
+                for (UUID id : cmdNodes) {
+                    if (!nodes.containsKey(id))
+                        throw new IllegalArgumentException("Node with id=" + 
id + " not found.");
+                }
+
+                Collection<GridClientNode> connectable = F.viewReadOnly(
+                    cmdNodes,
+                    nodes::get,
+                    id -> nodes.get(id).connectable()
+                );
+
+                if (!F.isEmpty(connectable))
+                    compute = compute.projection(connectable);
+
+                res = compute.execute(cmd.taskClass().getName(), new 
VisorTaskArgument<>(cmdNodes, arg, false));
+
+                cmd.printResult(arg, res, logger::info);
+            }
+            else
+                throw new IllegalArgumentException("Unknown command type: " + 
cmd);
+
+            return res;
+        }
+        catch (Throwable e) {
+            logger.error("Failed to perform operation.");
+            logger.error(CommandLogger.errorMessage(e));
+
+            throw e;
+        }
+    }
+
+    /** */
+    public boolean prepare(IgniteLogger logger) throws Exception {
+        if (!(cmd instanceof PreparableCommand))
+            return true;
+
+        try (GridClient client = startClient(clientCfg)) {
+            return ((PreparableCommand<A, ?>)cmd).prepare(client, arg, 
logger::info);
+        }
+    }
+
+    /**
+     * @return Message text to show user for. If null it means that 
confirmantion is not needed.
+     * @throws Exception If error occur.
+     */
+    public String confirmationPrompt() {
+        return cmd.confirmationPrompt(arg);
+    }
+
+    /** */
+    public <R> R invokeBeforeNodeStart(IgniteLogger logger) throws Exception {
+        try (GridClientBeforeNodeStart client = 
startClientBeforeNodeStart(clientCfg)) {
+            return ((BeforeNodeStartCommand<A, R>)cmd).execute(client, arg, 
logger::info);
+        }
+        catch (GridClientDisconnectedException e) {
+            throw new GridClientException(e.getCause());
+        }
+    }
+
+    /**
+     * Method to create thin client for communication with cluster.
+     *
+     * @param clientCfg Thin client configuration.
+     * @return Grid thin client instance which is already connected to cluster.
+     * @throws Exception If error occur.
+     */
+    private static GridClient startClient(GridClientConfiguration clientCfg) 
throws Exception {
+        GridClient client = GridClientFactory.start(clientCfg);
+
+        // If connection is unsuccessful, fail before doing any operations:
+        if (!client.connected()) {
+            GridClientException lastErr = client.checkLastError();
+
+            try {
+                client.close();
+            }
+            catch (Throwable e) {
+                lastErr.addSuppressed(e);
+            }
+
+            throw lastErr;
+        }
+
+        return client;
+    }
+
+    /**
+     * Method to create thin client for communication with node before it 
starts.
+     * If node has already started, there will be an error.
+     *
+     * @param clientCfg Thin client configuration.
+     * @return Grid thin client instance which is already connected to node 
before it starts.
+     * @throws Exception If error occur.
+     */
+    private static GridClientBeforeNodeStart startClientBeforeNodeStart(
+        GridClientConfiguration clientCfg
+    ) throws Exception {
+        GridClientBeforeNodeStart client = 
GridClientFactory.startBeforeNodeStart(clientCfg);
+
+        // If connection is unsuccessful, fail before doing any operations:
+        if (!client.connected()) {
+            GridClientException lastErr = client.checkLastError();
+
+            try {
+                client.close();
+            }
+            catch (Throwable e) {
+                lastErr.addSuppressed(e);
+            }
+
+            throw lastErr;
+        }
+
+        return client;
+    }
+
+    /** */
+    public static GridClientNode defaultNode(GridClient client, 
GridClientConfiguration clientCfg) throws GridClientException {

Review Comment:
   private. Let's document what is a default node (as JMX/OpenApi) should 
re-implement this logic.



##########
modules/core/src/main/java/org/apache/ignite/internal/management/api/ComputeCommand.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.ignite.internal.management.api;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.UUID;
+import java.util.function.Consumer;
+import org.apache.ignite.compute.ComputeTask;
+import org.apache.ignite.internal.dto.IgniteDataTransferObject;
+import org.apache.ignite.internal.util.typedef.T3;
+import org.apache.ignite.internal.visor.VisorTaskArgument;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Command that executed with some compute task.
+ */
+public interface ComputeCommand<A extends IgniteDataTransferObject, R> extends 
Command<A, R> {
+    /** @return Task class. */
+    public Class<? extends ComputeTask<VisorTaskArgument<A>, R>> taskClass();
+
+    /**
+     * Prints command result to the user.
+     * @param arg Argument.
+     * @param res Result.
+     * @param printer Implementation specific printer.
+     */
+    public default void printResult(A arg, R res, Consumer<String> printer) {
+        // No-op.
+    }
+
+    /**
+     * @param nodes Live nodes. Key is node ID, Boolean is client flag, Object 
is consistent id, Long is node order.
+     * @param arg Argument.
+     * @return nodes to execute command on, {@code null} means default node 
must be used.

Review Comment:
   Let's describe what does default mean, local / random node?



##########
modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/ArgumentParser.java:
##########
@@ -0,0 +1,508 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.ignite.internal.commandline;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.IgniteSystemProperties;
+import org.apache.ignite.internal.client.GridClientConfiguration;
+import org.apache.ignite.internal.commandline.argument.parser.CLIArgument;
+import 
org.apache.ignite.internal.commandline.argument.parser.CLIArgumentParser;
+import org.apache.ignite.internal.dto.IgniteDataTransferObject;
+import org.apache.ignite.internal.management.api.Argument;
+import org.apache.ignite.internal.management.api.ArgumentGroup;
+import org.apache.ignite.internal.management.api.BeforeNodeStartCommand;
+import org.apache.ignite.internal.management.api.CliSubcommandsWithPrefix;
+import org.apache.ignite.internal.management.api.Command;
+import org.apache.ignite.internal.management.api.CommandsRegistry;
+import org.apache.ignite.internal.management.api.ComputeCommand;
+import org.apache.ignite.internal.management.api.HelpCommand;
+import org.apache.ignite.internal.management.api.LocalCommand;
+import org.apache.ignite.internal.management.api.Positional;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.lang.IgniteBiTuple;
+import org.apache.ignite.lang.IgniteExperimental;
+import org.apache.ignite.ssl.SslContextFactory;
+
+import static 
org.apache.ignite.IgniteSystemProperties.IGNITE_ENABLE_EXPERIMENTAL_COMMAND;
+import static 
org.apache.ignite.internal.client.GridClientConfiguration.DFLT_PING_INTERVAL;
+import static 
org.apache.ignite.internal.client.GridClientConfiguration.DFLT_PING_TIMEOUT;
+import static org.apache.ignite.internal.commandline.CommandHandler.DFLT_HOST;
+import static org.apache.ignite.internal.commandline.CommandHandler.DFLT_PORT;
+import static 
org.apache.ignite.internal.commandline.CommandHandler.UTILITY_NAME;
+import static 
org.apache.ignite.internal.commandline.argument.parser.CLIArgument.optionalArg;
+import static 
org.apache.ignite.internal.management.api.CommandUtils.CMD_WORDS_DELIM;
+import static 
org.apache.ignite.internal.management.api.CommandUtils.PARAMETER_PREFIX;
+import static 
org.apache.ignite.internal.management.api.CommandUtils.PARAM_WORDS_DELIM;
+import static 
org.apache.ignite.internal.management.api.CommandUtils.asOptional;
+import static 
org.apache.ignite.internal.management.api.CommandUtils.fromFormattedCommandName;
+import static org.apache.ignite.internal.management.api.CommandUtils.isBoolean;
+import static 
org.apache.ignite.internal.management.api.CommandUtils.parameterExample;
+import static 
org.apache.ignite.internal.management.api.CommandUtils.toFormattedCommandName;
+import static 
org.apache.ignite.internal.management.api.CommandUtils.toFormattedFieldName;
+import static 
org.apache.ignite.internal.management.api.CommandUtils.toFormattedNames;
+import static 
org.apache.ignite.internal.management.api.CommandUtils.visitCommandParams;
+import static org.apache.ignite.ssl.SslContextFactory.DFLT_SSL_PROTOCOL;
+
+/**
+ * Argument parser.
+ * Also would parse high-level command and delegate parsing for its argument 
to the command.
+ */
+public class ArgumentParser {
+    /** */
+    private final IgniteLogger logger;
+
+    /** */
+    private final Map<String, Command<?, ?>> cmds;
+
+    /** */
+    static final String CMD_HOST = "--host";
+
+    /** */
+    static final String CMD_PORT = "--port";
+
+    /** */
+    static final String CMD_PASSWORD = "--password";
+
+    /** */
+    static final String CMD_USER = "--user";
+
+    /** Option is used for auto confirmation. */
+    public static final String CMD_AUTO_CONFIRMATION = "--yes";
+
+    /** Ping interval for grid client. See {@link 
GridClientConfiguration#getPingInterval()}. */
+    static final String CMD_PING_INTERVAL = "--ping-interval";
+
+    /** Ping timeout for grid client. See {@link 
GridClientConfiguration#getPingTimeout()}. */
+    static final String CMD_PING_TIMEOUT = "--ping-timeout";
+
+    /** Verbose mode. */
+    public static final String CMD_VERBOSE = "--verbose";
+
+    // SSL configuration section
+
+    /** */
+    static final String CMD_SSL_PROTOCOL = "--ssl-protocol";
+
+    /** */
+    static final String CMD_SSL_KEY_ALGORITHM = "--ssl-key-algorithm";
+
+    /** */
+    static final String CMD_SSL_CIPHER_SUITES = "--ssl-cipher-suites";
+
+    /** */
+    static final String CMD_KEYSTORE = "--keystore";
+
+    /** */
+    static final String CMD_KEYSTORE_PASSWORD = "--keystore-password";
+
+    /** */
+    static final String CMD_KEYSTORE_TYPE = "--keystore-type";
+
+    /** */
+    static final String CMD_TRUSTSTORE = "--truststore";
+
+    /** */
+    static final String CMD_TRUSTSTORE_PASSWORD = "--truststore-password";
+
+    /** */
+    static final String CMD_TRUSTSTORE_TYPE = "--truststore-type";
+
+    /** */
+    static final String CMD_ENABLE_EXPERIMENTAL = "--enable-experimental";
+
+    /** */
+    static final String CMD_SSL_FACTORY = "--ssl-factory";
+
+    /** Set of sensitive arguments */
+    private static final Set<String> SENSITIVE_ARGUMENTS = new HashSet<>();
+
+    /** */
+    private static final BiConsumer<String, Integer> PORT_VALIDATOR = (name, 
val) -> {
+        if (val <= 0 || val > 65535)
+            throw new IllegalArgumentException("Invalid value for " + name + 
": " + val);
+    };
+
+    /** */
+    private static final BiConsumer<String, Long> POSITIVE_LONG = (name, val) 
-> {
+        if (val <= 0)
+            throw new IllegalArgumentException("Invalid value for " + name + 
": " + val);
+    };
+
+    /** */
+    private final List<CLIArgument<?>> common = new ArrayList<>();
+
+    static {
+        SENSITIVE_ARGUMENTS.add(CMD_PASSWORD);
+        SENSITIVE_ARGUMENTS.add(CMD_KEYSTORE_PASSWORD);
+        SENSITIVE_ARGUMENTS.add(CMD_TRUSTSTORE_PASSWORD);
+    }
+
+    /**
+     * @param arg To check.
+     * @return True if provided argument is among sensitive one and not should 
be displayed.
+     */
+    public static boolean isSensitiveArgument(String arg) {
+        return SENSITIVE_ARGUMENTS.contains(arg);
+    }
+
+    /**
+     * @param logger Logger.
+     * @param cmds Supported commands.
+     */
+    public ArgumentParser(IgniteLogger logger, Map<String, Command<?, ?>> 
cmds) {

Review Comment:
   logger -> log



##########
modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/CommandInvoker.java:
##########
@@ -0,0 +1,323 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.commandline;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.internal.client.GridClient;
+import org.apache.ignite.internal.client.GridClientBeforeNodeStart;
+import org.apache.ignite.internal.client.GridClientCompute;
+import org.apache.ignite.internal.client.GridClientConfiguration;
+import org.apache.ignite.internal.client.GridClientDisconnectedException;
+import org.apache.ignite.internal.client.GridClientException;
+import org.apache.ignite.internal.client.GridClientFactory;
+import org.apache.ignite.internal.client.GridClientNode;
+import org.apache.ignite.internal.dto.IgniteDataTransferObject;
+import org.apache.ignite.internal.management.api.BeforeNodeStartCommand;
+import org.apache.ignite.internal.management.api.Command;
+import org.apache.ignite.internal.management.api.ComputeCommand;
+import org.apache.ignite.internal.management.api.LocalCommand;
+import org.apache.ignite.internal.management.api.PreparableCommand;
+import org.apache.ignite.internal.util.IgniteUtils;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.internal.util.typedef.T3;
+import org.apache.ignite.internal.visor.VisorTaskArgument;
+import org.apache.ignite.lang.IgniteBiTuple;
+
+import static java.util.Collections.singleton;
+import static java.util.stream.Collectors.toMap;
+import static org.apache.ignite.internal.commandline.CommandHandler.DFLT_HOST;
+
+/**
+ * Adapter of new management API command for legacy {@code control.sh} 
execution flow.
+ */
+public class CommandInvoker<A extends IgniteDataTransferObject> {
+    /** Command to execute. */
+    private final Command<A, ?> cmd;
+
+    /** Parsed argument. */
+    private final A arg;
+
+    /** Client configuration. */
+    private GridClientConfiguration clientCfg;
+
+    /** @param cmd Command to execute. */
+    public CommandInvoker(Command<A, ?> cmd, A arg, GridClientConfiguration 
clientCfg) {
+        this.cmd = cmd;
+        this.arg = arg;
+        this.clientCfg = clientCfg;
+    }
+
+    /**
+     * Actual command execution with verbose mode if needed.
+     * Implement it if your command supports verbose mode.
+     *
+     * @param logger Logger to use.
+     * @param verbose Use verbose mode or not
+     * @return Result of operation (mostly usable for tests).
+     * @throws Exception If error occur.
+     */
+    public <R> R invoke(IgniteLogger logger, boolean verbose) throws Exception 
{
+        try (GridClient client = startClient(clientCfg)) {
+            String deprecationMsg = cmd.deprecationMessage(arg);
+
+            if (deprecationMsg != null)
+                logger.warning(deprecationMsg);
+
+            R res;
+
+            if (cmd instanceof LocalCommand)
+                res = ((LocalCommand<A, R>)cmd).execute(client, arg, 
logger::info);
+            else if (cmd instanceof ComputeCommand) {
+                GridClientCompute compute = client.compute();
+
+                Map<UUID, GridClientNode> nodes = compute.nodes().stream()
+                    .collect(toMap(GridClientNode::nodeId, n -> n));
+
+                ComputeCommand<A, R> cmd = (ComputeCommand<A, R>)this.cmd;
+
+                Collection<UUID> cmdNodes = cmd.nodes(
+                    nodes.values()
+                        .stream()
+                        .collect(toMap(GridClientNode::nodeId, n -> new 
T3<>(n.isClient(), n.consistentId(), n.order()))),
+                    arg
+                );
+
+                if (cmdNodes == null)
+                    cmdNodes = singleton(defaultNode(client, 
clientCfg).nodeId());
+
+                for (UUID id : cmdNodes) {
+                    if (!nodes.containsKey(id))
+                        throw new IllegalArgumentException("Node with id=" + 
id + " not found.");
+                }
+
+                Collection<GridClientNode> connectable = F.viewReadOnly(
+                    cmdNodes,
+                    nodes::get,
+                    id -> nodes.get(id).connectable()
+                );
+
+                if (!F.isEmpty(connectable))
+                    compute = compute.projection(connectable);
+
+                res = compute.execute(cmd.taskClass().getName(), new 
VisorTaskArgument<>(cmdNodes, arg, false));
+
+                cmd.printResult(arg, res, logger::info);
+            }
+            else
+                throw new IllegalArgumentException("Unknown command type: " + 
cmd);
+
+            return res;
+        }
+        catch (Throwable e) {
+            logger.error("Failed to perform operation.");
+            logger.error(CommandLogger.errorMessage(e));
+
+            throw e;
+        }
+    }
+
+    /** */
+    public boolean prepare(IgniteLogger logger) throws Exception {

Review Comment:
   logger -> log



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


Reply via email to