This is an automated email from the ASF dual-hosted git repository. nizhikov pushed a commit to branch IGNITE-19676 in repository https://gitbox.apache.org/repos/asf/ignite.git
commit d79ad50c3aee2374220102b2582a00788f8d1f05 Author: nizhikov <nizhi...@apache.org> AuthorDate: Tue Jun 13 12:05:38 2023 +0300 IGNITE-19676 JMX Command invoker --- .../internal/commandline/ArgumentParser.java | 133 +------------- .../internal/commandline/CommandHandler.java | 105 +++++------ .../internal/commandline/CommandInvoker.java | 189 +++++++------------- .../java/org/apache/ignite/internal/IgniteEx.java | 7 + .../org/apache/ignite/internal/IgniteKernal.java | 10 ++ .../internal/management/ActivateCommand.java | 3 +- .../internal/management/DeactivateCommand.java | 3 +- .../internal/management/SetStateCommand.java | 3 +- .../ignite/internal/management/StateCommand.java | 7 +- .../management/api/AbstractCommandInvoker.java | 115 +++++++++++++ .../internal/management/api/CommandUtils.java | 131 ++++++++++++++ .../internal/management/api/LocalCommand.java | 4 +- .../api/{LocalCommand.java => Node.java} | 33 +++- .../management/api/NodeCommandInvoker.java | 124 +++++++++++++ .../management/cache/CacheListCommand.java | 2 +- .../consistency/ConsistencyRepairCommand.java | 6 +- .../internal/management/jmx/CommandMBean.java | 191 +++++++++++++++++++++ .../management/jmx/JmxComandRegistryInvoker.java | 117 +++++++++++++ .../JmxCommandRegistryInvokerPluginProvider.java | 110 ++++++++++++ .../internal/management/tx/TxInfoCommand.java | 3 +- .../apache/ignite/internal/util/IgniteUtils.java | 23 +++ 21 files changed, 994 insertions(+), 325 deletions(-) diff --git a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/ArgumentParser.java b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/ArgumentParser.java index 3a0a58ee016..20ab97269cb 100644 --- a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/ArgumentParser.java +++ b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/ArgumentParser.java @@ -19,7 +19,6 @@ 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; @@ -30,8 +29,6 @@ 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; @@ -41,13 +38,9 @@ import org.apache.ignite.internal.dto.IgniteDataTransferObject; import org.apache.ignite.internal.management.IgniteCommandRegistry; 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; @@ -65,13 +58,13 @@ import static org.apache.ignite.internal.commandline.argument.parser.CLIArgument import static org.apache.ignite.internal.management.api.CommandUtils.CMD_WORDS_DELIM; import static org.apache.ignite.internal.management.api.CommandUtils.NAME_PREFIX; import static org.apache.ignite.internal.management.api.CommandUtils.PARAM_WORDS_DELIM; +import static org.apache.ignite.internal.management.api.CommandUtils.argument; import static org.apache.ignite.internal.management.api.CommandUtils.asOptional; +import static org.apache.ignite.internal.management.api.CommandUtils.executable; 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; @@ -339,10 +332,7 @@ public class ArgumentParser { iter.remove(); } - if (!(cmd instanceof ComputeCommand) - && !(cmd instanceof LocalCommand) - && !(cmd instanceof BeforeNodeStartCommand) - && !(cmd instanceof HelpCommand)) { + if (!executable(cmd)) { throw new IllegalArgumentException( "Command " + toFormattedCommandName(cmd.getClass()) + " can't be executed" ); @@ -394,121 +384,4 @@ public class ArgumentParser { return F.t(positionalArgs, namedArgs); } - - /** - * Fill and vaildate command argument. - * - * @param argCls Argument class. - * @param positionalParamProvider Provider of positional parameters. - * @param paramProvider Provider of named parameters. - * @return Argument filled with parameters. - * @param <A> Argument type. - */ - private static <A extends IgniteDataTransferObject> A argument( - Class<A> argCls, - BiFunction<Field, Integer, Object> positionalParamProvider, - Function<Field, Object> paramProvider - ) { - try { - ArgumentState<A> arg = new ArgumentState<>(argCls); - - visitCommandParams( - argCls, - fld -> arg.accept(fld, positionalParamProvider.apply(fld, arg.nextIdx())), - fld -> arg.accept(fld, paramProvider.apply(fld)), - (argGrp, flds) -> flds.forEach(fld -> { - if (fld.isAnnotationPresent(Positional.class)) - arg.accept(fld, positionalParamProvider.apply(fld, arg.nextIdx())); - else - arg.accept(fld, paramProvider.apply(fld)); - }) - ); - - if (arg.argGrp != null && (!arg.grpOptional() && !arg.grpFldExists)) - throw new IllegalArgumentException("One of " + toFormattedNames(argCls, arg.grpdFlds) + " required"); - - return arg.res; - } - catch (InstantiationException | IllegalAccessException e) { - throw new IgniteException(e); - } - } - - /** */ - private static class ArgumentState<A extends IgniteDataTransferObject> implements BiConsumer<Field, Object> { - /** */ - final A res; - - /** */ - final ArgumentGroup argGrp; - - /** */ - boolean grpFldExists; - - /** */ - int idx; - - /** */ - final Set<String> grpdFlds; - - /** */ - public ArgumentState(Class<A> argCls) throws InstantiationException, IllegalAccessException { - res = argCls.newInstance(); - argGrp = argCls.getAnnotation(ArgumentGroup.class); - grpdFlds = argGrp == null - ? Collections.emptySet() - : new HashSet<>(Arrays.asList(argGrp.value())); - } - - /** */ - public boolean grpOptional() { - return argGrp == null || argGrp.optional(); - } - - /** */ - private int nextIdx() { - int idx0 = idx; - - idx++; - - return idx0; - } - - /** {@inheritDoc} */ - @Override public void accept(Field fld, Object val) { - boolean grpdFld = grpdFlds.contains(fld.getName()); - - if (val == null) { - if (grpdFld || fld.getAnnotation(Argument.class).optional()) - return; - - String name = fld.isAnnotationPresent(Positional.class) - ? parameterExample(fld, false) - : toFormattedFieldName(fld); - - throw new IllegalArgumentException("Argument " + name + " required."); - } - - if (grpdFld) { - if (grpFldExists && (argGrp != null && argGrp.onlyOneOf())) { - throw new IllegalArgumentException( - "Only one of " + toFormattedNames(res.getClass(), grpdFlds) + " allowed" - ); - } - - grpFldExists = true; - } - - try { - res.getClass().getMethod(fld.getName(), fld.getType()).invoke(res, val); - } - catch (NoSuchMethodException | IllegalAccessException e) { - throw new IgniteException(e); - } - catch (InvocationTargetException e) { - if (e.getTargetException() != null && e.getTargetException() instanceof RuntimeException) - throw (RuntimeException)e.getTargetException(); - } - } - } } diff --git a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/CommandHandler.java b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/CommandHandler.java index 318a4121c55..3a90fe80654 100644 --- a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/CommandHandler.java +++ b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/CommandHandler.java @@ -54,10 +54,8 @@ import org.apache.ignite.internal.management.api.CliSubcommandsWithPrefix; import org.apache.ignite.internal.management.api.Command; import org.apache.ignite.internal.management.api.CommandUtils; import org.apache.ignite.internal.management.api.CommandsRegistry; -import org.apache.ignite.internal.management.api.ComputeCommand; import org.apache.ignite.internal.management.api.EnumDescription; 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.management.cache.CacheCommand; import org.apache.ignite.internal.util.IgniteUtils; @@ -90,6 +88,7 @@ import static org.apache.ignite.internal.management.api.CommandUtils.NAME_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.cmdText; +import static org.apache.ignite.internal.management.api.CommandUtils.executable; import static org.apache.ignite.internal.management.api.CommandUtils.join; import static org.apache.ignite.internal.management.api.CommandUtils.parameterExample; import static org.apache.ignite.internal.management.api.CommandUtils.toFormattedCommandName; @@ -255,66 +254,75 @@ public class CommandHandler { commandName = toFormattedCommandName(args.root().getClass()).toUpperCase(); - CommandInvoker<A> invoker = new CommandInvoker<>(args.command(), args.commandArg(), getClientConfiguration(args)); + try (CommandInvoker<A> invoker = new CommandInvoker<>( + args.command(), + args.commandArg(), + getClientConfiguration(args) + )) { + int tryConnectMaxCount = 3; - int tryConnectMaxCount = 3; + boolean suppliedAuth = !F.isEmpty(args.userName()) && !F.isEmpty(args.password()); - boolean suppliedAuth = !F.isEmpty(args.userName()) && !F.isEmpty(args.password()); + boolean credentialsRequested = false; - boolean credentialsRequested = false; - - while (true) { - try { - if (!invoker.prepare(logger)) - return EXIT_CODE_OK; + while (true) { + try { + if (!invoker.prepare(logger::info)) + return EXIT_CODE_OK; - if (!args.autoConfirmation()) { - if (!confirm(invoker.confirmationPrompt())) { - logger.info("Operation cancelled."); + if (!args.autoConfirmation()) { + if (!confirm(invoker.confirmationPrompt())) { + logger.info("Operation cancelled."); - return EXIT_CODE_OK; + return EXIT_CODE_OK; + } } - } - logger.info("Command [" + commandName + "] started"); - logger.info("Arguments: " + argumentsToString(rawArgs)); - logger.info(U.DELIM); + logger.info("Command [" + commandName + "] started"); + logger.info("Arguments: " + argumentsToString(rawArgs)); + logger.info(U.DELIM); - if (args.command() instanceof HelpCommand) - printUsage(logger, args.root()); - else if (args.command() instanceof BeforeNodeStartCommand) - lastOperationRes = invoker.invokeBeforeNodeStart(logger); - else - lastOperationRes = invoker.invoke(logger, args.verbose()); + String deprecationMsg = args.command().deprecationMessage(args.commandArg()); - break; - } - catch (Throwable e) { - if (!isAuthError(e)) - throw e; + if (deprecationMsg != null) + logger.warning(deprecationMsg); - if (suppliedAuth) - throw new GridClientAuthenticationException("Wrong credentials."); + if (args.command() instanceof HelpCommand) + printUsage(logger, args.root()); + else if (args.command() instanceof BeforeNodeStartCommand) + lastOperationRes = invoker.invokeBeforeNodeStart(logger::info); + else + lastOperationRes = invoker.invoke(logger::info, args.verbose()); - if (tryConnectMaxCount == 0) { - throw new GridClientAuthenticationException("Maximum number of " + - "retries exceeded"); + break; } + catch (Throwable e) { + if (!isAuthError(e)) + throw e; + + if (suppliedAuth) + throw new GridClientAuthenticationException("Wrong credentials."); - logger.info(credentialsRequested ? - "Authentication error, please try again." : - "This cluster requires authentication."); + if (tryConnectMaxCount == 0) { + throw new GridClientAuthenticationException("Maximum number of " + + "retries exceeded"); + } - if (credentialsRequested) - tryConnectMaxCount--; + logger.info(credentialsRequested ? + "Authentication error, please try again." : + "This cluster requires authentication."); - invoker.clientConfiguration(getClientConfiguration( - retrieveUserName(args, invoker.clientConfiguration()), - new String(requestPasswordFromConsole("password: ")), - args - )); + if (credentialsRequested) + tryConnectMaxCount--; - credentialsRequested = true; + invoker.clientConfiguration(getClientConfiguration( + retrieveUserName(args, invoker.clientConfiguration()), + new String(requestPasswordFromConsole("password: ")), + args + )); + + credentialsRequested = true; + } } } @@ -827,10 +835,7 @@ public class CommandHandler { * @param logger Logger to print help to. */ public static void usage(Command<?, ?> cmd, List<Command<?, ?>> parents, IgniteLogger logger) { - if (cmd instanceof LocalCommand - || cmd instanceof ComputeCommand - || cmd instanceof HelpCommand - || cmd instanceof BeforeNodeStartCommand) { + if (executable(cmd)) { logger.info(""); if (cmd.getClass().isAnnotationPresent(IgniteExperimental.class)) diff --git a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/CommandInvoker.java b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/CommandInvoker.java index 06c66b3e015..e2b1d65fbd3 100644 --- a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/CommandInvoker.java +++ b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/CommandInvoker.java @@ -23,9 +23,9 @@ import java.util.Collection; import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.function.Consumer; 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; @@ -35,112 +35,40 @@ 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.AbstractCommandInvoker; 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.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; +public class CommandInvoker<A extends IgniteDataTransferObject> extends AbstractCommandInvoker<A> { /** Client configuration. */ private GridClientConfiguration clientCfg; + /** Client. */ + private GridClient client; + /** @param cmd Command to execute. */ public CommandInvoker(Command<A, ?> cmd, A arg, GridClientConfiguration clientCfg) { - this.cmd = cmd; - this.arg = arg; + super(cmd, arg); this.clientCfg = clientCfg; } - /** - * Actual command execution with verbose mode if needed. - * Implement it if your command supports verbose mode. - * - * @param log 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 log, boolean verbose) throws Exception { - try (GridClient client = startClient(clientCfg)) { - String deprecationMsg = cmd.deprecationMessage(arg); - - if (deprecationMsg != null) - log.warning(deprecationMsg); - - R res; - - if (cmd instanceof LocalCommand) - res = ((LocalCommand<A, R>)cmd).execute(client, arg, log::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, 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, log::info); - } - else - throw new IllegalArgumentException("Unknown command type: " + cmd); - - return res; - } - catch (Throwable e) { - log.error("Failed to perform operation."); - log.error(CommandLogger.errorMessage(e)); - - throw e; - } - } - /** */ - public boolean prepare(IgniteLogger log) throws Exception { + public boolean prepare(Consumer<String> printer) throws Exception { if (!(cmd instanceof PreparableCommand)) return true; - try (GridClient client = startClient(clientCfg)) { - return ((PreparableCommand<A, ?>)cmd).prepare(client, arg, log::info); - } + return ((PreparableCommand<A, ?>)cmd).prepare(client(), arg, printer); } /** @@ -151,24 +79,21 @@ public class CommandInvoker<A extends IgniteDataTransferObject> { } /** */ - public <R> R invokeBeforeNodeStart(IgniteLogger log) throws Exception { + public <R> R invokeBeforeNodeStart(Consumer<String> printer) throws Exception { try (GridClientBeforeNodeStart client = startClientBeforeNodeStart(clientCfg)) { - return ((BeforeNodeStartCommand<A, R>)cmd).execute(client, arg, log::info); + return ((BeforeNodeStartCommand<A, R>)cmd).execute(client, arg, printer); } 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); + /** {@inheritDoc} */ + @Override protected GridClient client() throws GridClientException { + if (client != null && client.connected()) + return client; + + client = GridClientFactory.start(clientCfg); // If connection is unsuccessful, fail before doing any operations: if (!client.connected()) { @@ -187,38 +112,14 @@ public class CommandInvoker<A extends IgniteDataTransferObject> { 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; + /** {@inheritDoc} */ + @Override protected Map<UUID, GridClientNode> nodes() throws GridClientException { + return client().compute().nodes().stream() + .collect(toMap(GridClientNode::nodeId, n -> n)); } - /** */ - private static GridClientNode defaultNode(GridClient client, GridClientConfiguration clientCfg) throws GridClientException { + /** {@inheritDoc} */ + @Override protected GridClientNode defaultNode() throws GridClientException { GridClientNode node; // Prefer node from connect string. @@ -241,22 +142,52 @@ public class CommandInvoker<A extends IgniteDataTransferObject> { String origAddr = addr.getHostName() + ":" + parts[1]; - node = listHosts(client).filter(tuple -> origAddr.equals(tuple.get2())).findFirst().map(IgniteBiTuple::get1).orElse(null); + node = listHosts(client()).filter(tuple -> origAddr.equals(tuple.get2())).findFirst().map(IgniteBiTuple::get1).orElse(null); if (node == null) - node = listHostsByClientNode(client).filter(tuple -> tuple.get2().size() == 1 && cfgAddr.equals(tuple.get2().get(0))). + node = listHostsByClientNode(client()).filter(tuple -> tuple.get2().size() == 1 && cfgAddr.equals(tuple.get2().get(0))). findFirst().map(IgniteBiTuple::get1).orElse(null); } else - node = listHosts(client).filter(tuple -> cfgAddr.equals(tuple.get2())).findFirst().map(IgniteBiTuple::get1).orElse(null); + node = listHosts(client()).filter(tuple -> cfgAddr.equals(tuple.get2())).findFirst().map(IgniteBiTuple::get1).orElse(null); // Otherwise choose random node. if (node == null) - node = balancedNode(client.compute()); + node = balancedNode(client().compute()); return node; } + /** + * 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; + } + /** * @param client Client. * @return List of hosts. @@ -313,4 +244,10 @@ public class CommandInvoker<A extends IgniteDataTransferObject> { public GridClientConfiguration clientConfiguration() { return clientCfg; } + + /** {@inheritDoc} */ + @Override public void close() { + if (client != null) + client.close(); + } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/IgniteEx.java b/modules/core/src/main/java/org/apache/ignite/internal/IgniteEx.java index 5d982290852..b76e916fe65 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/IgniteEx.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/IgniteEx.java @@ -26,6 +26,7 @@ import org.apache.ignite.IgniteSet; import org.apache.ignite.cluster.ClusterNode; import org.apache.ignite.configuration.CacheConfiguration; import org.apache.ignite.internal.cluster.IgniteClusterEx; +import org.apache.ignite.internal.management.IgniteCommandRegistry; import org.apache.ignite.internal.processors.cache.GridCacheUtilityKey; import org.apache.ignite.internal.processors.cache.IgniteInternalCache; import org.apache.ignite.lang.IgniteBiTuple; @@ -172,4 +173,10 @@ public interface IgniteEx extends Ignite { * @throws IgniteException If set could not be fetched or created. */ public <T> IgniteSet<T> set(String name, int cacheId, boolean collocated, boolean separated) throws IgniteException; + + /** + * @return Registry with all management commands known by node. + * @see org.apache.ignite.internal.management.api.Command + */ + public IgniteCommandRegistry commands(); } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/IgniteKernal.java b/modules/core/src/main/java/org/apache/ignite/internal/IgniteKernal.java index 39f4ad5be07..d2c11bbd73e 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/IgniteKernal.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/IgniteKernal.java @@ -94,6 +94,7 @@ import org.apache.ignite.internal.cache.query.index.IndexProcessor; import org.apache.ignite.internal.cluster.ClusterGroupAdapter; import org.apache.ignite.internal.cluster.IgniteClusterEx; import org.apache.ignite.internal.maintenance.MaintenanceProcessor; +import org.apache.ignite.internal.management.IgniteCommandRegistry; import org.apache.ignite.internal.managers.GridManager; import org.apache.ignite.internal.managers.IgniteMBeansManager; import org.apache.ignite.internal.managers.checkpoint.GridCheckpointManager; @@ -398,6 +399,10 @@ public class IgniteKernal implements IgniteEx, Externalizable { @GridToStringExclude private IgniteMBeansManager mBeansMgr; + /** Registry with all management commands known by node. */ + @GridToStringExclude + private final IgniteCommandRegistry cmdReg = new IgniteCommandRegistry(); + /** Ignite configuration instance. */ private IgniteConfiguration cfg; @@ -3093,6 +3098,11 @@ public class IgniteKernal implements IgniteEx, Externalizable { } } + /** {@inheritDoc} */ + @Override public IgniteCommandRegistry commands() { + return cmdReg; + } + /** * The {@code ctx.gateway().readLock()} is used underneath. */ diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/ActivateCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/ActivateCommand.java index 59f7f8c45ae..f45dd759cbf 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/ActivateCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/ActivateCommand.java @@ -20,6 +20,7 @@ package org.apache.ignite.internal.management; import java.util.function.Consumer; import org.apache.ignite.internal.client.GridClient; import org.apache.ignite.internal.client.GridClientClusterState; +import org.apache.ignite.internal.client.GridClientException; import org.apache.ignite.internal.management.api.LocalCommand; import org.apache.ignite.internal.management.api.NoArg; import static org.apache.ignite.cluster.ClusterState.ACTIVE; @@ -38,7 +39,7 @@ public class ActivateCommand implements LocalCommand<NoArg, NoArg> { } /** {@inheritDoc} */ - @Override public NoArg execute(GridClient cli, NoArg arg, Consumer<String> printer) throws Exception { + @Override public NoArg execute(GridClient cli, NoArg arg, Consumer<String> printer) throws GridClientException { GridClientClusterState state = cli.state(); state.state(ACTIVE, false); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/DeactivateCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/DeactivateCommand.java index 5b4b938d066..feef7f5a4e3 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/DeactivateCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/DeactivateCommand.java @@ -20,6 +20,7 @@ package org.apache.ignite.internal.management; import java.util.function.Consumer; import org.apache.ignite.internal.client.GridClient; import org.apache.ignite.internal.client.GridClientClusterState; +import org.apache.ignite.internal.client.GridClientException; import org.apache.ignite.internal.management.api.LocalCommand; import org.apache.ignite.internal.management.api.NoArg; import org.apache.ignite.internal.management.api.PreparableCommand; @@ -45,7 +46,7 @@ public class DeactivateCommand implements LocalCommand<DeactivateCommandArg, NoA } /** {@inheritDoc} */ - @Override public NoArg execute(GridClient cli, DeactivateCommandArg arg, Consumer<String> printer) throws Exception { + @Override public NoArg execute(GridClient cli, DeactivateCommandArg arg, Consumer<String> printer) throws GridClientException { GridClientClusterState state = cli.state(); state.state(INACTIVE, arg.force()); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/SetStateCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/SetStateCommand.java index e03a7e0ea98..f25ea3c0078 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/SetStateCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/SetStateCommand.java @@ -20,6 +20,7 @@ package org.apache.ignite.internal.management; import java.util.function.Consumer; import org.apache.ignite.cluster.ClusterState; import org.apache.ignite.internal.client.GridClient; +import org.apache.ignite.internal.client.GridClientException; import org.apache.ignite.internal.management.api.LocalCommand; /** */ @@ -35,7 +36,7 @@ public class SetStateCommand implements LocalCommand<SetStateCommandArg, Boolean } /** {@inheritDoc} */ - @Override public Boolean execute(GridClient cli, SetStateCommandArg arg, Consumer<String> printer) throws Exception { + @Override public Boolean execute(GridClient cli, SetStateCommandArg arg, Consumer<String> printer) throws GridClientException { ClusterState clusterState = cli.state().state(); if (clusterState == arg.state()) { diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/StateCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/StateCommand.java index 4386253affa..af4cb226fa3 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/StateCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/StateCommand.java @@ -22,6 +22,7 @@ import java.util.function.Consumer; import org.apache.ignite.cluster.ClusterState; import org.apache.ignite.internal.client.GridClient; import org.apache.ignite.internal.client.GridClientClusterState; +import org.apache.ignite.internal.client.GridClientException; import org.apache.ignite.internal.management.api.LocalCommand; import org.apache.ignite.internal.management.api.NoArg; import org.apache.ignite.internal.util.lang.GridTuple3; @@ -41,7 +42,11 @@ public class StateCommand implements LocalCommand<NoArg, GridTuple3<UUID, String } /** {@inheritDoc} */ - @Override public GridTuple3<UUID, String, ClusterState> execute(GridClient cli, NoArg arg, Consumer<String> printer) throws Exception { + @Override public GridTuple3<UUID, String, ClusterState> execute( + GridClient cli, + NoArg arg, + Consumer<String> printer + ) throws GridClientException { GridClientClusterState state = cli.state(); printer.accept("Cluster ID: " + state.id()); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/api/AbstractCommandInvoker.java b/modules/core/src/main/java/org/apache/ignite/internal/management/api/AbstractCommandInvoker.java new file mode 100644 index 00000000000..6d60e7664bf --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/api/AbstractCommandInvoker.java @@ -0,0 +1,115 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.internal.client.GridClient; +import org.apache.ignite.internal.client.GridClientCompute; +import org.apache.ignite.internal.client.GridClientException; +import org.apache.ignite.internal.client.GridClientNode; +import org.apache.ignite.internal.dto.IgniteDataTransferObject; +import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.internal.visor.VisorTaskArgument; + +import static java.util.Collections.singleton; +import static java.util.stream.Collectors.toMap; + +/** + * + */ +public abstract class AbstractCommandInvoker<A extends IgniteDataTransferObject> implements AutoCloseable { + /** Command to execute. */ + protected final Command<A, ?> cmd; + + /** Parsed argument. */ + protected final A arg; + + /** @param cmd Command to execute. */ + protected AbstractCommandInvoker(Command<A, ?> cmd, A arg) { + this.cmd = cmd; + this.arg = arg; + } + + /** + * Actual command execution with verbose mode if needed. + * Implement it if your command supports verbose mode. + * + * @param printer Result printer. + * @param verbose Use verbose mode or not + * @return Result of operation (mostly usable for tests). + * @throws GridClientException In case of error. + */ + public <R> R invoke(Consumer<String> printer, boolean verbose) throws GridClientException { + R res; + + if (cmd instanceof LocalCommand) + res = ((LocalCommand<A, R>)cmd).execute(client(), arg, printer); + 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, arg); + + if (cmdNodes == null) + cmdNodes = singleton(defaultNode().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, printer); + } + else + throw new IllegalArgumentException("Unknown command type: " + cmd); + + return res; + } + + /** + * Method to create thin client for communication with cluster. + * + * @return Grid thin client instance which is already connected to cluster. + * @throws GridClientException If error occur. + */ + protected abstract GridClient client() throws GridClientException; + + /** @return Cluster nodes. */ + protected abstract Map<UUID, GridClientNode> nodes() throws GridClientException; + + /** @return Default node to execute commands. */ + protected abstract GridClientNode defaultNode() throws GridClientException; +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/api/CommandUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/management/api/CommandUtils.java index 0c42c174667..3be3e9ad672 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/api/CommandUtils.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/api/CommandUtils.java @@ -19,6 +19,7 @@ package org.apache.ignite.internal.management.api; import java.lang.reflect.Array; import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -31,7 +32,9 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.BiConsumer; +import java.util.function.BiFunction; import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; import org.apache.ignite.IgniteException; @@ -411,6 +414,17 @@ public class CommandUtils { .collect(Collectors.toList()); } + /** + * @param cmd Command. + * @return {@code True} if command can be executed, {@code false} otherwise. + */ + public static boolean executable(Command<?, ?> cmd) { + return cmd instanceof LocalCommand + || cmd instanceof ComputeCommand + || cmd instanceof HelpCommand + || cmd instanceof BeforeNodeStartCommand; + } + /** * Join input parameters with specified {@code delimeter} between them. * @@ -555,4 +569,121 @@ public class CommandUtils { throw new NumberFormatException("Can't parse number '" + val + "', expected type: " + expectedType.getName()); } } + + /** + * Fill and vaildate command argument. + * + * @param argCls Argument class. + * @param positionalParamProvider Provider of positional parameters. + * @param paramProvider Provider of named parameters. + * @return Argument filled with parameters. + * @param <A> Argument type. + */ + public static <A extends IgniteDataTransferObject> A argument( + Class<A> argCls, + BiFunction<Field, Integer, Object> positionalParamProvider, + Function<Field, Object> paramProvider + ) { + try { + ArgumentState<A> arg = new ArgumentState<>(argCls); + + visitCommandParams( + argCls, + fld -> arg.accept(fld, positionalParamProvider.apply(fld, arg.nextIdx())), + fld -> arg.accept(fld, paramProvider.apply(fld)), + (argGrp, flds) -> flds.forEach(fld -> { + if (fld.isAnnotationPresent(Positional.class)) + arg.accept(fld, positionalParamProvider.apply(fld, arg.nextIdx())); + else + arg.accept(fld, paramProvider.apply(fld)); + }) + ); + + if (arg.argGrp != null && (!arg.grpOptional() && !arg.grpFldExists)) + throw new IllegalArgumentException("One of " + toFormattedNames(argCls, arg.grpdFlds) + " required"); + + return arg.res; + } + catch (InstantiationException | IllegalAccessException e) { + throw new IgniteException(e); + } + } + + /** */ + private static class ArgumentState<A extends IgniteDataTransferObject> implements BiConsumer<Field, Object> { + /** */ + final A res; + + /** */ + final ArgumentGroup argGrp; + + /** */ + boolean grpFldExists; + + /** */ + int idx; + + /** */ + final Set<String> grpdFlds; + + /** */ + public ArgumentState(Class<A> argCls) throws InstantiationException, IllegalAccessException { + res = argCls.newInstance(); + argGrp = argCls.getAnnotation(ArgumentGroup.class); + grpdFlds = argGrp == null + ? Collections.emptySet() + : new HashSet<>(Arrays.asList(argGrp.value())); + } + + /** */ + public boolean grpOptional() { + return argGrp == null || argGrp.optional(); + } + + /** */ + private int nextIdx() { + int idx0 = idx; + + idx++; + + return idx0; + } + + /** {@inheritDoc} */ + @Override public void accept(Field fld, Object val) { + boolean grpdFld = grpdFlds.contains(fld.getName()); + + if (val == null) { + if (grpdFld || fld.getAnnotation(Argument.class).optional()) + return; + + String name = fld.isAnnotationPresent(Positional.class) + ? parameterExample(fld, false) + : toFormattedFieldName(fld); + + throw new IllegalArgumentException("Argument " + name + " required."); + } + + if (grpdFld) { + if (grpFldExists && (argGrp != null && argGrp.onlyOneOf())) { + throw new IllegalArgumentException( + "Only one of " + toFormattedNames(res.getClass(), grpdFlds) + " allowed" + ); + } + + grpFldExists = true; + } + + try { + res.getClass().getMethod(fld.getName(), fld.getType()).invoke(res, val); + } + catch (NoSuchMethodException | IllegalAccessException e) { + throw new IgniteException(e); + } + catch (InvocationTargetException e) { + if (e.getTargetException() != null && e.getTargetException() instanceof RuntimeException) + throw (RuntimeException)e.getTargetException(); + } + } + } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/api/LocalCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/api/LocalCommand.java index fc32e932479..044423e7792 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/api/LocalCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/api/LocalCommand.java @@ -19,6 +19,7 @@ package org.apache.ignite.internal.management.api; import java.util.function.Consumer; import org.apache.ignite.internal.client.GridClient; +import org.apache.ignite.internal.client.GridClientException; import org.apache.ignite.internal.dto.IgniteDataTransferObject; /** @@ -30,6 +31,7 @@ public interface LocalCommand<A extends IgniteDataTransferObject, R> extends Com * @param arg Command argument. * @param printer Results printer. * @return Command result. + * @throws GridClientException In case of error. */ - public R execute(GridClient cli, A arg, Consumer<String> printer) throws Exception; + public R execute(GridClient cli, A arg, Consumer<String> printer) throws GridClientException; } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/api/LocalCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/api/Node.java similarity index 63% copy from modules/core/src/main/java/org/apache/ignite/internal/management/api/LocalCommand.java copy to modules/core/src/main/java/org/apache/ignite/internal/management/api/Node.java index fc32e932479..90838c533c4 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/api/LocalCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/api/Node.java @@ -17,19 +17,34 @@ package org.apache.ignite.internal.management.api; -import java.util.function.Consumer; -import org.apache.ignite.internal.client.GridClient; +import java.util.Map; +import java.util.UUID; +import org.apache.ignite.cluster.ClusterNode; import org.apache.ignite.internal.dto.IgniteDataTransferObject; /** - * Command that must be executed directly using {@link GridClient} instance. + * Cluster node representation required for management API. + * + * @see ComputeCommand#nodes(Map, IgniteDataTransferObject) */ -public interface LocalCommand<A extends IgniteDataTransferObject, R> extends Command<A, R> { +public interface Node { + /** + * @see ClusterNode#id() + */ + public UUID id(); + + /** + * @see ClusterNode#consistentId() + */ + public Object consistentId(); + + /** + * @see ClusterNode#order() + */ + public long order(); + /** - * @param cli Grid client instance. - * @param arg Command argument. - * @param printer Results printer. - * @return Command result. + * @see ClusterNode#isClient() */ - public R execute(GridClient cli, A arg, Consumer<String> printer) throws Exception; + public boolean isClient(); } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/api/NodeCommandInvoker.java b/modules/core/src/main/java/org/apache/ignite/internal/management/api/NodeCommandInvoker.java new file mode 100644 index 00000000000..a3117017159 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/api/NodeCommandInvoker.java @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.net.InetSocketAddress; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.internal.client.GridClient; +import org.apache.ignite.internal.client.GridClientCacheMode; +import org.apache.ignite.internal.client.GridClientException; +import org.apache.ignite.internal.client.GridClientNode; +import org.apache.ignite.internal.client.GridClientNodeMetrics; +import org.apache.ignite.internal.client.GridClientProtocol; +import org.apache.ignite.internal.dto.IgniteDataTransferObject; +import org.apache.ignite.internal.util.typedef.internal.U; +import org.jetbrains.annotations.Nullable; + +/** */ +public class NodeCommandInvoker<A extends IgniteDataTransferObject> extends AbstractCommandInvoker<A> { + /** */ + private final IgniteEx ignite; + + /** + * @param cmd Command to execute. + * @param arg Argument. + * @param ignite Ignite node. + */ + public NodeCommandInvoker(Command<A, ?> cmd, A arg, IgniteEx ignite) { + super(cmd, arg); + this.ignite = ignite; + } + + /** {@inheritDoc} */ + @Override protected GridClient client() throws GridClientException { + return null; + } + + /** {@inheritDoc} */ + @Override protected Map<UUID, GridClientNode> nodes() throws GridClientException { + return ignite.cluster().nodes().stream().map(n -> new GridClientNode() { + @Override public UUID nodeId() { + return n.id(); + } + + @Override public Object consistentId() { + return n.consistentId(); + } + + @Override public boolean connectable() { + return true; + } + + @Override public long order() { + return n.order(); + } + + @Override public boolean isClient() { + return n.isClient(); + } + + @Override public List<String> tcpAddresses() { + return U.arrayList(n.addresses()); + } + + @Override public List<String> tcpHostNames() { + return U.arrayList(n.hostNames()); + } + + @Override public int tcpPort() { + return -1; + } + + @Override public Map<String, Object> attributes() { + return n.attributes(); + } + + @Override public <T> @Nullable T attribute(String name) { + return n.attribute(name); + } + + @Override public GridClientNodeMetrics metrics() { + throw new UnsupportedOperationException(); + } + + @Override public Map<String, GridClientCacheMode> caches() { + throw new UnsupportedOperationException(); + } + + @Override public Collection<InetSocketAddress> availableAddresses(GridClientProtocol proto, boolean filterResolved) { + throw new UnsupportedOperationException(); + } + }).collect(Collectors.toMap(n -> n.nodeId(), Function.identity())); + } + + /** {@inheritDoc} */ + @Override protected GridClientNode defaultNode() throws GridClientException { + return null; + } + + /** {@inheritDoc} */ + @Override public void close() throws Exception { + // No-op. + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/cache/CacheListCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/cache/CacheListCommand.java index fd27d101e15..c0f34c60d62 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/cache/CacheListCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/cache/CacheListCommand.java @@ -72,7 +72,7 @@ public class CacheListCommand implements LocalCommand<CacheListCommandArg, Visor GridClient cli, CacheListCommandArg arg, Consumer<String> printer - ) throws Exception { + ) throws GridClientException { VisorViewCacheCmd cmd = arg.groups() ? GROUPS : (arg.seq() ? SEQ : CACHES); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/consistency/ConsistencyRepairCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/consistency/ConsistencyRepairCommand.java index 797ca54ce0b..096d4e4dfd3 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/consistency/ConsistencyRepairCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/consistency/ConsistencyRepairCommand.java @@ -22,7 +22,7 @@ import java.util.Collections; import java.util.Set; import java.util.function.Consumer; import java.util.stream.Collectors; -import org.apache.ignite.IgniteCheckedException; +import org.apache.ignite.IgniteException; import org.apache.ignite.internal.client.GridClient; import org.apache.ignite.internal.client.GridClientException; import org.apache.ignite.internal.client.GridClientNode; @@ -52,7 +52,7 @@ public class ConsistencyRepairCommand implements LocalCommand<ConsistencyRepairC GridClient cli, ConsistencyRepairCommandArg arg, Consumer<String> printer - ) throws Exception { + ) throws GridClientException, IgniteException { StringBuilder sb = new StringBuilder(); boolean failed = false; @@ -74,7 +74,7 @@ public class ConsistencyRepairCommand implements LocalCommand<ConsistencyRepairC String res = sb.toString(); if (failed) - throw new IgniteCheckedException(res); + throw new IgniteException(res); printer.accept(res); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/jmx/CommandMBean.java b/modules/core/src/main/java/org/apache/ignite/internal/management/jmx/CommandMBean.java new file mode 100644 index 00000000000..9f9c0bce821 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/jmx/CommandMBean.java @@ -0,0 +1,191 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.jmx; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Function; +import javax.management.Attribute; +import javax.management.AttributeList; +import javax.management.AttributeNotFoundException; +import javax.management.DynamicMBean; +import javax.management.InvalidAttributeValueException; +import javax.management.MBeanException; +import javax.management.MBeanInfo; +import javax.management.MBeanOperationInfo; +import javax.management.MBeanParameterInfo; +import javax.management.ReflectionException; +import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.internal.client.GridClientException; +import org.apache.ignite.internal.dto.IgniteDataTransferObject; +import org.apache.ignite.internal.management.api.Argument; +import org.apache.ignite.internal.management.api.Command; +import org.apache.ignite.internal.management.api.CommandUtils; +import org.apache.ignite.internal.management.api.EnumDescription; +import org.apache.ignite.internal.management.api.NodeCommandInvoker; + +import static javax.management.MBeanOperationInfo.ACTION; +import static org.apache.ignite.internal.management.api.CommandUtils.visitCommandParams; + +/** */ +public class CommandMBean<A extends IgniteDataTransferObject> implements DynamicMBean { + /** */ + public static final String METHOD = "invoke"; + + /** */ + private final IgniteEx ignite; + + /** */ + private final Command<A, ?> cmd; + + /** */ + public CommandMBean(IgniteEx ignite, Command<A, ?> cmd) { + this.ignite = ignite; + this.cmd = cmd; + } + + /** {@inheritDoc} */ + @Override public Object getAttribute( + String attribute + ) throws AttributeNotFoundException, MBeanException, ReflectionException { + throw new UnsupportedOperationException("getAttribute"); + } + + /** {@inheritDoc} */ + @Override public void setAttribute( + Attribute attribute + ) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException { + throw new UnsupportedOperationException("setAttribute"); + } + + /** {@inheritDoc} */ + @Override public Object invoke( + String actionName, + Object[] params, + String[] signature + ) throws MBeanException, ReflectionException { + if (!METHOD.equals(actionName)) + throw new UnsupportedOperationException(actionName); + + try { + StringBuilder resStr = new StringBuilder(); + + Consumer<String> printer = str -> resStr.append(str).append('\n'); + + new NodeCommandInvoker<>( + cmd, + new ParamsToArgument(params).argument(), + ignite + ).invoke(printer, false); + + return resStr.toString(); + } + catch (GridClientException e) { + throw new RuntimeException(e); + } + } + + /** {@inheritDoc} */ + @Override public MBeanInfo getMBeanInfo() { + return new MBeanInfo( + CommandMBean.class.getName(), + cmd.getClass().getSimpleName(), + null, + null, + new MBeanOperationInfo[]{ + new MBeanOperationInfo(METHOD, cmd.description(), argumentsDescription(), String.class.getName(), ACTION) + }, + null + ); + } + + /** */ + public MBeanParameterInfo[] argumentsDescription() { + List<MBeanParameterInfo> args = new ArrayList<>(); + + Consumer<Field> fldCnsmr = fld -> { + String descStr; + + if (!fld.isAnnotationPresent(EnumDescription.class)) + descStr = fld.getAnnotation(Argument.class).description(); + else { + EnumDescription enumDesc = fld.getAnnotation(EnumDescription.class); + + String[] names = enumDesc.names(); + String[] descriptions = enumDesc.descriptions(); + + StringBuilder bldr = new StringBuilder(); + + for (int i = 0; i < names.length; i++) { + if (i != 0) + bldr.append('\n'); + bldr.append(names[i]).append(" - ").append(descriptions[i]); + } + + descStr = bldr.toString(); + } + + args.add(new MBeanParameterInfo(fld.getName(), String.class.getName(), descStr)); + }; + + visitCommandParams(cmd.argClass(), fldCnsmr, fldCnsmr, (optional, flds) -> flds.forEach(fldCnsmr)); + + return args.toArray(new MBeanParameterInfo[args.size()]); + } + + /** {@inheritDoc} */ + @Override public AttributeList getAttributes(String[] attributes) { + throw new UnsupportedOperationException("getAttributes"); + } + + /** {@inheritDoc} */ + @Override public AttributeList setAttributes(AttributeList attributes) { + throw new UnsupportedOperationException("setAttributes"); + } + + /** */ + private class ParamsToArgument implements Function<Field, Object> { + /** */ + private int cntr; + + /** */ + private final Object[] vals; + + /** */ + private ParamsToArgument(Object[] vals) { + this.vals = vals; + } + + /** */ + public A argument() { + // This will map vals to argument fields. + return CommandUtils.argument(cmd.argClass(), (fld, pos) -> apply(fld), this); + } + + /** {@inheritDoc} */ + @Override public Object apply(Field field) { + String val = (String)vals[cntr]; + + cntr++; + + return val != null ? CommandUtils.parseVal(val, field.getType()) : null; + } + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/jmx/JmxComandRegistryInvoker.java b/modules/core/src/main/java/org/apache/ignite/internal/management/jmx/JmxComandRegistryInvoker.java new file mode 100644 index 00000000000..886581e15d6 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/jmx/JmxComandRegistryInvoker.java @@ -0,0 +1,117 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.jmx; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import javax.management.JMException; +import javax.management.MBeanServer; +import javax.management.ObjectName; +import org.apache.ignite.IgniteLogger; +import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.internal.management.api.Command; +import org.apache.ignite.internal.management.api.CommandsRegistry; +import org.apache.ignite.internal.util.typedef.internal.U; +import org.apache.ignite.plugin.IgnitePlugin; +import org.apache.ignite.plugin.PluginContext; + +import static org.apache.ignite.internal.management.api.CommandUtils.executable; +import static org.apache.ignite.internal.util.IgniteUtils.makeMBeanName; + +/** */ +public class JmxComandRegistryInvoker implements IgnitePlugin { + /** */ + private PluginContext ctx; + + /** */ + private IgniteLogger log; + + /** */ + private IgniteEx grid; + + /** */ + private final List<ObjectName> mBeans = new ArrayList<>(); + + /** */ + public void context(PluginContext ctx) { + this.ctx = ctx; + this.grid = (IgniteEx)ctx.grid(); + this.log = ctx.log(JmxComandRegistryInvoker.class); + } + + /** */ + public void onIgniteStart() { + if (U.IGNITE_MBEANS_DISABLED) { + log.info("Plugin disabled, IGNITE_MBEANS_DISABLED = true."); + + return; + } + + grid.commands().commands().forEachRemaining(cmd -> register(cmd.getKey(), new LinkedList<>(), cmd.getValue())); + } + + /** */ + public void register(String name, List<String> parents, Command<?, ?> cmd) { + if (cmd instanceof CommandsRegistry) { + parents.add(name); + + ((CommandsRegistry<?, ?>)cmd).commands() + .forEachRemaining(cmd0 -> register(cmd0.getKey(), parents, cmd0.getValue())); + + parents.remove(parents.size() - 1); + + if (!executable(cmd)) + return; + } + + try { + ObjectName mbean = U.registerMBean( + grid.configuration().getMBeanServer(), + makeMBeanName(grid.context().igniteInstanceName(), "management", parents, name), + new CommandMBean<>(grid, cmd), + CommandMBean.class + ); + + mBeans.add(mbean); + + if (log.isDebugEnabled()) + log.debug("Command JMX bean created. " + mbean); + } + catch (JMException e) { + log.error("MBean for command '" + name + "' can't be created.", e); + } + } + + /** */ + public void onIgniteStop() { + MBeanServer jmx = grid.configuration().getMBeanServer(); + + for (ObjectName mBean : mBeans) { + try { + jmx.unregisterMBean(mBean); + + if (log.isDebugEnabled()) + log.debug("Unregistered command MBean: " + mBean); + } + catch (JMException e) { + log.error("Failed to unregister command MBean: " + mBean, e); + } + } + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/jmx/JmxCommandRegistryInvokerPluginProvider.java b/modules/core/src/main/java/org/apache/ignite/internal/management/jmx/JmxCommandRegistryInvokerPluginProvider.java new file mode 100644 index 00000000000..fa9b732af8d --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/jmx/JmxCommandRegistryInvokerPluginProvider.java @@ -0,0 +1,110 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.jmx; + +import java.io.Serializable; +import java.util.UUID; +import org.apache.ignite.IgniteCheckedException; +import org.apache.ignite.cluster.ClusterNode; +import org.apache.ignite.internal.IgniteVersionUtils; +import org.apache.ignite.plugin.CachePluginContext; +import org.apache.ignite.plugin.CachePluginProvider; +import org.apache.ignite.plugin.ExtensionRegistry; +import org.apache.ignite.plugin.IgnitePlugin; +import org.apache.ignite.plugin.PluginContext; +import org.apache.ignite.plugin.PluginProvider; +import org.apache.ignite.plugin.PluginValidationException; +import org.jetbrains.annotations.Nullable; + +/** + * + */ +public class JmxCommandRegistryInvokerPluginProvider implements PluginProvider { + /** */ + private JmxComandRegistryInvoker jmxInvoker = new JmxComandRegistryInvoker(); + + /** {@inheritDoc} */ + @Override public String name() { + return "JMX invoker of CommandRegistry"; + } + + /** {@inheritDoc} */ + @Override public String version() { + return IgniteVersionUtils.VER_STR; + } + + /** {@inheritDoc} */ + @Override public String copyright() { + return IgniteVersionUtils.COPYRIGHT; + } + + /** {@inheritDoc} */ + @Override public IgnitePlugin plugin() { + return jmxInvoker; + } + + /** {@inheritDoc} */ + @Override public void initExtensions(PluginContext ctx, ExtensionRegistry registry) throws IgniteCheckedException { + jmxInvoker.context(ctx); + } + + /** {@inheritDoc} */ + @Override public CachePluginProvider createCacheProvider(CachePluginContext ctx) { + return null; + } + + /** {@inheritDoc} */ + @Override public void onIgniteStart() throws IgniteCheckedException { + jmxInvoker.onIgniteStart(); + } + + /** {@inheritDoc} */ + @Override public void onIgniteStop(boolean cancel) { + jmxInvoker.onIgniteStop(); + } + + /** {@inheritDoc} */ + @Override public @Nullable Serializable provideDiscoveryData(UUID nodeId) { + return null; + } + + /** {@inheritDoc} */ + @Override public void receiveDiscoveryData(UUID nodeId, Serializable data) { + // No-op. + } + + /** {@inheritDoc} */ + @Override public void validateNewNode(ClusterNode node) throws PluginValidationException { + // No-op. + } + + /** {@inheritDoc} */ + @Override public Object createComponent(PluginContext ctx, Class cls) { + return null; + } + + /** {@inheritDoc} */ + @Override public void start(PluginContext ctx) throws IgniteCheckedException { + // No-op. + } + + /** {@inheritDoc} */ + @Override public void stop(boolean cancel) throws IgniteCheckedException { + // No-op. + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/tx/TxInfoCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/tx/TxInfoCommand.java index 71087fe2f3e..c62ebfdbe5d 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/tx/TxInfoCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/tx/TxInfoCommand.java @@ -25,6 +25,7 @@ import java.util.Set; import java.util.function.Consumer; import org.apache.ignite.cluster.ClusterNode; import org.apache.ignite.internal.client.GridClient; +import org.apache.ignite.internal.client.GridClientException; import org.apache.ignite.internal.client.GridClientNode; import org.apache.ignite.internal.management.api.LocalCommand; import org.apache.ignite.internal.management.tx.TxCommand.AbstractTxCommandArg; @@ -60,7 +61,7 @@ public class TxInfoCommand implements LocalCommand<AbstractTxCommandArg, Map<Clu GridClient cli, AbstractTxCommandArg arg0, Consumer<String> printer - ) throws Exception { + ) throws GridClientException { TxInfoCommandArg arg = (TxInfoCommandArg)arg0; Optional<GridClientNode> node = cli.compute().nodes().stream() diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java index 3bd673b00da..fa65abdadd9 100755 --- a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java @@ -5053,6 +5053,26 @@ public abstract class IgniteUtils { */ public static ObjectName makeMBeanName(@Nullable String igniteInstanceName, @Nullable String grp, String name) throws MalformedObjectNameException { + return makeMBeanName(igniteInstanceName, grp, Collections.emptyList(), name); + } + + /** + * Constructs JMX object name with given properties. + * Map with ordered {@code groups} used for proper object name construction. + * + * @param igniteInstanceName Ignite instance name. + * @param grp Name of the group. + * @param grps Names of extended groups. + * @param name Name of mbean. + * @return JMX object name. + * @throws MalformedObjectNameException Thrown in case of any errors. + */ + public static ObjectName makeMBeanName( + @Nullable String igniteInstanceName, + @Nullable String grp, + List<String> grps, + String name + ) throws MalformedObjectNameException { SB sb = new SB(JMX_DOMAIN + ':'); appendClassLoaderHash(sb); @@ -5065,6 +5085,9 @@ public abstract class IgniteUtils { if (grp != null) sb.a("group=").a(escapeObjectNameValue(grp)).a(','); + for (int i = 0; i < grps.size(); i++) + sb.a("group").a(i).a("=").a(grps.get(i)).a(','); + sb.a("name=").a(escapeObjectNameValue(name)); return new ObjectName(sb.toString());