blob: ba4bb926417014d16abe771d3389158647466048 [file] [log] [blame]
/*
* 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.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.IgniteException;
import org.apache.ignite.IgniteLogger;
import org.apache.ignite.ShutdownPolicy;
import org.apache.ignite.cluster.ClusterState;
import org.apache.ignite.internal.dto.IgniteDataTransferObject;
import org.apache.ignite.internal.management.ChangeTagCommand;
import org.apache.ignite.internal.management.DeactivateCommand;
import org.apache.ignite.internal.management.IgniteCommandRegistry;
import org.apache.ignite.internal.management.SetStateCommand;
import org.apache.ignite.internal.management.SetStateCommandArg;
import org.apache.ignite.internal.management.ShutdownPolicyCommand;
import org.apache.ignite.internal.management.ShutdownPolicyCommandArg;
import org.apache.ignite.internal.management.SystemViewCommand;
import org.apache.ignite.internal.management.WarmUpCommand;
import org.apache.ignite.internal.management.api.Argument;
import org.apache.ignite.internal.management.api.Command;
import org.apache.ignite.internal.management.api.CommandsRegistry;
import org.apache.ignite.internal.management.api.EnumDescription;
import org.apache.ignite.internal.management.api.Positional;
import org.apache.ignite.internal.management.baseline.AbstractBaselineCommand;
import org.apache.ignite.internal.management.baseline.BaselineAutoAdjustCommand;
import org.apache.ignite.internal.management.baseline.BaselineVersionCommand;
import org.apache.ignite.internal.management.cache.CacheClearCommand;
import org.apache.ignite.internal.management.cache.CacheClearCommandArg;
import org.apache.ignite.internal.management.cache.CacheCommand;
import org.apache.ignite.internal.management.cache.CacheDestroyCommand;
import org.apache.ignite.internal.management.cache.CacheDestroyCommandArg;
import org.apache.ignite.internal.management.cache.CacheFindGarbageCommandArg;
import org.apache.ignite.internal.management.cache.CacheScheduleIndexesRebuildCommandArg;
import org.apache.ignite.internal.management.cache.CacheValidateIndexesCommandArg;
import org.apache.ignite.internal.management.cdc.CdcCommand;
import org.apache.ignite.internal.management.cdc.CdcDeleteLostSegmentLinksCommand;
import org.apache.ignite.internal.management.consistency.ConsistencyCommand;
import org.apache.ignite.internal.management.defragmentation.DefragmentationCommand;
import org.apache.ignite.internal.management.encryption.EncryptionChangeCacheKeyCommand;
import org.apache.ignite.internal.management.encryption.EncryptionChangeMasterKeyCommand;
import org.apache.ignite.internal.management.encryption.EncryptionCommand;
import org.apache.ignite.internal.management.kill.KillCommand;
import org.apache.ignite.internal.management.meta.MetaCommand;
import org.apache.ignite.internal.management.meta.MetaRemoveCommand;
import org.apache.ignite.internal.management.meta.MetaUpdateCommand;
import org.apache.ignite.internal.management.metric.MetricCommand;
import org.apache.ignite.internal.management.performancestatistics.PerformanceStatisticsCommand;
import org.apache.ignite.internal.management.property.PropertyCommand;
import org.apache.ignite.internal.management.snapshot.SnapshotCommand;
import org.apache.ignite.internal.management.snapshot.SnapshotRestoreCommand;
import org.apache.ignite.internal.management.tx.TxCommand;
import org.apache.ignite.internal.management.tx.TxCommandArg;
import org.apache.ignite.internal.management.tx.TxSortOrder;
import org.apache.ignite.internal.management.wal.WalCommand;
import org.apache.ignite.internal.management.wal.WalDeleteCommandArg;
import org.apache.ignite.internal.management.wal.WalPrintCommand;
import org.apache.ignite.internal.management.wal.WalPrintCommand.WalPrintCommandArg;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.T2;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.testframework.GridTestUtils;
import org.apache.ignite.testframework.junits.SystemPropertiesRule;
import org.apache.ignite.testframework.junits.WithSystemProperty;
import org.jetbrains.annotations.Nullable;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import static java.util.Arrays.asList;
import static java.util.Arrays.stream;
import static java.util.Collections.emptySet;
import static java.util.Collections.singletonList;
import static org.apache.ignite.IgniteSystemProperties.IGNITE_ENABLE_EXPERIMENTAL_COMMAND;
import static org.apache.ignite.internal.QueryMXBeanImpl.EXPECTED_GLOBAL_QRY_ID_FORMAT;
import static org.apache.ignite.internal.commandline.ArgumentParser.CMD_VERBOSE;
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.management.api.CommandUtils.cmdText;
import static org.apache.ignite.internal.management.api.CommandUtils.executable;
import static org.apache.ignite.internal.management.api.CommandUtils.visitCommandParams;
import static org.apache.ignite.testframework.GridTestUtils.assertThrows;
import static org.apache.ignite.util.GridCommandHandlerIndexingCheckSizeTest.CACHE;
import static org.apache.ignite.util.SystemViewCommandTest.NODE_ID;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* Tests Command Handler parsing arguments.
*/
@WithSystemProperty(key = IGNITE_ENABLE_EXPERIMENTAL_COMMAND, value = "true")
public class CommandHandlerParsingTest {
/** */
@ClassRule public static final TestRule classRule = new SystemPropertiesRule();
/** */
private static final String INVALID_REGEX = "[]";
/** */
public static final String WAL_PRINT = "print";
/** */
public static final String WAL_DELETE = "delete";
/** */
public static final String CHECK_FIRST = "--check-first";
/** */
public static final String CHECK_THROUGH = "--check-through";
/** */
public static final String VALIDATE_INDEXES = "validate_indexes";
/** */
public static final String WAL = "--wal";
/** */
public static final String SHUTDOWN_POLICY = "--shutdown-policy";
/** */
@Rule public final TestRule methodRule = new SystemPropertiesRule();
/**
* validate_indexes command arguments parsing and validation
*/
@Test
public void testValidateIndexArguments() {
//happy case for all parameters
try {
int expectedCheckFirst = 10;
int expectedCheckThrough = 11;
UUID nodeId = UUID.randomUUID();
ConnectionAndSslParameters args = parseArgs(asList(
CACHE,
VALIDATE_INDEXES,
"cache1,cache2",
nodeId.toString(),
CHECK_FIRST.toString(),
Integer.toString(expectedCheckFirst),
CHECK_THROUGH.toString(),
Integer.toString(expectedCheckThrough)
));
CacheValidateIndexesCommandArg arg = (CacheValidateIndexesCommandArg)args.commandArg();
assertEquals("nodeId parameter unexpected value", nodeId, arg.nodeIds()[0]);
assertEquals("checkFirst parameter unexpected value", expectedCheckFirst, arg.checkFirst());
assertEquals("checkThrough parameter unexpected value", expectedCheckThrough, arg.checkThrough());
}
catch (IllegalArgumentException e) {
fail("Unexpected exception: " + e);
}
try {
int expectedParam = 11;
UUID nodeId = UUID.randomUUID();
ConnectionAndSslParameters args = parseArgs(asList(
CACHE,
VALIDATE_INDEXES,
nodeId.toString(),
CHECK_THROUGH.toString(),
Integer.toString(expectedParam)
));
CacheValidateIndexesCommandArg arg = (CacheValidateIndexesCommandArg)args.commandArg();
assertNull("caches weren't specified, null value expected", arg.caches());
assertEquals("nodeId parameter unexpected value", nodeId, arg.nodeIds()[0]);
assertEquals("checkFirst parameter unexpected value", -1, arg.checkFirst());
assertEquals("checkThrough parameter unexpected value", expectedParam, arg.checkThrough());
}
catch (IllegalArgumentException e) {
e.printStackTrace();
}
assertParseArgsThrows(
"Value for '--check-first' property should be positive.",
CACHE,
VALIDATE_INDEXES,
CHECK_FIRST.toString(),
"0"
);
assertParseArgsThrows(
"Please specify a value for argument: --check-through",
CACHE,
VALIDATE_INDEXES,
CHECK_THROUGH.toString()
);
}
/** */
@Test
public void testFindAndDeleteGarbage() {
String nodeId = UUID.randomUUID().toString();
String delete = "--delete";
String grps = "group1,grpoup2,group3";
List<List<String>> lists = generateArgumentList(
"find_garbage",
new T2<>(nodeId, false),
new T2<>(delete, false),
new T2<>(grps, false)
);
for (List<String> list : lists) {
ConnectionAndSslParameters args = parseArgs(list);
CacheFindGarbageCommandArg arg = (CacheFindGarbageCommandArg)args.commandArg();
if (list.contains(nodeId))
assertEquals("nodeId parameter unexpected value", nodeId, arg.nodeIds()[0].toString());
else
assertNull(arg.nodeIds());
assertEquals(list.contains(delete), arg.delete());
if (list.contains(grps))
assertEquals(3, arg.groups().length);
else
assertNull(arg.groups());
}
}
/** */
private List<List<String>> generateArgumentList(String subcommand, T2<String, Boolean>...optional) {
List<List<T2<String, Boolean>>> lists = generateAllCombinations(asList(optional), (x) -> x.get2());
ArrayList<List<String>> res = new ArrayList<>();
ArrayList<String> empty = new ArrayList<>();
empty.add(CACHE);
empty.add(subcommand);
res.add(empty);
for (List<T2<String, Boolean>> list : lists) {
ArrayList<String> arg = new ArrayList<>(empty);
list.forEach(x -> arg.add(x.get1()));
res.add(arg);
}
return res;
}
/** */
private <T> List<List<T>> generateAllCombinations(List<T> source, Predicate<T> stopFunc) {
List<List<T>> res = new ArrayList<>();
for (int i = 0; i < source.size(); i++) {
List<T> srcCopy = new ArrayList<>(source);
T removed = srcCopy.remove(i);
generateAllCombinations(singletonList(removed), srcCopy, stopFunc, res);
}
return res;
}
/** */
private <T> void generateAllCombinations(List<T> res, List<T> source, Predicate<T> stopFunc, List<List<T>> acc) {
acc.add(res);
if (stopFunc != null && stopFunc.test(res.get(res.size() - 1)))
return;
if (source.size() == 1) {
ArrayList<T> list = new ArrayList<>(res);
list.add(source.get(0));
acc.add(list);
return;
}
for (int i = 0; i < source.size(); i++) {
ArrayList<T> res0 = new ArrayList<>(res);
List<T> srcCopy = new ArrayList<>(source);
T removed = srcCopy.remove(i);
res0.add(removed);
generateAllCombinations(res0, srcCopy, stopFunc, acc);
}
}
/**
* Tests parsing and validation for the SSL arguments.
*/
@Test
public void testParseAndValidateSSLArguments() {
new IgniteCommandRegistry().commands().forEachRemaining(e -> {
Command<?, ?> cmd = e.getValue();
if (requireArgs(cmd.getClass()))
return;
assertParseArgsThrows("Please specify a value for argument: --truststore", "--truststore", "--tx");
ConnectionAndSslParameters args = parseArgs(asList(
"--keystore", "testKeystore",
"--keystore-password", "testKeystorePassword",
"--keystore-type", "testKeystoreType",
"--truststore", "testTruststore",
"--truststore-password", "testTruststorePassword",
"--truststore-type", "testTruststoreType",
"--ssl-key-algorithm", "testSSLKeyAlgorithm",
"--ssl-protocol", "testSSLProtocol",
cmdText(cmd)
));
assertArrayEquals(new String[] {"testSSLProtocol"}, args.sslProtocol());
assertEquals("testSSLKeyAlgorithm", args.sslKeyAlgorithm());
assertEquals("testKeystore", args.sslKeyStorePath());
assertArrayEquals("testKeystorePassword".toCharArray(), args.sslKeyStorePassword());
assertEquals("testKeystoreType", args.sslKeyStoreType());
assertEquals("testTruststore", args.sslTrustStorePath());
assertArrayEquals("testTruststorePassword".toCharArray(), args.sslTrustStorePassword());
assertEquals("testTruststoreType", args.sslTrustStoreType());
assertEquals(cmd.getClass(), args.command().getClass());
});
}
/**
* Tests parsing and validation for user and password arguments.
*/
@Test
public void testParseAndValidateUserAndPassword() {
new IgniteCommandRegistry().commands().forEachRemaining(e -> {
Command<?, ?> cmd = e.getValue();
if (requireArgs(cmd.getClass()))
return;
assertParseArgsThrows("Please specify a value for argument: --user", "--user", "--tx");
assertParseArgsThrows("Please specify a value for argument: --password", "--password", "--tx");
ConnectionAndSslParameters args = parseArgs(asList("--user", "testUser", "--password", "testPass", cmdText(cmd)));
assertEquals("testUser", args.userName());
assertEquals("testPass", args.password());
assertEquals(cmd.getClass(), args.command().getClass());
});
}
/**
* Tests parsing and validation of WAL commands.
*/
@Test
public void testParseAndValidateWalActions() {
ConnectionAndSslParameters args = parseArgs(asList(WAL, WAL_PRINT));
assertEquals(WalPrintCommand.class, args.command().getClass());
WalDeleteCommandArg arg = (WalDeleteCommandArg)args.commandArg();
assertTrue(arg instanceof WalPrintCommandArg);
String nodes = UUID.randomUUID().toString() + "," + UUID.randomUUID().toString();
args = parseArgs(asList(WAL, WAL_DELETE, nodes));
arg = (WalDeleteCommandArg)args.commandArg();
assertFalse(arg instanceof WalPrintCommandArg);
assertEquals(nodes, String.join(",", arg.consistentIds()));
assertParseArgsThrows("Command wal can't be executed", WAL);
String rnd = UUID.randomUUID().toString();
assertParseArgsThrows("Command wal can't be executed", WAL, rnd);
}
/**
* Tets checks a parser of shutdown policy command.
*/
@Test
public void testParseShutdownPolicyParameters() {
ConnectionAndSslParameters args = parseArgs(asList(SHUTDOWN_POLICY));
assertEquals(ShutdownPolicyCommand.class, args.command().getClass());
assertNull(((ShutdownPolicyCommandArg)args.commandArg()).shutdownPolicy());
for (ShutdownPolicy plc : ShutdownPolicy.values()) {
args = parseArgs(asList(SHUTDOWN_POLICY, String.valueOf(plc)));
assertEquals(ShutdownPolicyCommand.class, args.command().getClass());
assertSame(plc, ((ShutdownPolicyCommandArg)args.commandArg()).shutdownPolicy());
}
}
/**
* Tests that the auto confirmation flag was correctly parsed.
*/
@Test
public <A extends IgniteDataTransferObject> void testParseAutoConfirmationFlag() {
new IgniteCommandRegistry().commands()
.forEachRemaining(e -> checkParseAutoConfirmationFlag(e.getValue(), null));
}
/** */
private <A extends IgniteDataTransferObject> void checkParseAutoConfirmationFlag(
Command<A, ?> cmd,
CommandsRegistry<A, ?> parent
) {
if (cmd instanceof CommandsRegistry) {
((CommandsRegistry<A, ?>)cmd).commands().forEachRemaining(
e -> checkParseAutoConfirmationFlag((Command<A, ?>)e.getValue(), (CommandsRegistry<A, ?>)cmd));
}
if (!executable(cmd))
return;
try {
A arg;
if (cmd.getClass() == CacheDestroyCommand.class) {
CacheDestroyCommandArg a = new CacheDestroyCommandArg();
a.caches(F.asArray("cache1"));
arg = (A)a;
}
else if (cmd.getClass() == CacheClearCommand.class) {
CacheClearCommandArg a = new CacheClearCommandArg();
a.caches(F.asArray("cache1"));
arg = (A)a;
}
else
arg = cmd.argClass().newInstance();
if (cmd.confirmationPrompt(arg) == null)
return;
}
catch (InstantiationException | IllegalAccessException ex) {
throw new IgniteException(ex);
}
CommandHandler.CommandName cmdPrinter = new CommandHandler.CommandName();
if (parent != null)
cmdPrinter.accept(parent);
cmdPrinter.accept(cmd);
String[] cmdText = cmdPrinter.name.substring(1).split(" ");
ConnectionAndSslParameters<A> args;
if (cmd.getClass() == SetStateCommand.class)
cmdText = F.concat(cmdText, "ACTIVE");
else if (cmd.getClass() == ChangeTagCommand.class)
cmdText = F.concat(cmdText, "newTagValue");
else if (cmd.getClass() == CdcDeleteLostSegmentLinksCommand.class)
cmdText = F.concat(cmdText, NODE_ID, UUID.randomUUID().toString());
else if (cmd.getClass() == BaselineVersionCommand.class)
cmdText = F.concat(cmdText, "1");
else if (cmd.getClass() == BaselineAutoAdjustCommand.class)
cmdText = F.concat(cmdText, "enable");
else if (AbstractBaselineCommand.class.isAssignableFrom(cmd.getClass()))
cmdText = F.concat(cmdText, "consistentId1");
else if (cmd.getClass() == CacheDestroyCommand.class || cmd.getClass() == CacheClearCommand.class)
cmdText = F.concat(cmdText, "--caches", "cache1");
else if (cmd.getClass() == EncryptionChangeMasterKeyCommand.class)
cmdText = F.concat(cmdText, "masterKeyName1");
else if (cmd.getClass() == EncryptionChangeCacheKeyCommand.class)
cmdText = F.concat(cmdText, "cacheGroup1");
else if (cmd.getClass() == SnapshotRestoreCommand.class)
cmdText = F.concat(cmdText, "snp1");
else if (cmd.getClass() == MetaUpdateCommand.class)
return;
else if (cmd.getClass() == MetaRemoveCommand.class)
cmdText = F.concat(cmdText, "--typeId", "1");
args = parseArgs(asList(cmdText));
checkCommonParametersCorrectlyParsed(cmd, args, false);
args = parseArgs(asList(F.concat(cmdText, "--yes")));
checkCommonParametersCorrectlyParsed(cmd, args, true);
if (cmd.getClass() == DeactivateCommand.class) {
args = parseArgs(asList(F.concat(cmdText, "--force", "--yes")));
checkCommonParametersCorrectlyParsed(cmd, args, true);
}
else if (cmd.getClass() == SetStateCommand.class) {
ClusterState argState = (((SetStateCommandArg)args.commandArg())).state();
assertEquals("ACTIVE", argState.toString());
}
else if (cmd.getClass() == TxCommand.class) {
args = parseArgs(asList(F.concat(cmdText, "--xid", "xid1", "--min-duration", "10", "--kill", "--yes")));
checkCommonParametersCorrectlyParsed(cmd, args, true);
TxCommandArg txTaskArg = (TxCommandArg)args.commandArg();
assertEquals("xid1", txTaskArg.xid());
assertEquals(10_000, txTaskArg.minDuration().longValue());
assertTrue(txTaskArg.kill());
}
}
/** */
private void checkCommonParametersCorrectlyParsed(
Command<?, ?> cmd,
ConnectionAndSslParameters args,
boolean autoConfirm
) {
assertEquals(cmd.getClass(), args.command().getClass());
assertEquals(DFLT_HOST, args.host());
assertEquals(DFLT_PORT, args.port());
assertEquals(autoConfirm, args.autoConfirmation());
}
/**
* Tests host and port arguments.
* Tests connection settings arguments.
*/
@Test
public void testConnectionSettings() {
new IgniteCommandRegistry().commands().forEachRemaining(e -> {
Command<?, ?> cmd = e.getValue();
if (requireArgs(cmd.getClass()))
return;
String name = cmdText(cmd);
ConnectionAndSslParameters args = parseArgs(asList(name));
assertEquals(cmd.getClass(), args.command().getClass());
assertEquals(DFLT_HOST, args.host());
assertEquals(DFLT_PORT, args.port());
args = parseArgs(asList("--port", "12345", "--host", "test-host", "--ping-interval", "5000",
"--ping-timeout", "40000", name));
assertEquals(cmd.getClass(), args.command().getClass());
assertEquals("test-host", args.host());
assertEquals(12345, args.port());
assertEquals(5000, args.pingInterval());
assertEquals(40000, args.pingTimeout());
assertParseArgsThrows("Can't parse number 'wrong-port'", "--port", "wrong-port", name);
assertParseArgsThrows("Invalid value for --ping-interval: -10", "--ping-interval", "-10", name);
assertParseArgsThrows("Invalid value for --ping-timeout: -20", "--ping-timeout", "-20", name);
});
}
/**
* Test parsing dump transaction arguments.
*/
@Test
public void testTransactionArguments() {
ConnectionAndSslParameters args;
parseArgs(asList("--tx"));
assertParseArgsThrows("Please specify a value for argument: --min-duration", "--tx", "--min-duration");
assertParseArgsThrows("Ouch! Argument is invalid: --min-duration", "--tx", "--min-duration", "-1");
assertParseArgsThrows("Please specify a value for argument: --min-size", "--tx", "--min-size");
assertParseArgsThrows("Ouch! Argument is invalid: --min-size", "--tx", "--min-size", "-1");
assertParseArgsThrows("--label", "--tx", "--label");
assertParseArgsThrows("Illegal regex syntax", "--tx", "--label", "tx123[");
assertParseArgsThrows("Only one of [--servers, --nodes, --clients] allowed", "--tx", "--servers", "--nodes", "1,2,3");
args = parseArgs(asList("--tx", "--min-duration", "120", "--min-size", "10", "--limit", "100", "--order", "SIZE", "--servers"));
TxCommandArg arg = (TxCommandArg)args.commandArg();
assertEquals(Long.valueOf(120 * 1000L), arg.minDuration());
assertEquals(Integer.valueOf(10), arg.minSize());
assertEquals(Integer.valueOf(100), arg.limit());
assertEquals(TxSortOrder.SIZE, arg.order());
assertTrue(arg.servers());
assertFalse(arg.clients());
args = parseArgs(asList("--tx", "--min-duration", "130", "--min-size", "1", "--limit", "60", "--order", "DURATION",
"--clients"));
arg = (TxCommandArg)args.commandArg();
assertEquals(Long.valueOf(130 * 1000L), arg.minDuration());
assertEquals(Integer.valueOf(1), arg.minSize());
assertEquals(Integer.valueOf(60), arg.limit());
assertEquals(TxSortOrder.DURATION, arg.order());
assertFalse(arg.servers());
assertTrue(arg.clients());
args = parseArgs(asList("--tx", "--nodes", "1,2,3"));
arg = (TxCommandArg)args.commandArg();
assertFalse(arg.servers());
assertFalse(arg.clients());
assertArrayEquals(new String[] {"1", "2", "3"}, arg.nodes());
}
/**
* Test parsing kill arguments.
*/
@Test
public void testKillArguments() {
assertParseArgsThrows("Command kill can't be executed", "--kill");
String uuid = UUID.randomUUID().toString();
// Scan command format errors.
assertParseArgsThrows("Argument origin_node_id required.", "--kill", "scan");
assertParseArgsThrows("Argument cache_name required.", "--kill", "scan", uuid);
assertParseArgsThrows("Argument query_id required.", "--kill", "scan", uuid, "cache");
assertParseArgsThrows("String representation of \"java.util.UUID\" is exepected", IllegalArgumentException.class,
"--kill", "scan", "not_a_uuid");
assertParseArgsThrows("Can't parse number 'not_a_number'", NumberFormatException.class,
"--kill", "scan", uuid, "my-cache", "not_a_number");
// Compute command format errors.
assertParseArgsThrows("Argument session_id required.", "--kill", "compute");
assertParseArgsThrows("Invalid UUID string: not_a_uuid", IllegalArgumentException.class,
"--kill", "compute", "not_a_uuid");
// Service command format errors.
assertParseArgsThrows("Argument name required.", "--kill", "service");
// Transaction command format errors.
assertParseArgsThrows("Argument xid required.", "--kill", "transaction");
// SQL command format errors.
assertParseArgsThrows("Argument query_id required.", "--kill", "sql");
assertParseArgsThrows("Expected global query id. " + EXPECTED_GLOBAL_QRY_ID_FORMAT,
"--kill", "sql", "not_sql_id");
// Continuous command format errors.
assertParseArgsThrows("Argument origin_node_id required.", "--kill", "continuous");
assertParseArgsThrows("Argument routine_id required.", "--kill", "continuous", UUID.randomUUID().toString());
assertParseArgsThrows("String representation of \"java.util.UUID\" is exepected", IllegalArgumentException.class,
"--kill", "continuous", "not_a_uuid");
assertParseArgsThrows("String representation of \"java.util.UUID\" is exepected", IllegalArgumentException.class,
"--kill", "continuous", UUID.randomUUID().toString(), "not_a_uuid");
}
/**
* Negative argument validation test for tracing-configuration command.
*
* validate that following tracing-configuration arguments validated as expected:
* <ul>
* <li>
* reset_all, get_all
* <ul>
* <li>
* --scope
* <ul>
* <li>
* if value is missing:
* IllegalArgumentException
* (The scope should be specified.
* The following values can be used: [DISCOVERY, EXCHANGE, COMMUNICATION, TX].")
* </li>
* <li>
* if unsupported value is used:
* IllegalArgumentException
* (Invalid scope 'aaa'. The following values can be used: [DISCOVERY, EXCHANGE, COMMUNICATION, TX])
* </li>
* </ul>
* </li>
* </ul>
* </li>
* <li>
* reset, get:
* <ul>
* <li>
* --scope
* <ul>
* <li>
* if value is missing:
* IllegalArgumentException
* (The scope should be specified.
* The following values can be used: [DISCOVERY, EXCHANGE, COMMUNICATION, TX].")
* </li>
* <li>
* if unsupported value is used:
* IllegalArgumentException
* (Invalid scope 'aaa'. The following values can be used: [DISCOVERY, EXCHANGE, COMMUNICATION, TX])
* </li>
* </ul>
* </li>
* <li>
* --label
* <ul>
* <li>
* if value is missing:
* IllegalArgumentException (The label should be specified.)
* </li>
* </ul>
* </li>
* </ul>
* </li>
* <li>
* set:
* <ul>
* <li>
* --scope
* <ul>
* <li>
* if value is missing:
* IllegalArgumentException
* (The scope should be specified.
* The following values can be used: [DISCOVERY, EXCHANGE, COMMUNICATION, TX].")
* </li>
* <li>
* if unsupported value is used:
* IllegalArgumentException
* (Invalid scope 'aaa'. The following values can be used: [DISCOVERY, EXCHANGE, COMMUNICATION, TX])
* </li>
* </ul>
* </li>
* <li>
* --label
* <ul>
* <li>
* if value is missing:
* IllegalArgumentException (The label should be specified.)
* </li>
* </ul>
* </li>
* <li>
* --sampling-rate
* <ul>
* <li>
* if value is missing:
* IllegalArgumentException
* (The sampling-rate should be specified. Decimal value between 0 and 1 should be used.)
* </li>
* <li>
* if unsupported value is used:
* IllegalArgumentException (Invalid samling-rate 'aaa'. Decimal value between 0 and 1 should be used.)
* </li>
* </ul>
* </li>
* <li>
* --included-scopes
* <ul>
* <li>
* if value is missing:
* IllegalArgumentException (At least one supported scope should be specified.)
* </li>
* <li>
* if unsupported value is used:
* IllegalArgumentException
* (Invalid supported scope: aaa.
* The following values can be used: [DISCOVERY, EXCHANGE, COMMUNICATION, TX].)
* </li>
* </ul>
* </li>
* </ul>
* </li>
* </ul>
*/
@Test
public void testTracingConfigurationArgumentsValidation() {
// reset
assertParseArgsThrows("Please specify a value for argument: --scope", "--tracing-configuration", "reset", "--scope");
assertParseArgsThrows("Failed to parse --scope command argument", "--tracing-configuration", "reset", "--scope", "aaa");
assertParseArgsThrows("Please specify a value for argument: --label", "--tracing-configuration", "reset", "--label");
// reset all
assertParseArgsThrows("Please specify a value for argument: --scope", "--tracing-configuration", "reset_all", "--scope");
assertParseArgsThrows("Failed to parse --scope command argument", "--tracing-configuration", "reset_all", "--scope", "aaa");
// get
assertParseArgsThrows("Please specify a value for argument: --scope", "--tracing-configuration", "get", "--scope");
assertParseArgsThrows("Failed to parse --scope command argument", "--tracing-configuration", "get", "--scope", "aaa");
assertParseArgsThrows("Please specify a value for argument: --label", "--tracing-configuration", "get", "--label");
// get all
assertParseArgsThrows("Please specify a value for argument: --scope", "--tracing-configuration", "get_all", "--scope");
assertParseArgsThrows("Failed to parse --scope command argument", "--tracing-configuration", "get_all", "--scope", "aaa");
// set
assertParseArgsThrows("Please specify a value for argument: --scope", "--tracing-configuration", "set", "--scope");
assertParseArgsThrows("Failed to parse --scope command argument", "--tracing-configuration", "set", "--scope", "aaa");
assertParseArgsThrows("Please specify a value for argument: --label", "--tracing-configuration", "set", "--label");
assertParseArgsThrows("Please specify a value for argument: --sampling-rate", "--tracing-configuration", "set", "--sampling-rate");
assertParseArgsThrows("Failed to parse --sampling-rate command argument",
"--tracing-configuration", "set", "--sampling-rate", "aaa");
assertParseArgsThrows("Invalid sampling-rate '-1.0'. Decimal value between 0 and 1 should be used.",
"--tracing-configuration", "set", "--sampling-rate", "-1", "--scope", "SQL");
assertParseArgsThrows("Invalid sampling-rate '2.0'. Decimal value between 0 and 1 should be used.",
"--tracing-configuration", "set", "--sampling-rate", "2", "--scope", "SQL");
assertParseArgsThrows("Please specify a value for argument: --included-scopes",
"--tracing-configuration", "set", "--included-scopes");
assertParseArgsThrows("Failed to parse --included-scopes command argument",
"--tracing-configuration", "set", "--included-scopes", "TX,aaa");
}
/**
* Positive argument validation test for tracing-configuration command.
*/
@Test
public void testTracingConfigurationArgumentsValidationMandatoryArgumentSet() {
parseArgs(asList("--tracing-configuration"));
parseArgs(asList("--tracing-configuration", "get_all"));
assertParseArgsThrows("Mandatory argument(s) missing: [--scope]", "--tracing-configuration", "reset");
assertParseArgsThrows("Mandatory argument(s) missing: [--scope]", "--tracing-configuration", "get");
assertParseArgsThrows("Mandatory argument(s) missing: [--scope]", "--tracing-configuration", "set");
}
/**
* Test checks that option {@link ArgumentParser#CMD_VERBOSE} is parsed
* correctly and if it is not present, it takes the default value
* {@code false}.
*/
@Test
public void testParseVerboseOption() {
new IgniteCommandRegistry().commands().forEachRemaining(e -> {
Command<?, ?> cmd = e.getValue();
if (requireArgs(cmd.getClass()))
return;
assertFalse(cmd.toString(), parseArgs(singletonList(cmdText(cmd))).verbose());
assertTrue(cmd.toString(), parseArgs(asList(cmdText(cmd), CMD_VERBOSE)).verbose());
});
}
/** */
@SuppressWarnings("ThrowableNotThrown")
@Test
public void testIndexForceRebuildWrongArgs() {
String nodeId = UUID.randomUUID().toString();
GridTestUtils.assertThrows(
null,
() -> parseArgs(asList(CACHE, "indexes_force_rebuild", "--node-id")),
IllegalArgumentException.class,
"Please specify a value for argument: --node-id"
);
GridTestUtils.assertThrows(
null,
() -> parseArgs(asList(CACHE, "indexes_force_rebuild", "--node-id", nodeId, "--cache-names")),
IllegalArgumentException.class,
"Please specify a value for argument: --cache-names"
);
GridTestUtils.assertThrows(
null,
() -> parseArgs(asList(CACHE, "indexes_force_rebuild", "--node-id", nodeId, "--group-names")),
IllegalArgumentException.class,
"Please specify a value for argument: --group-names"
);
GridTestUtils.assertThrows(
null,
() -> parseArgs(asList(
CACHE, "indexes_force_rebuild",
"--node-id", nodeId,
"--group-names", "someNames",
"--cache-names", "someNames"
)),
IllegalArgumentException.class,
"Only one of [--group-names, --cache-names] allowed"
);
GridTestUtils.assertThrows(
null,
() -> parseArgs(asList(
CACHE, "indexes_force_rebuild",
"--node-id", nodeId,
"--cache-names", "someNames",
"--cache-names", "someMoreNames"
)),
IllegalArgumentException.class,
"--cache-names argument specified twice"
);
GridTestUtils.assertThrows(
null,
() -> parseArgs(asList(
CACHE, "indexes_force_rebuild",
"--node-id", nodeId,
"--group-names", "someNames",
"--group-names", "someMoreNames"
)),
IllegalArgumentException.class,
"--group-names argument specified twice"
);
GridTestUtils.assertThrows(
null,
() -> parseArgs(asList(
CACHE, "indexes_force_rebuild",
"--node-id", nodeId,
"--group-names", "--some-other-arg"
)),
IllegalArgumentException.class,
"Unexpected value: --some-other-arg"
);
GridTestUtils.assertThrows(
null,
() -> parseArgs(asList(
CACHE, "indexes_force_rebuild",
"--node-id", nodeId,
"--cache-names", "--some-other-arg"
)),
IllegalArgumentException.class,
"Unexpected value: --some-other-arg"
);
GridTestUtils.assertThrows(
null,
() -> parseArgs(asList(
CACHE, "indexes_force_rebuild",
"--node-id", nodeId,
"--node-ids", nodeId + ',' + nodeId,
"--cache-names", "someNames"
)),
IllegalArgumentException.class,
"Only one of [--node-ids, --all-nodes, --node-id] allowed"
);
GridTestUtils.assertThrows(
null,
() -> parseArgs(asList(
CACHE, "indexes_force_rebuild",
"--node-id", nodeId,
"--all-nodes"
)),
IllegalArgumentException.class,
"Only one of [--node-ids, --all-nodes, --node-id] allowed"
);
GridTestUtils.assertThrows(
null,
() -> parseArgs(asList(
CACHE, "indexes_force_rebuild",
"--node-ids", nodeId + ',' + nodeId,
"--all-nodes"
)),
IllegalArgumentException.class,
"Only one of [--node-ids, --all-nodes, --node-id] allowed"
);
GridTestUtils.assertThrows(
null,
() -> parseArgs(asList(
CACHE, "indexes_force_rebuild",
"--node-id", nodeId,
"--node-ids", nodeId + ',' + nodeId,
"--all-nodes"
)),
IllegalArgumentException.class,
"Only one of [--node-ids, --all-nodes, --node-id] allowed"
);
}
/** */
@Test
public void testScheduleIndexRebuildWrongArgs() {
GridTestUtils.assertThrows(
null,
() -> parseArgs(asList(CACHE, "schedule_indexes_rebuild", "--node-id")),
IllegalArgumentException.class,
"Please specify a value for argument: --node-id"
);
String nodeId = UUID.randomUUID().toString();
GridTestUtils.assertThrows(
null,
() -> parseArgs(asList(CACHE, "schedule_indexes_rebuild", "--node-id", nodeId, "--cache-names")),
IllegalArgumentException.class,
"Please specify a value for argument: --cache-names"
);
GridTestUtils.assertThrows(
null,
() -> parseArgs(asList(CACHE, "schedule_indexes_rebuild", "--node-id", nodeId, "--node-id", nodeId)),
IllegalArgumentException.class,
"--node-id argument specified twice"
);
GridTestUtils.assertThrows(
null,
() -> parseArgs(asList(CACHE, "schedule_indexes_rebuild", "--node-id", nodeId, "--cache-names", "a",
"--cache-names", "b")),
IllegalArgumentException.class,
"--cache-names argument specified twice"
);
GridTestUtils.assertThrows(
null,
() -> parseArgs(asList(CACHE, "schedule_indexes_rebuild", "--node-id", nodeId, "--group-names", "a",
"--group-names", "b")),
IllegalArgumentException.class,
"--group-names argument specified twice"
);
GridTestUtils.assertThrows(
null,
() -> parseArgs(asList(CACHE, "schedule_indexes_rebuild")),
IllegalArgumentException.class,
"One of [--group-names, --cache-names] required"
);
GridTestUtils.assertThrows(
null,
() -> parseArgs(asList(CACHE, "schedule_indexes_rebuild", "--cache-names", "foo[]")),
IllegalArgumentException.class,
"Square brackets must contain comma-separated indexes or not be used at all."
);
GridTestUtils.assertThrows(
null,
() -> parseArgs(asList(CACHE, "schedule_indexes_rebuild", "--node-id", nodeId, "--all-nodes", "--group-names", "a")),
IllegalArgumentException.class,
"Only one of [--node-ids, --all-nodes, --node-id]"
);
GridTestUtils.assertThrows(
null,
() -> parseArgs(asList(CACHE, "schedule_indexes_rebuild", "--node-id", nodeId, "--node-ids", nodeId + ',' + nodeId,
"--group-names", "a")),
IllegalArgumentException.class,
"Only one of [--node-ids, --all-nodes, --node-id]"
);
GridTestUtils.assertThrows(
null,
() -> parseArgs(asList(CACHE, "schedule_indexes_rebuild", "--all-nodes", "--node-ids", nodeId + ',' + nodeId,
"--group-names", "a")),
IllegalArgumentException.class,
"Only one of [--node-ids, --all-nodes, --node-id]"
);
GridTestUtils.assertThrows(
null,
() -> parseArgs(asList(CACHE, "schedule_indexes_rebuild", "--node-id", nodeId, "--all-nodes",
"--node-ids", nodeId + ',' + nodeId, "--group-names", "a")),
IllegalArgumentException.class,
"Only one of [--node-ids, --all-nodes, --node-id]"
);
}
/** */
@Test
public void testScheduleIndexRebuildArgs() {
UUID nodeId = UUID.randomUUID();
Map<String, Set<String>> params1 = new HashMap<>();
params1.put("cache1", new HashSet<>(Arrays.asList("foo", "bar")));
params1.put("cache2", null);
params1.put("foocache", new HashSet<>(Arrays.asList("idx", "bar")));
params1.put("bar", Collections.singleton("foo"));
CacheScheduleIndexesRebuildCommandArg arg1 = (CacheScheduleIndexesRebuildCommandArg)
parseArgs(asList(CACHE, "schedule_indexes_rebuild", "--node-id", nodeId.toString(),
"--cache-names", buildScheduleIndexRebuildCacheNames(params1))).commandArg();
assertEquals(normalizeScheduleIndexRebuildCacheNamesMap(params1), arg1.cacheToIndexes());
assertEquals(null, arg1.groupNames());
Map<String, Set<String>> params2 = new HashMap<>();
params2.put("cache1", new HashSet<>(Arrays.asList("foo", "bar")));
params2.put("cache2", null);
params2.put("foocache", new HashSet<>(Arrays.asList("idx", "bar")));
params2.put("bar", Collections.singleton("foo"));
Map<String, Set<String>> normalized = normalizeScheduleIndexRebuildCacheNamesMap(params2);
CacheScheduleIndexesRebuildCommandArg arg2 = (CacheScheduleIndexesRebuildCommandArg)
parseArgs(asList(CACHE, "schedule_indexes_rebuild", "--node-id", nodeId.toString(),
"--cache-names", buildScheduleIndexRebuildCacheNames(params2), "--group-names", "foocache,someGrp")
).commandArg();
assertEquals(normalized, arg2.cacheToIndexes());
assertArrayEquals(new String[]{"foocache", "someGrp"}, arg2.groupNames());
}
/**
* Builds a new --cache-names parameters map replacing nulls with empty set so it should be the same as
* the parsed argument of the {@link CacheScheduleIndexesRebuildCommandArg#cacheToIndexes()}.
*
* @param paramsMap Cache -> indexes map.
* @return New map with nulls replaced with empty set.
*/
private static Map<String, Set<String>> normalizeScheduleIndexRebuildCacheNamesMap(Map<String, Set<String>> paramsMap) {
return paramsMap.entrySet().stream()
.collect(Collectors.toMap(Entry::getKey, e -> e.getValue() != null ? e.getValue() : emptySet()));
}
/**
* Builds a --cache-names parameter string for the schedule_indexes_rebuild command from the
* cache -> indexes map.
* Example: {foo: [], bar: null, test: [idx1, idx2]} will be converted into the "foo[],bar,test[idx1,idx2]" string.
*
* @param paramsMap Cache -> indexes map.
* @return --cache-names parameter string.
*/
private String buildScheduleIndexRebuildCacheNames(Map<String, Set<String>> paramsMap) {
return paramsMap.entrySet().stream().map(e -> {
StringBuilder sb = new StringBuilder();
String cacheName = e.getKey();
Set<String> indexes = e.getValue();
sb.append(cacheName);
if (indexes != null) {
sb.append("[");
sb.append(String.join(",", indexes));
sb.append("]");
}
return sb.toString();
}).collect(Collectors.joining(","));
}
/** */
@SuppressWarnings("ThrowableNotThrown")
@Test
public void testIndexListWrongArgs() {
String nodeId = UUID.randomUUID().toString();
GridTestUtils.assertThrows(
null,
() -> parseArgs(asList(CACHE, "indexes_list", "--node-id")),
IllegalArgumentException.class,
"Please specify a value for argument: --node-id"
);
GridTestUtils.assertThrows(
null,
() -> parseArgs(asList(CACHE, "indexes_list", "--node-id", nodeId, "--group-name")),
IllegalArgumentException.class,
"Please specify a value for argument: --group-name"
);
GridTestUtils.assertThrows(
null,
() -> parseArgs(asList(CACHE, "indexes_list", "--node-id", nodeId, "--group-name", INVALID_REGEX)),
IllegalArgumentException.class,
"Invalid group name regex: " + INVALID_REGEX
);
GridTestUtils.assertThrows(
null,
() -> parseArgs(asList(CACHE, "indexes_list", "--node-id", nodeId, "--cache-name")),
IllegalArgumentException.class,
"Please specify a value for argument: --cache-name"
);
GridTestUtils.assertThrows(
null,
() -> parseArgs(asList(CACHE, "indexes_list", "--node-id", nodeId, "--cache-name", INVALID_REGEX)),
IllegalArgumentException.class,
"Invalid cache name regex: " + INVALID_REGEX
);
GridTestUtils.assertThrows(
null,
() -> parseArgs(asList(CACHE, "indexes_list", "--node-id", nodeId, "--index-name")),
IllegalArgumentException.class,
"Please specify a value for argument: --index-name"
);
GridTestUtils.assertThrows(
null,
() -> parseArgs(asList(CACHE, "indexes_list", "--node-id", nodeId, "--index-name", INVALID_REGEX)),
IllegalArgumentException.class,
"Invalid index name regex: " + INVALID_REGEX
);
}
/** */
@SuppressWarnings("ThrowableNotThrown")
@Test
public void testIndexRebuildStatusWrongArgs() {
GridTestUtils.assertThrows(
null,
() -> parseArgs(asList(CACHE, "indexes_list", "--node-id")),
IllegalArgumentException.class,
"Please specify a value for argument: --node-id"
);
}
/**
* Test verifies correctness of parsing of arguments --warm-up command.
*/
@Test
public void testWarmUpArgs() {
String[][] args = {
{"--warm-up"},
{"--warm-up", "1"},
{"--warm-up", "stop"}
};
for (String[] arg : args) {
GridTestUtils.assertThrows(
null,
() -> parseArgs(asList(arg)),
IllegalArgumentException.class,
"Command warm-up can't be executed"
);
}
assertNotNull(parseArgs(asList("--warm-up", "--stop")));
}
/** Tests that enum {@link Argument} has enum constants description: {@link EnumDescription}. */
@Test
public void testEnumParameterDescription() {
new IgniteCommandRegistry().commands().forEachRemaining(e -> checkEnumDescription(e.getValue()));
}
/** */
private void checkEnumDescription(Command<?, ?> cmd) {
if (cmd instanceof CommandsRegistry)
((CommandsRegistry<?, ?>)cmd).commands().forEachRemaining(e -> checkEnumDescription(e.getValue()));
if (!executable(cmd))
return;
Consumer<Field> fldCnsmr = fld -> {
if (!fld.getType().isEnum())
return;
EnumDescription descAnn = fld.getAnnotation(EnumDescription.class);
assertNotNull("Please, specify a description to the enum parameter using " +
"@" + EnumDescription.class.getSimpleName() + " annotation. " +
"Parameter: " + cmd.argClass().getSimpleName() + "#" + fld.getName(),
descAnn);
assertEquals("Please, specify a description to enum constants: " +
stream(fld.getType().getEnumConstants())
.filter(e -> stream(descAnn.names()).noneMatch(n -> n.equals(((Enum<?>)e).name())))
.collect(Collectors.toSet()) +
". Parameter: " + cmd.argClass().getSimpleName() + "#" + fld.getName(),
fld.getType().getEnumConstants().length, descAnn.names().length);
Argument argAnn = fld.getAnnotation(Argument.class);
Positional posAnn = fld.getAnnotation(Positional.class);
if (posAnn == null) {
assertFalse("Please, set a description for the argument: " +
cmd.argClass().getSimpleName() + "#" + fld.getName(),
argAnn.description().isEmpty());
}
else {
assertTrue("Please, remove a description for the positional argument: " +
cmd.argClass().getSimpleName() + "#" + fld.getName(),
argAnn.description().isEmpty());
}
};
visitCommandParams(cmd.argClass(), fldCnsmr, fldCnsmr, (grp, flds) -> flds.forEach(fldCnsmr));
}
/**
* @param args Raw arg list.
* @return Common parameters container object.
*/
private <A extends IgniteDataTransferObject> ConnectionAndSslParameters<A> parseArgs(List<String> args) {
return new ArgumentParser(setupTestLogger(), new IgniteCommandRegistry()).parseAndValidate(args);
}
/**
* @return logger for tests.
*/
private IgniteLogger setupTestLogger() {
try {
return U.initLogger(null, getClass().getName(), null, U.defaultWorkDirectory());
}
catch (IgniteCheckedException e) {
throw new IgniteException(e);
}
}
/**
* Checks that parse arguments fails with {@link IllegalArgumentException} and {@code failMsg} message.
*
* @param failMsg Exception message (optional).
* @param args Incoming arguments.
*/
private void assertParseArgsThrows(@Nullable String failMsg, String... args) {
assertParseArgsThrows(failMsg, IllegalArgumentException.class, args);
}
/**
* Checks that parse arguments fails with {@code exception} and {@code failMsg} message.
*
* @param failMsg Exception message (optional).
* @param cls Exception class.
* @param args Incoming arguments.
*/
private void assertParseArgsThrows(@Nullable String failMsg, Class<? extends Exception> cls, String... args) {
assertThrows(null, () -> parseArgs(asList(args)), cls, failMsg);
}
/**
* Return {@code True} if cmd there are required arguments.
*
* @return {@code True} if cmd there are required arguments.
*/
private boolean requireArgs(Class<?> cmd) {
return cmd == CacheCommand.class ||
cmd == WalCommand.class ||
cmd == SetStateCommand.class ||
cmd == EncryptionCommand.class ||
cmd == KillCommand.class ||
cmd == SnapshotCommand.class ||
cmd == ChangeTagCommand.class ||
cmd == MetaCommand.class ||
cmd == WarmUpCommand.class ||
cmd == PropertyCommand.class ||
cmd == SystemViewCommand.class ||
cmd == MetricCommand.class ||
cmd == DefragmentationCommand.class ||
cmd == PerformanceStatisticsCommand.class ||
cmd == ConsistencyCommand.class ||
cmd == CdcCommand.class;
}
}