blob: fbe89f22ee1fde0101ceb3657766d81a52f7d1dc [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.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import org.apache.ignite.cluster.ClusterNode;
import org.apache.ignite.internal.IgniteFeatures;
import org.apache.ignite.internal.client.GridClient;
import org.apache.ignite.internal.client.GridClientConfiguration;
import org.apache.ignite.internal.client.GridClientException;
import org.apache.ignite.internal.commandline.argument.CommandArgUtils;
import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.visor.tx.FetchNearXidVersionTask;
import org.apache.ignite.internal.visor.tx.TxKeyLockType;
import org.apache.ignite.internal.visor.tx.TxMappingType;
import org.apache.ignite.internal.visor.tx.TxVerboseId;
import org.apache.ignite.internal.visor.tx.TxVerboseInfo;
import org.apache.ignite.internal.visor.tx.TxVerboseKey;
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.transactions.TransactionState;
import static org.apache.ignite.internal.client.util.GridClientUtils.checkFeatureSupportedByCluster;
import static org.apache.ignite.internal.commandline.CommandList.TX;
import static org.apache.ignite.internal.commandline.CommandLogger.DOUBLE_INDENT;
import static org.apache.ignite.internal.commandline.CommandLogger.optional;
import static org.apache.ignite.internal.commandline.CommandLogger.or;
import static org.apache.ignite.internal.commandline.CommonArgParser.CMD_AUTO_CONFIRMATION;
import static org.apache.ignite.internal.commandline.TaskExecutor.executeTask;
import static org.apache.ignite.internal.commandline.TxCommandArg.TX_INFO;
/**
* Transaction commands.
*/
public class TxCommands implements Command<VisorTxTaskArg> {
/** Arguments */
private VisorTxTaskArg args;
/** Logger. */
private Logger logger;
/** {@inheritDoc} */
@Override public void printUsage(Logger logger) {
Command.usage(logger, "List or kill transactions:", TX, getTxOptions());
Command.usage(logger, "Print detailed information (topology and key lock ownership) about specific transaction:",
TX, TX_INFO.argName(), or("<TX identifier as GridCacheVersion [topVer=..., order=..., nodeOrder=...] " +
"(can be found in logs)>", "<TX identifier as UUID (can be retrieved via --tx command)>"));
}
/**
* @return Transaction command options.
*/
private String[] getTxOptions() {
List<String> list = new ArrayList<>();
list.add(optional(TxCommandArg.TX_XID, "XID"));
list.add(optional(TxCommandArg.TX_DURATION, "SECONDS"));
list.add(optional(TxCommandArg.TX_SIZE, "SIZE"));
list.add(optional(TxCommandArg.TX_LABEL, "PATTERN_REGEX"));
list.add(optional(or(TxCommandArg.TX_SERVERS, TxCommandArg.TX_CLIENTS)));
list.add(optional(TxCommandArg.TX_NODES, "consistentId1[,consistentId2,....,consistentIdN]"));
list.add(optional(TxCommandArg.TX_LIMIT, "NUMBER"));
list.add(optional(TxCommandArg.TX_ORDER, or(VisorTxSortOrder.values())));
list.add(optional(TxCommandArg.TX_KILL));
list.add(optional(TX_INFO));
list.add(optional(CMD_AUTO_CONFIRMATION));
return list.toArray(new String[list.size()]);
}
/** {@inheritDoc} */
@Override public VisorTxTaskArg arg() {
return args;
}
/**
* Dump transactions information.
*
* @param clientCfg Client configuration.
*/
@Override public Object execute(GridClientConfiguration clientCfg, Logger logger) throws Exception {
this.logger = logger;
try (GridClient client = Command.startClient(clientCfg)) {
if (args.getOperation() == VisorTxOperation.INFO)
return transactionInfo(client, clientCfg);
Map<ClusterNode, VisorTxTaskResult> res = executeTask(client, VisorTxTask.class, args, clientCfg);
if (res.isEmpty())
logger.info("Nothing found.");
else if (args.getOperation() == VisorTxOperation.KILL)
logger.info("Killed transactions:");
else
logger.info("Matching transactions:");
for (Map.Entry<ClusterNode, VisorTxTaskResult> entry : res.entrySet()) {
if (entry.getValue().getInfos().isEmpty())
continue;
ClusterNode key = entry.getKey();
logger.info(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())
logger.info(info.toUserString());
}
return res;
}
catch (Throwable e) {
logger.severe("Failed to perform operation.");
logger.severe(CommandLogger.errorMessage(e));
throw e;
}
}
/**
* Dump transactions information.
*
* @param client Client.
*/
private void transactions(GridClient client, GridClientConfiguration conf) throws GridClientException {
try {
if (args.getOperation() == VisorTxOperation.INFO) {
transactionInfo(client, conf);
return;
}
Map<ClusterNode, VisorTxTaskResult> res = executeTask(client, VisorTxTask.class, args, conf);
for (Map.Entry<ClusterNode, VisorTxTaskResult> entry : res.entrySet()) {
if (entry.getValue().getInfos().isEmpty())
continue;
ClusterNode key = entry.getKey();
logger.info(nodeDescription(key));
for (VisorTxInfo info : entry.getValue().getInfos())
logger.info(info.toUserString());
}
}
catch (Throwable e) {
logger.severe("Failed to perform operation.");
throw e;
}
}
/** {@inheritDoc} */
@Override public String confirmationPrompt() {
if (args != null && args.getOperation() == VisorTxOperation.KILL)
return "Warning: the command will kill some transactions.";
return null;
}
/**
* @param argIter Argument iterator.
*/
@Override public void parseArguments(CommandArgIterator argIter) {
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;
TxVerboseId txVerboseId = null;
while (true) {
String str = argIter.peekNextArg();
if (str == null)
break;
TxCommandArg arg = CommandArgUtils.of(str, TxCommandArg.class);
if (arg == null)
break;
switch (arg) {
case TX_LIMIT:
argIter.nextArg("");
limit = (int)argIter.nextNonNegativeLongArg(TxCommandArg.TX_LIMIT.toString());
break;
case TX_ORDER:
argIter.nextArg("");
sortOrder = VisorTxSortOrder.valueOf(argIter.nextArg(TxCommandArg.TX_ORDER.toString()).toUpperCase());
break;
case TX_SERVERS:
argIter.nextArg("");
proj = VisorTxProjection.SERVER;
break;
case TX_CLIENTS:
argIter.nextArg("");
proj = VisorTxProjection.CLIENT;
break;
case TX_NODES:
argIter.nextArg("");
Set<String> ids = argIter.nextStringSet(TxCommandArg.TX_NODES.toString());
if (ids.isEmpty()) {
throw new IllegalArgumentException("Consistent id list is empty.");
}
consistentIds = new ArrayList<>(ids);
break;
case TX_DURATION:
argIter.nextArg("");
duration = argIter.nextNonNegativeLongArg(TxCommandArg.TX_DURATION.toString()) * 1000L;
break;
case TX_SIZE:
argIter.nextArg("");
size = (int)argIter.nextNonNegativeLongArg(TxCommandArg.TX_SIZE.toString());
break;
case TX_LABEL:
argIter.nextArg("");
lbRegex = argIter.nextArg(TxCommandArg.TX_LABEL.toString());
try {
Pattern.compile(lbRegex);
}
catch (PatternSyntaxException ignored) {
throw new IllegalArgumentException("Illegal regex syntax");
}
break;
case TX_XID:
argIter.nextArg("");
xid = argIter.nextArg(TxCommandArg.TX_XID.toString());
break;
case TX_KILL:
argIter.nextArg("");
op = VisorTxOperation.KILL;
break;
case TX_INFO:
argIter.nextArg("");
op = VisorTxOperation.INFO;
txVerboseId = TxVerboseId.fromString(argIter.nextArg(TX_INFO.argName()));
break;
default:
throw new AssertionError();
}
}
if (proj != null && consistentIds != null)
throw new IllegalArgumentException("Projection can't be used together with list of consistent ids.");
this.args = new VisorTxTaskArg(op, limit, duration, size, null, proj,
consistentIds, xid, lbRegex, sortOrder, txVerboseId);
}
/**
* Provides text descrition of a cluster node.
*
* @param node Node.
*/
private static String nodeDescription(ClusterNode node) {
return node.getClass().getSimpleName() + " [id=" + node.id() +
", addrs=" + node.addresses() +
", order=" + node.order() +
", ver=" + node.version() +
", isClient=" + node.isClient() +
", consistentId=" + node.consistentId() +
"]";
}
/**
* Executes --tx --info command.
*
* @param client Client.
*/
private Object transactionInfo(GridClient client, GridClientConfiguration conf) throws GridClientException {
checkFeatureSupportedByCluster(client, IgniteFeatures.TX_INFO_COMMAND, true, true);
GridCacheVersion nearXidVer = executeTask(client, FetchNearXidVersionTask.class, args.txInfoArgument(), conf);
boolean histMode = false;
if (nearXidVer != null) {
logger.info("Resolved transaction near XID version: " + nearXidVer);
args.txInfoArgument(new TxVerboseId(null, nearXidVer));
}
else {
logger.info("Active transactions not found.");
if (args.txInfoArgument().gridCacheVersion() != null) {
logger.info("Will try to peek history to find out whether transaction was committed / rolled back.");
histMode = true;
}
else {
logger.info("You can specify transaction in GridCacheVersion format in order to peek history " +
"to find out whether transaction was committed / rolled back.");
return null;
}
}
Map<ClusterNode, VisorTxTaskResult> res = executeTask(client, VisorTxTask.class, args, conf);
if (histMode)
printTxInfoHistoricalResult(res);
else
printTxInfoResult(res);
return res;
}
/**
* Prints result of --tx --info command to output.
*
* @param res Response.
*/
private void printTxInfoResult(Map<ClusterNode, VisorTxTaskResult> res) {
String lb = null;
Map<Integer, String> usedCaches = new HashMap<>();
Map<Integer, String> usedCacheGroups = new HashMap<>();
VisorTxInfo firstInfo = null;
TxVerboseInfo firstVerboseInfo = null;
Set<TransactionState> states = new HashSet<>();
for (Map.Entry<ClusterNode, VisorTxTaskResult> entry : res.entrySet()) {
for (VisorTxInfo info : entry.getValue().getInfos()) {
assert info.getTxVerboseInfo() != null;
if (lb == null)
lb = info.getLabel();
if (firstInfo == null) {
firstInfo = info;
firstVerboseInfo = info.getTxVerboseInfo();
}
usedCaches.putAll(info.getTxVerboseInfo().usedCaches());
usedCacheGroups.putAll(info.getTxVerboseInfo().usedCacheGroups());
states.add(info.getState());
}
}
String indent = "";
logger.info("");
logger.info(indent + "Transaction detailed info:");
printTransactionDetailedInfo(
res, usedCaches, usedCacheGroups, firstInfo, firstVerboseInfo, states, indent + DOUBLE_INDENT);
}
/**
* Prints detailed info about transaction to output.
*
* @param res Response.
* @param usedCaches Used caches.
* @param usedCacheGroups Used cache groups.
* @param firstInfo First info.
* @param firstVerboseInfo First verbose info.
* @param states States.
* @param indent Indent.
*/
private void printTransactionDetailedInfo(Map<ClusterNode, VisorTxTaskResult> res, Map<Integer, String> usedCaches,
Map<Integer, String> usedCacheGroups, VisorTxInfo firstInfo, TxVerboseInfo firstVerboseInfo,
Set<TransactionState> states, String indent) {
logger.info(indent + "Near XID version: " + firstVerboseInfo.nearXidVersion());
logger.info(indent + "Near XID version (UUID): " + firstInfo.getNearXid());
logger.info(indent + "Isolation: " + firstInfo.getIsolation());
logger.info(indent + "Concurrency: " + firstInfo.getConcurrency());
logger.info(indent + "Timeout: " + firstInfo.getTimeout());
logger.info(indent + "Initiator node: " + firstVerboseInfo.nearNodeId());
logger.info(indent + "Initiator node (consistent ID): " + firstVerboseInfo.nearNodeConsistentId());
logger.info(indent + "Label: " + firstInfo.getLabel());
logger.info(indent + "Topology version: " + firstInfo.getTopologyVersion());
logger.info(indent + "Used caches (ID to name): " + usedCaches);
logger.info(indent + "Used cache groups (ID to name): " + usedCacheGroups);
logger.info(indent + "States across the cluster: " + states);
logger.info(indent + "Transaction topology: ");
printTransactionTopology(res, indent + DOUBLE_INDENT);
}
/**
* Prints transaction topology to output.
*
* @param res Response.
* @param indent Indent.
*/
private void printTransactionTopology(Map<ClusterNode, VisorTxTaskResult> res, String indent) {
for (Map.Entry<ClusterNode, VisorTxTaskResult> entry : res.entrySet()) {
logger.info(indent + nodeDescription(entry.getKey()) + ':');
printTransactionMappings(indent + DOUBLE_INDENT, entry);
}
}
/**
* Prints transaction mappings for specific cluster node to output.
*
* @param indent Indent.
* @param entry Entry.
*/
private void printTransactionMappings(String indent, Map.Entry<ClusterNode, VisorTxTaskResult> entry) {
for (VisorTxInfo info : entry.getValue().getInfos()) {
TxVerboseInfo verboseInfo = info.getTxVerboseInfo();
if (verboseInfo != null) {
logger.info(indent + "Mapping [type=" + verboseInfo.txMappingType() + "]:");
printTransactionMapping(indent + DOUBLE_INDENT, info, verboseInfo);
}
else {
logger.info(indent + "Mapping [type=HISTORICAL]:");
logger.info(indent + DOUBLE_INDENT + "State: " + info.getState());
}
}
}
/**
* Prints specific transaction mapping to output.
*
* @param indent Indent.
* @param info Info.
* @param verboseInfo Verbose info.
*/
private void printTransactionMapping(String indent, VisorTxInfo info, TxVerboseInfo verboseInfo) {
logger.info(indent + "XID version (UUID): " + info.getXid());
logger.info(indent + "State: " + info.getState());
if (verboseInfo.txMappingType() == TxMappingType.REMOTE) {
logger.info(indent + "Primary node: " + verboseInfo.dhtNodeId());
logger.info(indent + "Primary node (consistent ID): " + verboseInfo.dhtNodeConsistentId());
}
if (!F.isEmpty(verboseInfo.localTxKeys())) {
logger.info(indent + "Mapped keys:");
printTransactionKeys(indent + DOUBLE_INDENT, verboseInfo);
}
}
/**
* Prints keys of specific transaction mapping to output.
*
* @param indent Indent.
* @param verboseInfo Verbose info.
*/
private void printTransactionKeys(String indent, TxVerboseInfo verboseInfo) {
for (TxVerboseKey txVerboseKey : verboseInfo.localTxKeys()) {
logger.info(indent + (txVerboseKey.read() ? "Read" : "Write") +
" [lock=" + txVerboseKey.lockType() + "]: " + txVerboseKey.txKey());
if (txVerboseKey.lockType() == TxKeyLockType.AWAITS_LOCK)
logger.info(indent + DOUBLE_INDENT + "Lock owner XID: " + txVerboseKey.ownerVersion());
}
}
/**
* Prints results of --tx --info to output in case requested transaction is not active.
*
* @param res Response.
*/
private void printTxInfoHistoricalResult(Map<ClusterNode, VisorTxTaskResult> res) {
if (F.isEmpty(res))
logger.info("Transaction was not found in history across the cluster.");
else {
logger.info("Transaction was found in completed versions history of the following nodes:");
for (Map.Entry<ClusterNode, VisorTxTaskResult> entry : res.entrySet()) {
logger.info(DOUBLE_INDENT + nodeDescription(entry.getKey()) + ':');
logger.info(DOUBLE_INDENT + DOUBLE_INDENT + "State: " + entry.getValue().getInfos().get(0).getState());
}
}
}
/** {@inheritDoc} */
@Override public String name() {
return TX.toCommandName();
}
}