blob: c484018e0e568e2ce1afd52f1e307de53b50398f [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.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import java.util.stream.Collectors;
import org.apache.ignite.IgniteSystemProperties;
import org.apache.ignite.cluster.ClusterNode;
import org.apache.ignite.compute.ComputeTask;
import org.apache.ignite.internal.IgniteNodeAttributes;
import org.apache.ignite.internal.client.GridClient;
import org.apache.ignite.internal.client.GridClientAuthenticationException;
import org.apache.ignite.internal.client.GridClientClosedException;
import org.apache.ignite.internal.client.GridClientClusterState;
import org.apache.ignite.internal.client.GridClientCompute;
import org.apache.ignite.internal.client.GridClientConfiguration;
import org.apache.ignite.internal.client.GridClientDisconnectedException;
import org.apache.ignite.internal.client.GridClientException;
import org.apache.ignite.internal.client.GridClientFactory;
import org.apache.ignite.internal.client.GridClientHandshakeException;
import org.apache.ignite.internal.client.GridClientNode;
import org.apache.ignite.internal.client.GridServerUnreachableException;
import org.apache.ignite.internal.client.impl.connection.GridClientConnectionResetException;
import org.apache.ignite.internal.commandline.cache.CacheArguments;
import org.apache.ignite.internal.commandline.cache.CacheCommand;
import org.apache.ignite.internal.commandline.cache.distribution.CacheDistributionTask;
import org.apache.ignite.internal.commandline.cache.distribution.CacheDistributionTaskArg;
import org.apache.ignite.internal.commandline.cache.distribution.CacheDistributionTaskResult;
import org.apache.ignite.internal.commandline.cache.reset_lost_partitions.CacheResetLostPartitionsTask;
import org.apache.ignite.internal.commandline.cache.reset_lost_partitions.CacheResetLostPartitionsTaskArg;
import org.apache.ignite.internal.commandline.cache.reset_lost_partitions.CacheResetLostPartitionsTaskResult;
import org.apache.ignite.internal.processors.cache.verify.CacheInfo;
import org.apache.ignite.internal.processors.cache.verify.ContentionInfo;
import org.apache.ignite.internal.processors.cache.verify.IdleVerifyResultV2;
import org.apache.ignite.internal.processors.cache.verify.PartitionHashRecord;
import org.apache.ignite.internal.processors.cache.verify.PartitionKey;
import org.apache.ignite.internal.processors.cache.verify.VerifyBackupPartitionsTaskV2;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.X;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.internal.visor.VisorTaskArgument;
import org.apache.ignite.internal.visor.baseline.VisorBaselineNode;
import org.apache.ignite.internal.visor.baseline.VisorBaselineOperation;
import org.apache.ignite.internal.visor.baseline.VisorBaselineTask;
import org.apache.ignite.internal.visor.baseline.VisorBaselineTaskArg;
import org.apache.ignite.internal.visor.baseline.VisorBaselineTaskResult;
import org.apache.ignite.internal.visor.misc.VisorClusterNode;
import org.apache.ignite.internal.visor.misc.VisorWalTask;
import org.apache.ignite.internal.visor.misc.VisorWalTaskArg;
import org.apache.ignite.internal.visor.misc.VisorWalTaskOperation;
import org.apache.ignite.internal.visor.misc.VisorWalTaskResult;
import org.apache.ignite.internal.visor.tx.VisorTxInfo;
import org.apache.ignite.internal.visor.tx.VisorTxOperation;
import org.apache.ignite.internal.visor.tx.VisorTxProjection;
import org.apache.ignite.internal.visor.tx.VisorTxSortOrder;
import org.apache.ignite.internal.visor.tx.VisorTxTask;
import org.apache.ignite.internal.visor.tx.VisorTxTaskArg;
import org.apache.ignite.internal.visor.tx.VisorTxTaskResult;
import org.apache.ignite.internal.visor.verify.IndexValidationIssue;
import org.apache.ignite.internal.visor.verify.ValidateIndexesPartitionResult;
import org.apache.ignite.internal.visor.verify.VisorContentionTask;
import org.apache.ignite.internal.visor.verify.VisorContentionTaskArg;
import org.apache.ignite.internal.visor.verify.VisorContentionTaskResult;
import org.apache.ignite.internal.visor.verify.VisorIdleVerifyDumpTask;
import org.apache.ignite.internal.visor.verify.VisorIdleVerifyDumpTaskArg;
import org.apache.ignite.internal.visor.verify.VisorIdleVerifyTask;
import org.apache.ignite.internal.visor.verify.VisorIdleVerifyTaskArg;
import org.apache.ignite.internal.visor.verify.VisorIdleVerifyTaskResult;
import org.apache.ignite.internal.visor.verify.VisorIdleVerifyTaskV2;
import org.apache.ignite.internal.visor.verify.VisorValidateIndexesJobResult;
import org.apache.ignite.internal.visor.verify.VisorValidateIndexesTaskArg;
import org.apache.ignite.internal.visor.verify.VisorValidateIndexesTaskResult;
import org.apache.ignite.internal.visor.verify.VisorViewCacheTask;
import org.apache.ignite.internal.visor.verify.VisorViewCacheTaskArg;
import org.apache.ignite.internal.visor.verify.VisorViewCacheTaskResult;
import org.apache.ignite.lang.IgniteProductVersion;
import org.apache.ignite.plugin.security.SecurityCredentials;
import org.apache.ignite.plugin.security.SecurityCredentialsBasicProvider;
import static org.apache.ignite.IgniteSystemProperties.IGNITE_ENABLE_EXPERIMENTAL_COMMAND;
import static org.apache.ignite.internal.IgniteVersionUtils.ACK_VER_STR;
import static org.apache.ignite.internal.IgniteVersionUtils.COPYRIGHT;
import static org.apache.ignite.internal.commandline.Command.ACTIVATE;
import static org.apache.ignite.internal.commandline.Command.BASELINE;
import static org.apache.ignite.internal.commandline.Command.CACHE;
import static org.apache.ignite.internal.commandline.Command.DEACTIVATE;
import static org.apache.ignite.internal.commandline.Command.STATE;
import static org.apache.ignite.internal.commandline.Command.TX;
import static org.apache.ignite.internal.commandline.Command.WAL;
import static org.apache.ignite.internal.visor.baseline.VisorBaselineOperation.ADD;
import static org.apache.ignite.internal.visor.baseline.VisorBaselineOperation.COLLECT;
import static org.apache.ignite.internal.visor.baseline.VisorBaselineOperation.REMOVE;
import static org.apache.ignite.internal.visor.baseline.VisorBaselineOperation.SET;
import static org.apache.ignite.internal.visor.baseline.VisorBaselineOperation.VERSION;
import static org.apache.ignite.internal.visor.verify.VisorViewCacheCmd.GROUPS;
import static org.apache.ignite.internal.visor.verify.VisorViewCacheCmd.SEQ;
/**
* Class that execute several commands passed via command line.
*/
public class CommandHandler {
/** Logger. */
private static final Logger log = Logger.getLogger(CommandHandler.class.getName());
/** */
static final String DFLT_HOST = "127.0.0.1";
/** */
static final String DFLT_PORT = "11211";
/** */
private static final String CMD_HELP = "--help";
/** */
private static final String CMD_HOST = "--host";
/** */
private static final String CMD_PORT = "--port";
/** */
private static final String CMD_PASSWORD = "--password";
/** */
private static final String CMD_USER = "--user";
/** Option is used for auto confirmation. */
private static final String CMD_AUTO_CONFIRMATION = "--yes";
/** */
protected static final String CMD_PING_INTERVAL = "--ping-interval";
/** */
protected static final String CMD_PING_TIMEOUT = "--ping-timeout";
/** */
private static final String CMD_DUMP = "--dump";
/** */
private static final String CMD_SKIP_ZEROS = "--skipZeros";
/** */
private static final String CMD_USER_ATTRIBUTES = "--user-attributes";
/** List of optional auxiliary commands. */
private static final Set<String> AUX_COMMANDS = new HashSet<>();
static {
AUX_COMMANDS.add(CMD_HELP);
AUX_COMMANDS.add(CMD_HOST);
AUX_COMMANDS.add(CMD_PORT);
AUX_COMMANDS.add(CMD_PASSWORD);
AUX_COMMANDS.add(CMD_USER);
AUX_COMMANDS.add(CMD_AUTO_CONFIRMATION);
AUX_COMMANDS.add(CMD_PING_INTERVAL);
AUX_COMMANDS.add(CMD_PING_TIMEOUT);
}
/** Broadcast uuid. */
private static final UUID BROADCAST_UUID = UUID.randomUUID();
/** */
public static final String CONFIRM_MSG = "y";
/** */
private static final String BASELINE_ADD = "add";
/** */
private static final String BASELINE_REMOVE = "remove";
/** */
private static final String BASELINE_COLLECT = "collect";
/** */
private static final String BASELINE_SET = "set";
/** */
private static final String BASELINE_SET_VERSION = "version";
/** Parameter name for validate_indexes command. */
static final String VI_CHECK_FIRST = "checkFirst";
/** Parameter name for validate_indexes command. */
static final String VI_CHECK_THROUGH = "checkThrough";
/** */
static final String WAL_PRINT = "print";
/** */
static final String WAL_DELETE = "delete";
/** */
static final String DELIM = "--------------------------------------------------------------------------------";
/** */
public static final int EXIT_CODE_OK = 0;
/** */
public static final int EXIT_CODE_INVALID_ARGUMENTS = 1;
/** */
public static final int EXIT_CODE_CONNECTION_FAILED = 2;
/** */
public static final int ERR_AUTHENTICATION_FAILED = 3;
/** */
public static final int EXIT_CODE_UNEXPECTED_ERROR = 4;
/** */
private static final long DFLT_PING_INTERVAL = 5000L;
/** */
private static final long DFLT_PING_TIMEOUT = 30_000L;
/** */
private static final Scanner IN = new Scanner(System.in);
/** Validate indexes task name. */
private static final String VALIDATE_INDEXES_TASK = "org.apache.ignite.internal.visor.verify.VisorValidateIndexesTask";
/** */
private static final String TX_LIMIT = "limit";
/** */
private static final String TX_ORDER = "order";
/** */
public static final String CMD_TX_ORDER_START_TIME = "START_TIME";
/** */
private static final String TX_SERVERS = "servers";
/** */
private static final String TX_CLIENTS = "clients";
/** */
private static final String TX_DURATION = "minDuration";
/** */
private static final String TX_SIZE = "minSize";
/** */
private static final String TX_LABEL = "label";
/** */
private static final String TX_NODES = "nodes";
/** */
private static final String TX_XID = "xid";
/** */
private static final String TX_KILL = "kill";
/** */
private Iterator<String> argsIt;
/** */
private String peekedArg;
/** */
private Object lastOperationRes;
/** */
private GridClientConfiguration clientCfg;
/** Check if experimental commands are enabled. Default {@code false}. */
private final boolean enableExperimental = IgniteSystemProperties.getBoolean(IGNITE_ENABLE_EXPERIMENTAL_COMMAND, false);
/**
* Output specified string to console.
*
* @param s String to output.
*/
private void log(String s) {
System.out.println(s);
}
/**
* Provides a prompt, then reads a single line of text from the console.
*
* @param prompt text
* @return A string containing the line read from the console
*/
private String readLine(String prompt) {
System.out.print(prompt);
return IN.nextLine();
}
/**
* Output empty line.
*/
private void nl() {
System.out.println("");
}
/**
* Print error to console.
*
* @param errCode Error code to return.
* @param s Optional message.
* @param e Error to print.
*/
private int error(int errCode, String s, Throwable e) {
if (!F.isEmpty(s))
log(s);
String msg = e.getMessage();
if (F.isEmpty(msg))
msg = e.getClass().getName();
if (msg.startsWith("Failed to handle request")) {
int p = msg.indexOf("err=");
msg = msg.substring(p + 4, msg.length() - 1);
}
log("Error: " + msg);
return errCode;
}
/**
* Requests interactive user confirmation if forthcoming operation is dangerous.
*
* @param args Arguments.
* @return {@code true} if operation confirmed (or not needed), {@code false} otherwise.
*/
private boolean confirm(Arguments args) {
String prompt = confirmationPrompt(args);
if (prompt == null)
return true;
return CONFIRM_MSG.equalsIgnoreCase(readLine(prompt));
}
/**
* @param args Arguments.
* @return Prompt text if confirmation needed, otherwise {@code null}.
*/
private String confirmationPrompt(Arguments args) {
String str = null;
switch (args.command()) {
case DEACTIVATE:
str = "Warning: the command will deactivate a cluster.";
break;
case BASELINE:
if (!BASELINE_COLLECT.equals(args.baselineAction()))
str = "Warning: the command will perform changes in baseline.";
break;
case WAL:
if (WAL_DELETE.equals(args.walAction()))
str = "Warning: the command will delete unused WAL segments.";
break;
case TX:
if (args.transactionArguments().getOperation() == VisorTxOperation.KILL)
str = "Warning: the command will kill some transactions.";
break;
default:
break;
}
return str == null ? null : str + "\nPress '" + CONFIRM_MSG + "' to continue . . . ";
}
/**
* @param rawArgs Arguments.
*/
private void initArgIterator(List<String> rawArgs) {
argsIt = rawArgs.iterator();
peekedArg = null;
}
/**
* @return Returns {@code true} if the iteration has more elements.
*/
private boolean hasNextArg() {
return peekedArg != null || argsIt.hasNext();
}
/**
* Activate cluster.
*
* @param client Client.
* @throws GridClientException If failed to activate.
*/
private void activate(GridClient client) throws Throwable {
try {
GridClientClusterState state = client.state();
state.active(true);
log("Cluster activated");
}
catch (Throwable e) {
log("Failed to activate cluster.");
throw e;
}
}
/**
* Deactivate cluster.
*
* @param client Client.
* @throws Throwable If failed to deactivate.
*/
private void deactivate(GridClient client) throws Throwable {
try {
GridClientClusterState state = client.state();
state.active(false);
log("Cluster deactivated");
}
catch (Throwable e) {
log("Failed to deactivate cluster.");
throw e;
}
}
/**
* Print cluster state.
*
* @param client Client.
* @throws Throwable If failed to print state.
*/
private void state(GridClient client) throws Throwable {
try {
GridClientClusterState state = client.state();
log("Cluster is " + (state.active() ? "active" : "inactive"));
}
catch (Throwable e) {
log("Failed to get cluster state.");
throw e;
}
}
/**
* @param client Client.
* @param taskCls Task class.
* @param taskArgs Task arguments.
* @return Task result.
* @throws GridClientException If failed to execute task.
*/
private <R> R executeTask(GridClient client, Class<? extends ComputeTask<?, R>> taskCls,
Object taskArgs) throws GridClientException {
return executeTaskByNameOnNode(client, taskCls.getName(), taskArgs, null);
}
/**
* @param client Client
* @param taskClsName Task class name.
* @param taskArgs Task args.
* @param nodeId Node ID to execute task at (if null, random node will be chosen by balancer).
* @return Task result.
* @throws GridClientException If failed to execute task.
*/
private <R> R executeTaskByNameOnNode(GridClient client, String taskClsName, Object taskArgs, UUID nodeId
) throws GridClientException {
GridClientCompute compute = client.compute();
if (nodeId == BROADCAST_UUID) {
Collection<GridClientNode> nodes = compute.nodes(GridClientNode::connectable);
if (F.isEmpty(nodes))
throw new GridClientDisconnectedException("Connectable nodes not found", null);
List<UUID> nodeIds = nodes.stream()
.map(GridClientNode::nodeId)
.collect(Collectors.toList());
return client.compute().execute(taskClsName, new VisorTaskArgument<>(nodeIds, taskArgs, false));
}
GridClientNode node = null;
if (nodeId == null) {
Collection<GridClientNode> nodes = compute.nodes(GridClientNode::connectable);
// Prefer node from connect string.
String origAddr = clientCfg.getServers().iterator().next();
for (GridClientNode clientNode : nodes) {
Iterator<String> it = F.concat(clientNode.tcpAddresses().iterator(), clientNode.tcpHostNames().iterator());
while (it.hasNext()) {
if (origAddr.equals(it.next() + ":" + clientNode.tcpPort())) {
node = clientNode;
break;
}
}
}
// Otherwise choose random node.
if (node == null)
node = getBalancedNode(compute);
}
else {
for (GridClientNode n : compute.nodes()) {
if (n.connectable() && nodeId.equals(n.nodeId())) {
node = n;
break;
}
}
if (node == null)
throw new IllegalArgumentException("Node with id=" + nodeId + " not found");
}
return compute.projection(node).execute(taskClsName, new VisorTaskArgument<>(node.nodeId(), taskArgs, false));
}
/**
* @param compute instance
* @return balanced node
*/
private GridClientNode getBalancedNode(GridClientCompute compute) throws GridClientException {
Collection<GridClientNode> nodes = compute.nodes(GridClientNode::connectable);
if (F.isEmpty(nodes))
throw new GridClientDisconnectedException("Connectable node not found", null);
return compute.balancer().balancedNode(nodes);
}
/**
* Executes --cache subcommand.
*
* @param client Client.
* @param cacheArgs Cache args.
*/
private void cache(GridClient client, CacheArguments cacheArgs) throws Throwable {
switch (cacheArgs.command()) {
case HELP:
printCacheHelp();
break;
case IDLE_VERIFY:
cacheIdleVerify(client, cacheArgs);
break;
case VALIDATE_INDEXES:
cacheValidateIndexes(client, cacheArgs);
break;
case CONTENTION:
cacheContention(client, cacheArgs);
break;
case DISTRIBUTION:
cacheDistribution(client, cacheArgs);
break;
case RESET_LOST_PARTITIONS:
cacheResetLostPartitions(client, cacheArgs);
break;
default:
cacheView(client, cacheArgs);
break;
}
}
/**
*
*/
private void printCacheHelp() {
log("--cache subcommand allows to do the following operations:");
usage(" Show information about caches, groups or sequences that match a regex:", CACHE, " list regexPattern [groups|seq] [nodeId]");
usage(" Show hot keys that are point of contention for multiple transactions:", CACHE, " contention minQueueSize [nodeId] [maxPrint]");
usage(" Verify partition counters and hashes between primary and backups on idle cluster:", CACHE, " idle_verify [--dump] [--skipZeros] [cache1,...,cacheN]");
usage(" Validate custom indexes on idle cluster:", CACHE, " validate_indexes [cache1,...,cacheN] [nodeId] [checkFirst|checkThrough]");
usage(" Collect partition distribution information:", CACHE, " distribution nodeId|null [cacheName1,...,cacheNameN] [--user-attributes attributeName1[,...,attributeNameN]]");
usage(" Reset lost partitions:", CACHE, " reset_lost_partitions cacheName1[,...,cacheNameN]");
log(" If [nodeId] is not specified, contention and validate_indexes commands will be broadcasted to all server nodes.");
log(" Another commands where [nodeId] is optional will run on a random server node.");
log(" checkFirst numeric parameter for validate_indexes specifies number of first K keys to be validated.");
log(" checkThrough numeric parameter for validate_indexes allows to check each Kth key.");
nl();
}
/**
* @param client Client.
* @param cacheArgs Cache args.
*/
private void cacheContention(GridClient client, CacheArguments cacheArgs) throws GridClientException {
VisorContentionTaskArg taskArg = new VisorContentionTaskArg(
cacheArgs.minQueueSize(), cacheArgs.maxPrint());
UUID nodeId = cacheArgs.nodeId() == null ? BROADCAST_UUID : cacheArgs.nodeId();
VisorContentionTaskResult res = executeTaskByNameOnNode(
client, VisorContentionTask.class.getName(), taskArg, nodeId);
if (!F.isEmpty(res.exceptions())) {
log("Contention check failed on nodes:");
for (Map.Entry<UUID, Exception> e : res.exceptions().entrySet()) {
log("Node ID = " + e.getKey());
log("Exception message:");
log(e.getValue().getMessage());
nl();
}
}
for (ContentionInfo info : res.getInfos())
info.print();
}
/**
* @param client Client.
* @param cacheArgs Cache args.
*/
private void cacheValidateIndexes(GridClient client, CacheArguments cacheArgs) throws GridClientException {
VisorValidateIndexesTaskArg taskArg = new VisorValidateIndexesTaskArg(
cacheArgs.caches(),
cacheArgs.checkFirst(),
cacheArgs.checkThrough()
);
UUID nodeId = cacheArgs.nodeId() == null ? BROADCAST_UUID : cacheArgs.nodeId();
VisorValidateIndexesTaskResult taskRes = executeTaskByNameOnNode(
client, VALIDATE_INDEXES_TASK, taskArg, nodeId);
if (!F.isEmpty(taskRes.exceptions())) {
log("Index validation failed on nodes:");
for (Map.Entry<UUID, Exception> e : taskRes.exceptions().entrySet()) {
log("Node ID = " + e.getKey());
log("Exception message:");
log(e.getValue().getMessage());
nl();
}
}
boolean errors = false;
for (Map.Entry<UUID, VisorValidateIndexesJobResult> nodeEntry : taskRes.results().entrySet()) {
Map<PartitionKey, ValidateIndexesPartitionResult> partRes = nodeEntry.getValue().partitionResult();
for (Map.Entry<PartitionKey, ValidateIndexesPartitionResult> e : partRes.entrySet()) {
ValidateIndexesPartitionResult res = e.getValue();
if (!res.issues().isEmpty()) {
errors = true;
log(e.getKey().toString() + " " + e.getValue().toString());
for (IndexValidationIssue is : res.issues())
log(is.toString());
}
}
Map<String, ValidateIndexesPartitionResult> idxRes = nodeEntry.getValue().indexResult();
for (Map.Entry<String, ValidateIndexesPartitionResult> e : idxRes.entrySet()) {
ValidateIndexesPartitionResult res = e.getValue();
if (!res.issues().isEmpty()) {
errors = true;
log("SQL Index " + e.getKey() + " " + e.getValue().toString());
for (IndexValidationIssue is : res.issues())
log(is.toString());
}
}
}
if (!errors)
log("validate_indexes has finished, no issues found.");
else
log("validate_indexes has finished with errors (listed above).");
}
/**
* @param client Client.
* @param cacheArgs Cache args.
*/
private void cacheView(GridClient client, CacheArguments cacheArgs) throws GridClientException {
VisorViewCacheTaskArg taskArg = new VisorViewCacheTaskArg(cacheArgs.regex(), cacheArgs.cacheCommand());
VisorViewCacheTaskResult res = executeTaskByNameOnNode(
client, VisorViewCacheTask.class.getName(), taskArg, cacheArgs.nodeId());
for (CacheInfo info : res.cacheInfos())
info.print(cacheArgs.cacheCommand());
}
/**
* Executes appropriate version of idle_verify check. Old version will be used if there are old nodes in the
* cluster.
*
* @param client Client.
* @param cacheArgs Cache args.
*/
private void cacheIdleVerify(GridClient client, CacheArguments cacheArgs) throws GridClientException {
Collection<GridClientNode> nodes = client.compute().nodes(GridClientNode::connectable);
boolean idleVerifyV2 = true;
for (GridClientNode node : nodes) {
String nodeVerStr = node.attribute(IgniteNodeAttributes.ATTR_BUILD_VER);
IgniteProductVersion nodeVer = IgniteProductVersion.fromString(nodeVerStr);
if (nodeVer.compareTo(VerifyBackupPartitionsTaskV2.V2_SINCE_VER) < 0) {
idleVerifyV2 = false;
break;
}
}
if (cacheArgs.dump())
cacheIdleVerifyDump(client, cacheArgs);
else if (idleVerifyV2)
cacheIdleVerifyV2(client, cacheArgs);
else
legacyCacheIdleVerify(client, cacheArgs);
}
/**
* @param client Client.
* @param cacheArgs Cache args.
*/
private void legacyCacheIdleVerify(GridClient client, CacheArguments cacheArgs) throws GridClientException {
VisorIdleVerifyTaskResult res = executeTask(
client, VisorIdleVerifyTask.class, new VisorIdleVerifyTaskArg(cacheArgs.caches()));
Map<PartitionKey, List<PartitionHashRecord>> conflicts = res.getConflicts();
if (conflicts.isEmpty()) {
log("idle_verify check has finished, no conflicts have been found.");
nl();
}
else {
log("idle_verify check has finished, found " + conflicts.size() + " conflict partitions.");
nl();
for (Map.Entry<PartitionKey, List<PartitionHashRecord>> entry : conflicts.entrySet()) {
log("Conflict partition: " + entry.getKey());
log("Partition instances: " + entry.getValue());
}
}
}
/**
* @param client Client.
* @param cacheArgs Cache args.
*/
private void cacheDistribution(GridClient client, CacheArguments cacheArgs) throws GridClientException {
CacheDistributionTaskArg taskArg = new CacheDistributionTaskArg(cacheArgs.caches(), cacheArgs.getUserAttributes());
UUID nodeId = cacheArgs.nodeId() == null ? BROADCAST_UUID : cacheArgs.nodeId();
CacheDistributionTaskResult res = executeTaskByNameOnNode(client, CacheDistributionTask.class.getName(), taskArg, nodeId);
res.print(System.out);
}
/**
* @param client Client.
* @param cacheArgs Cache args.
*/
private void cacheResetLostPartitions(GridClient client, CacheArguments cacheArgs) throws GridClientException {
CacheResetLostPartitionsTaskArg taskArg = new CacheResetLostPartitionsTaskArg(cacheArgs.caches());
CacheResetLostPartitionsTaskResult res = executeTaskByNameOnNode(client, CacheResetLostPartitionsTask.class.getName(), taskArg, null);
res.print(System.out);
}
/**
* @param client Client.
* @param cacheArgs Cache args.
*/
private void cacheIdleVerifyDump(GridClient client, CacheArguments cacheArgs) throws GridClientException {
String path = executeTask(
client,
VisorIdleVerifyDumpTask.class,
new VisorIdleVerifyDumpTaskArg(cacheArgs.caches(), cacheArgs.isSkipZeros())
);
log("VisorIdleVerifyDumpTask successfully written output to '" + path + "'");
}
/**
* @param client Client.
* @param cacheArgs Cache args.
*/
private void cacheIdleVerifyV2(GridClient client, CacheArguments cacheArgs) throws GridClientException {
IdleVerifyResultV2 res = executeTask(
client, VisorIdleVerifyTaskV2.class, new VisorIdleVerifyTaskArg(cacheArgs.caches()));
res.print(System.out::print);
}
/**
* Change baseline.
*
* @param client Client.
* @param baselineAct Baseline action to execute. @throws GridClientException If failed to execute baseline
* action.
* @param baselineArgs Baseline action arguments.
* @throws Throwable If failed to execute baseline action.
*/
private void baseline(GridClient client, String baselineAct, String baselineArgs) throws Throwable {
switch (baselineAct) {
case BASELINE_ADD:
baselineAdd(client, baselineArgs);
break;
case BASELINE_REMOVE:
baselineRemove(client, baselineArgs);
break;
case BASELINE_SET:
baselineSet(client, baselineArgs);
break;
case BASELINE_SET_VERSION:
baselineVersion(client, baselineArgs);
break;
case BASELINE_COLLECT:
baselinePrint(client);
break;
}
}
/**
* Prepare task argument.
*
* @param op Operation.
* @param s Argument from command line.
* @return Task argument.
*/
private VisorBaselineTaskArg arg(VisorBaselineOperation op, String s) {
switch (op) {
case ADD:
case REMOVE:
case SET:
List<String> consistentIds = getConsistentIds(s);
return new VisorBaselineTaskArg(op, -1, consistentIds);
case VERSION:
try {
long topVer = Long.parseLong(s);
return new VisorBaselineTaskArg(op, topVer, null);
}
catch (NumberFormatException e) {
throw new IllegalArgumentException("Invalid topology version: " + s, e);
}
default:
return new VisorBaselineTaskArg(op, -1, null);
}
}
/**
* @param s String of consisted ids delimited by comma.
* @return List of consistent ids.
*/
private List<String> getConsistentIds(String s) {
if (F.isEmpty(s))
throw new IllegalArgumentException("Empty list of consistent IDs");
List<String> consistentIds = new ArrayList<>();
for (String consistentId : s.split(","))
consistentIds.add(consistentId.trim());
return consistentIds;
}
/**
* Print baseline topology.
*
* @param res Task result with baseline topology.
*/
private void baselinePrint0(VisorBaselineTaskResult res) {
log("Cluster state: " + (res.isActive() ? "active" : "inactive"));
log("Current topology version: " + res.getTopologyVersion());
nl();
Map<String, VisorBaselineNode> baseline = res.getBaseline();
Map<String, VisorBaselineNode> srvs = res.getServers();
if (F.isEmpty(baseline))
log("Baseline nodes not found.");
else {
log("Baseline nodes:");
for (VisorBaselineNode node : baseline.values()) {
log(" ConsistentID=" + node.getConsistentId() + ", STATE=" +
(srvs.containsKey(node.getConsistentId()) ? "ONLINE" : "OFFLINE"));
}
log(DELIM);
log("Number of baseline nodes: " + baseline.size());
nl();
List<VisorBaselineNode> others = new ArrayList<>();
for (VisorBaselineNode node : srvs.values()) {
if (!baseline.containsKey(node.getConsistentId()))
others.add(node);
}
if (F.isEmpty(others))
log("Other nodes not found.");
else {
log("Other nodes:");
for (VisorBaselineNode node : others)
log(" ConsistentID=" + node.getConsistentId());
log("Number of other nodes: " + others.size());
}
}
}
/**
* Print current baseline.
*
* @param client Client.
*/
private void baselinePrint(GridClient client) throws GridClientException {
VisorBaselineTaskResult res = executeTask(client, VisorBaselineTask.class, arg(COLLECT, ""));
baselinePrint0(res);
}
/**
* Add nodes to baseline.
*
* @param client Client.
* @param baselineArgs Baseline action arguments.
* @throws Throwable If failed to add nodes to baseline.
*/
private void baselineAdd(GridClient client, String baselineArgs) throws Throwable {
try {
VisorBaselineTaskResult res = executeTask(client, VisorBaselineTask.class, arg(ADD, baselineArgs));
baselinePrint0(res);
}
catch (Throwable e) {
log("Failed to add nodes to baseline.");
throw e;
}
}
/**
* Remove nodes from baseline.
*
* @param client Client.
* @param consistentIds Consistent IDs.
* @throws Throwable If failed to remove nodes from baseline.
*/
private void baselineRemove(GridClient client, String consistentIds) throws Throwable {
try {
VisorBaselineTaskResult res = executeTask(client, VisorBaselineTask.class, arg(REMOVE, consistentIds));
baselinePrint0(res);
}
catch (Throwable e) {
log("Failed to remove nodes from baseline.");
throw e;
}
}
/**
* Set baseline.
*
* @param client Client.
* @param consistentIds Consistent IDs.
* @throws Throwable If failed to set baseline.
*/
private void baselineSet(GridClient client, String consistentIds) throws Throwable {
try {
VisorBaselineTaskResult res = executeTask(client, VisorBaselineTask.class, arg(SET, consistentIds));
baselinePrint0(res);
}
catch (Throwable e) {
log("Failed to set baseline.");
throw e;
}
}
/**
* Set baseline by topology version.
*
* @param client Client.
* @param arg Argument from command line.
*/
private void baselineVersion(GridClient client, String arg) throws GridClientException {
try {
VisorBaselineTaskResult res = executeTask(client, VisorBaselineTask.class, arg(VERSION, arg));
baselinePrint0(res);
}
catch (Throwable e) {
log("Failed to set baseline with specified topology version.");
throw e;
}
}
/**
* Dump transactions information.
*
* @param client Client.
* @param arg Transaction search arguments
*/
private void transactions(GridClient client, VisorTxTaskArg arg) throws GridClientException {
try {
Map<ClusterNode, VisorTxTaskResult> res = executeTask(client, VisorTxTask.class, arg);
lastOperationRes = res;
if (res.isEmpty())
log("Nothing found.");
else if (arg.getOperation() == VisorTxOperation.KILL)
log("Killed transactions:");
else
log("Matching transactions:");
for (Map.Entry<ClusterNode, VisorTxTaskResult> entry : res.entrySet()) {
if (entry.getValue().getInfos().isEmpty())
continue;
ClusterNode key = entry.getKey();
log(key.getClass().getSimpleName() + " [id=" + key.id() +
", addrs=" + key.addresses() +
", order=" + key.order() +
", ver=" + key.version() +
", isClient=" + key.isClient() +
", consistentId=" + key.consistentId() +
"]");
for (VisorTxInfo info : entry.getValue().getInfos())
log(info.toUserString());
}
}
catch (Throwable e) {
log("Failed to perform operation.");
throw e;
}
}
/**
* Execute WAL command.
*
* @param client Client.
* @param walAct WAL action to execute.
* @param walArgs WAL args.
* @throws Throwable If failed to execute wal action.
*/
private void wal(GridClient client, String walAct, String walArgs) throws Throwable {
switch (walAct) {
case WAL_DELETE:
deleteUnusedWalSegments(client, walArgs);
break;
case WAL_PRINT:
default:
printUnusedWalSegments(client, walArgs);
break;
}
}
/**
* Execute delete unused WAL segments task.
*
* @param client Client.
* @param walArgs WAL args.
*/
private void deleteUnusedWalSegments(GridClient client, String walArgs) throws Throwable {
VisorWalTaskResult res = executeTask(client, VisorWalTask.class,
walArg(VisorWalTaskOperation.DELETE_UNUSED_WAL_SEGMENTS, walArgs));
printDeleteWalSegments0(res);
}
/**
* Execute print unused WAL segments task.
*
* @param client Client.
* @param walArgs Wal args.
*/
private void printUnusedWalSegments(GridClient client, String walArgs) throws Throwable {
VisorWalTaskResult res = executeTask(client, VisorWalTask.class,
walArg(VisorWalTaskOperation.PRINT_UNUSED_WAL_SEGMENTS, walArgs));
printUnusedWalSegments0(res);
}
/**
* Prepare WAL task argument.
*
* @param op Operation.
* @param s Argument from command line.
* @return Task argument.
*/
private VisorWalTaskArg walArg(VisorWalTaskOperation op, String s) {
List<String> consistentIds = null;
if (!F.isEmpty(s)) {
consistentIds = new ArrayList<>();
for (String consistentId : s.split(","))
consistentIds.add(consistentId.trim());
}
switch (op) {
case DELETE_UNUSED_WAL_SEGMENTS:
case PRINT_UNUSED_WAL_SEGMENTS:
return new VisorWalTaskArg(op, consistentIds);
default:
return new VisorWalTaskArg(VisorWalTaskOperation.PRINT_UNUSED_WAL_SEGMENTS, consistentIds);
}
}
/**
* Print list of unused wal segments.
*
* @param taskRes Task result with baseline topology.
*/
private void printUnusedWalSegments0(VisorWalTaskResult taskRes) {
log("Unused wal segments per node:");
nl();
Map<String, Collection<String>> res = taskRes.results();
Map<String, Exception> failRes = taskRes.exceptions();
Map<String, VisorClusterNode> nodesInfo = taskRes.getNodesInfo();
for (Map.Entry<String, Collection<String>> entry : res.entrySet()) {
VisorClusterNode node = nodesInfo.get(entry.getKey());
log("Node=" + node.getConsistentId());
log(" addresses " + U.addressesAsString(node.getAddresses(), node.getHostNames()));
for (String fileName : entry.getValue())
log(" " + fileName);
nl();
}
for (Map.Entry<String, Exception> entry : failRes.entrySet()) {
VisorClusterNode node = nodesInfo.get(entry.getKey());
log("Node=" + node.getConsistentId());
log(" addresses " + U.addressesAsString(node.getAddresses(), node.getHostNames()));
log(" failed with error: " + entry.getValue().getMessage());
nl();
}
}
/**
* Print list of unused wal segments.
*
* @param taskRes Task result with baseline topology.
*/
private void printDeleteWalSegments0(VisorWalTaskResult taskRes) {
log("WAL segments deleted for nodes:");
nl();
Map<String, Collection<String>> res = taskRes.results();
Map<String, Exception> errors = taskRes.exceptions();
Map<String, VisorClusterNode> nodesInfo = taskRes.getNodesInfo();
for (Map.Entry<String, Collection<String>> entry : res.entrySet()) {
VisorClusterNode node = nodesInfo.get(entry.getKey());
log("Node=" + node.getConsistentId());
log(" addresses " + U.addressesAsString(node.getAddresses(), node.getHostNames()));
nl();
}
for (Map.Entry<String, Exception> entry : errors.entrySet()) {
VisorClusterNode node = nodesInfo.get(entry.getKey());
log("Node=" + node.getConsistentId());
log(" addresses " + U.addressesAsString(node.getAddresses(), node.getHostNames()));
log(" failed with error: " + entry.getValue().getMessage());
nl();
}
}
/**
* @param e Exception to check.
* @return {@code true} if specified exception is {@link GridClientAuthenticationException}.
*/
private boolean isAuthError(Throwable e) {
return X.hasCause(e, GridClientAuthenticationException.class);
}
/**
* @param e Exception to check.
* @return {@code true} if specified exception is a connection error.
*/
private boolean isConnectionError(Throwable e) {
return e instanceof GridClientClosedException ||
e instanceof GridClientConnectionResetException ||
e instanceof GridClientDisconnectedException ||
e instanceof GridClientHandshakeException ||
e instanceof GridServerUnreachableException;
}
/**
* Print command usage.
*
* @param desc Command description.
* @param args Arguments.
*/
private void usage(String desc, Command cmd, String... args) {
log(desc);
log(" control.sh [--host HOST_OR_IP] [--port PORT] [--user USER] [--password PASSWORD] " +
" [--ping-interval PING_INTERVAL] [--ping-timeout PING_TIMEOUT] " + cmd.text() + String.join("", args));
nl();
}
/**
* Extract next argument.
*
* @param err Error message.
* @return Next argument value.
*/
private String nextArg(String err) {
if (peekedArg != null) {
String res = peekedArg;
peekedArg = null;
return res;
}
if (argsIt.hasNext())
return argsIt.next();
throw new IllegalArgumentException(err);
}
/**
* Returns the next argument in the iteration, without advancing the iteration.
*
* @return Next argument value or {@code null} if no next argument.
*/
private String peekNextArg() {
if (peekedArg == null && argsIt.hasNext())
peekedArg = argsIt.next();
return peekedArg;
}
/**
* Parses and validates arguments.
*
* @param rawArgs Array of arguments.
* @return Arguments bean.
* @throws IllegalArgumentException In case arguments aren't valid.
*/
Arguments parseAndValidate(List<String> rawArgs) {
String host = DFLT_HOST;
String port = DFLT_PORT;
String user = null;
String pwd = null;
String baselineAct = "";
String baselineArgs = "";
Long pingInterval = DFLT_PING_INTERVAL;
Long pingTimeout = DFLT_PING_TIMEOUT;
String walAct = "";
String walArgs = "";
boolean autoConfirmation = false;
CacheArguments cacheArgs = null;
List<Command> commands = new ArrayList<>();
initArgIterator(rawArgs);
VisorTxTaskArg txArgs = null;
while (hasNextArg()) {
String str = nextArg("").toLowerCase();
Command cmd = Command.of(str);
if (cmd != null) {
switch (cmd) {
case ACTIVATE:
case DEACTIVATE:
case STATE:
commands.add(cmd);
break;
case TX:
commands.add(TX);
txArgs = parseTransactionArguments();
break;
case BASELINE:
commands.add(BASELINE);
baselineAct = BASELINE_COLLECT; //default baseline action
str = peekNextArg();
if (str != null) {
str = str.toLowerCase();
if (BASELINE_ADD.equals(str) || BASELINE_REMOVE.equals(str) ||
BASELINE_SET.equals(str) || BASELINE_SET_VERSION.equals(str)) {
baselineAct = nextArg("Expected baseline action");
baselineArgs = nextArg("Expected baseline arguments");
}
}
break;
case CACHE:
commands.add(CACHE);
cacheArgs = parseAndValidateCacheArgs();
break;
case WAL:
if (!enableExperimental)
throw new IllegalArgumentException("Experimental command is disabled.");
commands.add(WAL);
str = nextArg("Expected arguments for " + WAL.text());
walAct = str.toLowerCase();
if (WAL_PRINT.equals(walAct) || WAL_DELETE.equals(walAct))
walArgs = (str = peekNextArg()) != null && !isCommandOrOption(str)
? nextArg("Unexpected argument for " + WAL.text() + ": " + walAct)
: "";
else
throw new IllegalArgumentException("Unexpected action " + walAct + " for " + WAL.text());
break;
default:
throw new IllegalArgumentException("Unexpected command: " + str);
}
}
else {
switch (str) {
case CMD_HOST:
host = nextArg("Expected host name");
break;
case CMD_PORT:
port = nextArg("Expected port number");
try {
int p = Integer.parseInt(port);
if (p <= 0 || p > 65535)
throw new IllegalArgumentException("Invalid value for port: " + port);
}
catch (NumberFormatException ignored) {
throw new IllegalArgumentException("Invalid value for port: " + port);
}
break;
case CMD_PING_INTERVAL:
pingInterval = getPingParam("Expected ping interval", "Invalid value for ping interval");
break;
case CMD_PING_TIMEOUT:
pingTimeout = getPingParam("Expected ping timeout", "Invalid value for ping timeout");
break;
case CMD_USER:
user = nextArg("Expected user name");
break;
case CMD_PASSWORD:
pwd = nextArg("Expected password");
break;
case CMD_AUTO_CONFIRMATION:
autoConfirmation = true;
break;
default:
throw new IllegalArgumentException("Unexpected argument: " + str);
}
}
}
int sz = commands.size();
if (sz < 1)
throw new IllegalArgumentException("No action was specified");
if (sz > 1)
throw new IllegalArgumentException("Only one action can be specified, but found: " + sz);
Command cmd = commands.get(0);
boolean hasUsr = F.isEmpty(user);
boolean hasPwd = F.isEmpty(pwd);
if (hasUsr != hasPwd)
throw new IllegalArgumentException("Both user and password should be specified");
return new Arguments(cmd, host, port, user, pwd, baselineAct, baselineArgs, txArgs, cacheArgs, walAct, walArgs,
pingTimeout, pingInterval, autoConfirmation);
}
/**
* Parses and validates cache arguments.
*
* @return --cache subcommand arguments in case validation is successful.
*/
private CacheArguments parseAndValidateCacheArgs() {
if (!hasNextCacheArg()) {
throw new IllegalArgumentException("Arguments are expected for --cache subcommand, " +
"run --cache help for more info.");
}
CacheArguments cacheArgs = new CacheArguments();
String str = nextArg("").toLowerCase();
CacheCommand cmd = CacheCommand.of(str);
if (cmd == null)
cmd = CacheCommand.HELP;
cacheArgs.command(cmd);
switch (cmd) {
case HELP:
break;
case IDLE_VERIFY:
int idleVerifyArgsCnt = 3;
while (hasNextCacheArg() && idleVerifyArgsCnt-- > 0) {
String nextArg = nextArg("");
if (CMD_DUMP.equals(nextArg))
cacheArgs.dump(true);
else if (CMD_SKIP_ZEROS.equals(nextArg))
cacheArgs.skipZeros(true);
else
parseCacheNames(nextArg, cacheArgs);
}
break;
case CONTENTION:
cacheArgs.minQueueSize(Integer.parseInt(nextArg("Min queue size expected")));
if (hasNextCacheArg())
cacheArgs.nodeId(UUID.fromString(nextArg("")));
if (hasNextCacheArg())
cacheArgs.maxPrint(Integer.parseInt(nextArg("")));
else
cacheArgs.maxPrint(10);
break;
case VALIDATE_INDEXES:
int argsCnt = 0;
while (hasNextCacheArg() && argsCnt++ < 4) {
String arg = nextArg("");
if (VI_CHECK_FIRST.equals(arg) || VI_CHECK_THROUGH.equals(arg)) {
if (!hasNextCacheArg())
throw new IllegalArgumentException("Numeric value for '" + arg + "' parameter expected.");
int numVal;
String numStr = nextArg("");
try {
numVal = Integer.parseInt(numStr);
}
catch (IllegalArgumentException e) {
throw new IllegalArgumentException(
"Not numeric value was passed for '"
+ arg
+ "' parameter: "
+ numStr
);
}
if (numVal <= 0)
throw new IllegalArgumentException("Value for '" + arg + "' property should be positive.");
if (VI_CHECK_FIRST.equals(arg))
cacheArgs.checkFirst(numVal);
else
cacheArgs.checkThrough(numVal);
continue;
}
try {
cacheArgs.nodeId(UUID.fromString(arg));
continue;
}
catch (IllegalArgumentException ignored) {
//No-op.
}
parseCacheNames(arg, cacheArgs);
}
break;
case DISTRIBUTION:
String nodeIdStr = nextArg("Node id expected or null");
if (!"null".equals(nodeIdStr))
cacheArgs.nodeId(UUID.fromString(nodeIdStr));
while (hasNextCacheArg()) {
String nextArg = nextArg("");
if (CMD_USER_ATTRIBUTES.equals(nextArg)){
nextArg = nextArg("User attributes are expected to be separated by commas");
Set<String> userAttributes = new HashSet();
for (String userAttribute:nextArg.split(","))
userAttributes.add(userAttribute.trim());
cacheArgs.setUserAttributes(userAttributes);
nextArg = (hasNextCacheArg()) ? nextArg("") : null;
}
if (nextArg!=null)
parseCacheNames(nextArg, cacheArgs);
}
break;
case RESET_LOST_PARTITIONS:
parseCacheNames(nextArg("Cache name expected"), cacheArgs);
break;
default:
cacheArgs.regex(nextArg("Regex is expected"));
if (hasNextCacheArg()) {
String tmp = nextArg("");
switch (tmp) {
case "groups":
cacheArgs.cacheCommand(GROUPS);
break;
case "seq":
cacheArgs.cacheCommand(SEQ);
break;
default:
cacheArgs.nodeId(UUID.fromString(tmp));
}
}
break;
}
if (hasNextCacheArg())
throw new IllegalArgumentException("Unexpected argument of --cache subcommand: " + peekNextArg());
return cacheArgs;
}
/**
* @return <code>true</code> if there's next argument for --cache subcommand.
*/
private boolean hasNextCacheArg() {
return hasNextArg() && Command.of(peekNextArg()) == null && !AUX_COMMANDS.contains(peekNextArg());
}
/**
* @param cacheArgs Cache args.
*/
private void parseCacheNames(String cacheNames, CacheArguments cacheArgs) {
String[] cacheNamesArr = cacheNames.split(",");
Set<String> cacheNamesSet = new HashSet<>();
for (String cacheName : cacheNamesArr) {
if (F.isEmpty(cacheName))
throw new IllegalArgumentException("Non-empty cache names expected.");
cacheNamesSet.add(cacheName.trim());
}
cacheArgs.caches(cacheNamesSet);
}
/**
* Get ping param for grid client.
*
* @param nextArgErr Argument extraction error message.
* @param invalidErr Param validation error message.
*/
private Long getPingParam(String nextArgErr, String invalidErr) {
String raw = nextArg(nextArgErr);
try {
long val = Long.valueOf(raw);
if (val <= 0)
throw new IllegalArgumentException(invalidErr + ": " + val);
else
return val;
}
catch (NumberFormatException ignored) {
throw new IllegalArgumentException(invalidErr + ": " + raw);
}
}
/**
* @return Transaction arguments.
*/
private VisorTxTaskArg parseTransactionArguments() {
VisorTxProjection proj = null;
Integer limit = null;
VisorTxSortOrder sortOrder = null;
Long duration = null;
Integer size = null;
String lbRegex = null;
List<String> consistentIds = null;
VisorTxOperation op = VisorTxOperation.LIST;
String xid = null;
boolean end = false;
do {
String str = peekNextArg();
if (str == null)
break;
switch (str) {
case TX_LIMIT:
nextArg("");
limit = (int)nextLongArg(TX_LIMIT);
break;
case TX_ORDER:
nextArg("");
sortOrder = VisorTxSortOrder.fromString(nextArg(TX_ORDER));
break;
case TX_SERVERS:
nextArg("");
proj = VisorTxProjection.SERVER;
break;
case TX_CLIENTS:
nextArg("");
proj = VisorTxProjection.CLIENT;
break;
case TX_NODES:
nextArg("");
consistentIds = getConsistentIds(nextArg(TX_NODES));
break;
case TX_DURATION:
nextArg("");
duration = nextLongArg(TX_DURATION) * 1000L;
break;
case TX_SIZE:
nextArg("");
size = (int)nextLongArg(TX_SIZE);
break;
case TX_LABEL:
nextArg("");
lbRegex = nextArg(TX_LABEL);
try {
Pattern.compile(lbRegex);
}
catch (PatternSyntaxException ignored) {
throw new IllegalArgumentException("Illegal regex syntax");
}
break;
case TX_XID:
nextArg("");
xid = nextArg(TX_XID);
break;
case TX_KILL:
nextArg("");
op = VisorTxOperation.KILL;
break;
default:
end = true;
}
}
while (!end);
if (proj != null && consistentIds != null)
throw new IllegalArgumentException("Projection can't be used together with list of consistent ids.");
return new VisorTxTaskArg(op, limit, duration, size, null, proj, consistentIds, xid, lbRegex, sortOrder);
}
/**
* @return Numeric value.
*/
private long nextLongArg(String lb) {
String str = nextArg("Expecting " + lb);
try {
long val = Long.parseLong(str);
if (val < 0)
throw new IllegalArgumentException("Invalid value for " + lb + ": " + val);
return val;
}
catch (NumberFormatException ignored) {
throw new IllegalArgumentException("Invalid value for " + lb + ": " + str);
}
}
/**
* Check if raw arg is command or option.
*
* @return {@code true} If raw arg is command, overwise {@code false}.
*/
private boolean isCommandOrOption(String raw) {
return raw != null && raw.contains("--");
}
/**
* Parse and execute command.
*
* @param rawArgs Arguments to parse and execute.
* @return Exit code.
*/
public int execute(List<String> rawArgs) {
log("Control utility [ver. " + ACK_VER_STR + "]");
log(COPYRIGHT);
log("User: " + System.getProperty("user.name"));
log(DELIM);
try {
if (F.isEmpty(rawArgs) || (rawArgs.size() == 1 && CMD_HELP.equalsIgnoreCase(rawArgs.get(0)))) {
log("This utility can do the following commands:");
usage(" Activate cluster:", ACTIVATE);
usage(" Deactivate cluster:", DEACTIVATE, " [" + CMD_AUTO_CONFIRMATION + "]");
usage(" Print current cluster state:", STATE);
usage(" Print cluster baseline topology:", BASELINE);
usage(" Add nodes into baseline topology:", BASELINE, " add consistentId1[,consistentId2,....,consistentIdN] [" + CMD_AUTO_CONFIRMATION + "]");
usage(" Remove nodes from baseline topology:", BASELINE, " remove consistentId1[,consistentId2,....,consistentIdN] [" + CMD_AUTO_CONFIRMATION + "]");
usage(" Set baseline topology:", BASELINE, " set consistentId1[,consistentId2,....,consistentIdN] [" + CMD_AUTO_CONFIRMATION + "]");
usage(" Set baseline topology based on version:", BASELINE, " version topologyVersion [" + CMD_AUTO_CONFIRMATION + "]");
usage(" List or kill transactions:", TX, " [xid XID] [minDuration SECONDS] " +
"[minSize SIZE] [label PATTERN_REGEX] [servers|clients] " +
"[nodes consistentId1[,consistentId2,....,consistentIdN] [limit NUMBER] [order DURATION|SIZE|", CMD_TX_ORDER_START_TIME, "] [kill] [" + CMD_AUTO_CONFIRMATION + "]");
if (enableExperimental) {
usage(" Print absolute paths of unused archived wal segments on each node:", WAL,
" print [consistentId1,consistentId2,....,consistentIdN]");
usage(" Delete unused archived wal segments on each node:", WAL,
" delete [consistentId1,consistentId2,....,consistentIdN] [" + CMD_AUTO_CONFIRMATION + "]");
}
log(" View caches information in a cluster. For more details type:");
log(" control.sh --cache help");
nl();
log("By default commands affecting the cluster require interactive confirmation.");
log("Use " + CMD_AUTO_CONFIRMATION + " option to disable it.");
nl();
log("Default values:");
log(" HOST_OR_IP=" + DFLT_HOST);
log(" PORT=" + DFLT_PORT);
log(" PING_INTERVAL=" + DFLT_PING_INTERVAL);
log(" PING_TIMEOUT=" + DFLT_PING_TIMEOUT);
nl();
log("Exit codes:");
log(" " + EXIT_CODE_OK + " - successful execution.");
log(" " + EXIT_CODE_INVALID_ARGUMENTS + " - invalid arguments.");
log(" " + EXIT_CODE_CONNECTION_FAILED + " - connection failed.");
log(" " + ERR_AUTHENTICATION_FAILED + " - authentication failed.");
log(" " + EXIT_CODE_UNEXPECTED_ERROR + " - unexpected error.");
return EXIT_CODE_OK;
}
Arguments args = parseAndValidate(rawArgs);
if (!args.autoConfirmation() && !confirm(args)) {
log("Operation cancelled.");
return EXIT_CODE_OK;
}
clientCfg = new GridClientConfiguration();
clientCfg.setPingInterval(args.pingInterval());
clientCfg.setPingTimeout(args.pingTimeout());
clientCfg.setServers(Collections.singletonList(args.host() + ":" + args.port()));
if (!F.isEmpty(args.user())) {
clientCfg.setSecurityCredentialsProvider(
new SecurityCredentialsBasicProvider(new SecurityCredentials(args.user(), args.password())));
}
try (GridClient client = GridClientFactory.start(clientCfg)) {
switch (args.command()) {
case ACTIVATE:
activate(client);
break;
case DEACTIVATE:
deactivate(client);
break;
case STATE:
state(client);
break;
case BASELINE:
baseline(client, args.baselineAction(), args.baselineArguments());
break;
case TX:
transactions(client, args.transactionArguments());
break;
case CACHE:
cache(client, args.cacheArgs());
break;
case WAL:
wal(client, args.walAction(), args.walArguments());
break;
}
}
return 0;
}
catch (IllegalArgumentException e) {
return error(EXIT_CODE_INVALID_ARGUMENTS, "Check arguments.", e);
}
catch (Throwable e) {
if (isAuthError(e))
return error(ERR_AUTHENTICATION_FAILED, "Authentication error.", e);
if (isConnectionError(e))
return error(EXIT_CODE_CONNECTION_FAILED, "Connection to cluster failed.", e);
return error(EXIT_CODE_UNEXPECTED_ERROR, "", e);
}
}
/**
* @param args Arguments to parse and apply.
*/
public static void main(String[] args) {
CommandHandler hnd = new CommandHandler();
System.exit(hnd.execute(Arrays.asList(args)));
}
/**
* Used for tests.
*
* @return Last operation result;
*/
@SuppressWarnings("unchecked")
public <T> T getLastOperationResult() {
return (T)lastOperationRes;
}
}