/*
* Copyright (c) 2013 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.zkutils;
import java.io.IOException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.Scanner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emc.storageos.coordinator.client.model.MigrationStatus;
/**
* Create simple cli for zk, implements these functions:
* 1. Dump contents from zk in human readable form
* 2. Control the upgrade process(including manually prevent, suspend, release, get info)
*/
// Suppress Sonar violation of Lazy initialization of static fields should be synchronized
// There's only one thread initializing static fields, so it's thread safe.
@SuppressWarnings("squid:S2444")
public class Main {
private static final Logger log = LoggerFactory.getLogger(Main.class);
private static final String WITH_DATA = "-withdata";
private static final String HOST_ARG = "-host";
private static final String LOCK_UPGRADE = "-upgrade";
private static final String RESET_MIFAIL = "-migrationfail";
private static final String REGEX_IP = "(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)";
private static String rollbackWarnMessage = "\n"
+ "********************************* WARNINGS *********************************\n"
+ "1. It applies for DR Standby only. Read the following notes before moving \n"
+ " ahead. It will erase current data in zk/db/geodb.\n\n"
+ "2. Try to recover DR Active first when disaster happens. Use rollback as last\n"
+ " resort when Active is lost and current data on Standby is incomplete\n\n"
+ "3. Do rollback for the following cases:\n"
+ " \ta) Resume operation was manually triggered on GUI, data-syncing was\n"
+ " \t started but disrupted before it's done.\n"
+ " \tb) Resume operation was automatially triggered by active site,\n"
+ " \t data-syncing was started but disrupted before it's done.\n\n"
+ "4. Rollback operation will change data (including zk, db and geodb) back to\n"
+ " previous data revision before resuming.\n\n"
+ "5. Make sure all nodes are up and running before proceeding with rollback\n"
+ " operation on DR Standby.\n\n"
+ "6. All nodes will reboot if rollback is successfully triggered, data will\n"
+ " be switched to previous revision after rebooting.\n\n"
+ "7. If rollback is not successfully triggered or finished, check zkutils.log\n"
+ " and syssvc.log for troubleshooting.\n\n"
+ "8. After rollback is successfully done, trigger failover immediately on this\n"
+ " DR Standby. Original Active should not be brought back again.\n"
+ "****************************************************************************\n";
private static LockCmdHandler lockCmdHandler;
private static ZkCmdHandler zkCmdHandler;
private static ZkTxnHandler zkTxnHandler;
private static ServiceConfigCmdHandler serviceCmdHandler;
private static String host = "localhost";
private static int port = 2181;
private static boolean withData = false;
private static String path = null;
private static KeystoreCmdHandler keystoreCmdHandler;
private enum Command {
LOCK, HOLD, RELEASE, INFO, PATH, EPHEMERAL, RESET, GETLASTVALIDZXID, TRUNCATETXNLOG, GETKEYANDCERT, EXPORTKEYSTORE, SAVE_SSH_KEYS,
GEN_SSH_AUTH_KEYS, SET, TUNE_DR_CONFIG, ROLLBACK_DATA_REVISION
}
/**
* Dump usage
*/
private static void usage() {
System.out.println("Usage: ");
System.out.println("\tPrint Contents:");
System.out.println(String.format("\t%s [%s] [%s <IP>] <Path Substring>",
Command.PATH.name().toLowerCase(), WITH_DATA, HOST_ARG));
System.out.println(String.format("\t%s [%s <IP>] <Path Substring>",
Command.SET.name().toLowerCase(), HOST_ARG));
System.out.println("\t\tData will be read from STDIN");
System.out.println(String.format("\t%s [%s] [%s <IP>]",
Command.EPHEMERAL.name().toLowerCase(), WITH_DATA, HOST_ARG));
System.out.println(String.format("\t\t%s <IP>\tSpecify <IP>, default is localhost.", HOST_ARG));
System.out.println(String.format("\t\t%s \tPrint with data content.", WITH_DATA));
System.out.println("\n\tHandle ZK txn log:");
System.out.println(String.format("\t%s \t\tGet last valid logged txn id.",
Command.GETLASTVALIDZXID.name().toLowerCase()));
System.out.println(String.format("\t%s \t\tTruncate to the last valid txnlog.", Command.TRUNCATETXNLOG.name().toLowerCase()));
System.out.println(String.format("\t%s <arg>(in hex)\t\tTruncate to the specific txn log.", Command.TRUNCATETXNLOG.name()
.toLowerCase()));
System.out.println("\n\tHandle Lock Process:");
System.out.println(String.format("\t%s <arg>\t\tLock to prevent starting <arg> process",
Command.LOCK.name().toLowerCase()));
System.out.println(String.format("\t%s <arg>\t\tHold on to suspend <arg> process",
Command.HOLD.name().toLowerCase()));
System.out.println(String.format("\t%s <arg>\t\tRelase all lock to <arg> continuously",
Command.RELEASE.name().toLowerCase()));
System.out.println(String.format("\t%s <arg>\t\tShow the infomation about <arg>",
Command.INFO.name().toLowerCase()));
System.out.println(String.format("\t\tAvailable Arguments:"));
System.out.println(String.format("\t\t%s\tHandle upgrading process.", LOCK_UPGRADE));
System.out.println("\n\tService Configuration:");
System.out.println(String.format("\t%s <arg>\t\tReset the service configuration of <arg>",
Command.RESET.name().toLowerCase()));
System.out.println(String.format("\t\tAvailable Arguments:"));
System.out.println(String.format("\t\t%s\tReset MIGRATION_FAILED status.", RESET_MIFAIL));
System.out.println("\n\tSecurity operations:");
System.out.println(String.format(
"\t%s \t\t\tGet VDC's key and certificate chain.", Command.GETKEYANDCERT
.name().toLowerCase()));
System.out.println(String.format(
"\t%s \t\t\tSave the ssh keys and configs for host, root and svcuser",
Command.SAVE_SSH_KEYS.name().toLowerCase()));
System.out.println(String.format(
"\t%s \t\t\tExport the keystore to the default location.",
Command.EXPORTKEYSTORE.name().toLowerCase()));
System.out.println(String.format(
"\t%s \t\t\tGenerate AuthorizedKeys2 for each user.",
Command.GEN_SSH_AUTH_KEYS.name().toLowerCase()));
System.out.println("\n\tDisaster Recovery Operations:");
System.out.println(String.format("\t%s <key> <value>\t\tAdd \"key=value\" line to DR configuration", Command.TUNE_DR_CONFIG.name().toLowerCase()));
System.out.println(String.format("\t%s \t\t\tRollback DR Standby to previous viable data revision", Command.ROLLBACK_DATA_REVISION.name().toLowerCase()));
}
/**
* Zkutils entry method.
*
* @param args
* command and arguments.
* @throws Exception
*/
public static void main(String[] args) throws Exception {
if (args.length == 0) {
usage();
return;
}
Command cmd;
try {
cmd = Command.valueOf(args[0].trim().toUpperCase());
} catch (IllegalArgumentException e) {
System.err.println("Invalid command " + args[0]);
usage();
return;
}
try {
switch (cmd) {
case PATH:
if (args.length < 2) {
throw new IllegalArgumentException("The path substring is missing");
}
processZkCmdArgs(args);
initZkCmdHandler(host, port, withData);
zkCmdHandler.printNodes(path);
break;
case SET:
if (args.length < 2) {
throw new IllegalArgumentException("The path and/or data substring is missing");
}
processZkCmdArgs(args);
initZkCmdHandler(host, port, withData);
zkCmdHandler.setNodeData(path);
break;
case EPHEMERAL:
processZkCmdArgs(args);
initZkCmdHandler(host, port, withData);
zkCmdHandler.printEphemeralNodes();
break;
case ROLLBACK_DATA_REVISION:
if (args.length > 1) {
throw new IllegalArgumentException("Invalid paramerters");
}
System.out.println(rollbackWarnMessage);
System.out.print("Do you still want to continue [y/n]:");
Scanner userInput = new Scanner(System.in);
String answer = userInput.nextLine();
if (answer == null || !answer.equals("y")) {
System.out.println("You have aborted rollback operation");
System.exit(1);
}
initZkCmdHandler(host, port, withData);
zkCmdHandler.rollbackDataRevision();
break;
case TUNE_DR_CONFIG:
if (args.length != 3) {
throw new IllegalArgumentException("Invalid parameters");
}
String key = args[1] != null ? args[1].trim() : "";
String value = args[2] != null ? args[2].trim() : "";
if (key.isEmpty() || value.isEmpty()) {
throw new IllegalArgumentException("Invalid parameters");
}
processZkCmdArgs(args);
initZkCmdHandler(host, port, withData);
zkCmdHandler.tuneDrConfig(key, value);
break;
case GETLASTVALIDZXID:
if (args.length > 1) {
throw new IllegalArgumentException("Invalid parameters");
}
initZkTxnHandler();
zkTxnHandler.getLastValidZxid();
break;
case TRUNCATETXNLOG:
if (args.length > 2) {
throw new IllegalArgumentException("Invalid parameters");
}
initZkTxnHandler();
boolean success = false;
if (args.length < 2) {
success = zkTxnHandler.truncateToZxid();
} else {
success = zkTxnHandler.truncateToZxid(args[1]);
}
if (!success) {
System.exit(1);
}
break;
case LOCK:
processLockCmdArgs(args);
initLockCmdHandler();
lockCmdHandler.aquireUpgradeLocks();
// because using the non-persistent lock, need exit manually
System.out.println("If you want to upgrade, "
+ "Please kill this thread and use 'release' command");
break;
case HOLD:
processLockCmdArgs(args);
initLockCmdHandler();
lockCmdHandler.aquireUpgradeLockByLoop();
System.out.println("If you want to continue, Please use 'release' command");
stop();
break;
case RELEASE:
processLockCmdArgs(args);
initLockCmdHandler();
lockCmdHandler.releaseAllLocks();
stop();
break;
case INFO:
processLockCmdArgs(args);
initLockCmdHandler();
lockCmdHandler.getUpgradeLockOwner();
stop();
break;
case RESET:
MigrationStatus status = processServiceCmdArgs(args);
initServiceCmdHandler();
serviceCmdHandler.resetMigrationStatus(status);
break;
case GETKEYANDCERT:
if (args.length > 1) {
throw new IllegalArgumentException("Invalid parameters");
}
initKeystoreCmdHandler();
keystoreCmdHandler.getViPRKey();
System.out.println();
keystoreCmdHandler.getViPRCertificate();
break;
case EXPORTKEYSTORE:
if (args.length > 1) {
throw new IllegalArgumentException("Invalid parameters");
}
initKeystoreCmdHandler();
try {
keystoreCmdHandler.exportKeystore();
} catch (Exception e) {
log.error(e.getMessage(), e);
System.err.println("Exception e=" + e);
System.exit(1);
}
break;
default:
throw new IllegalArgumentException("Invalid command");
}
} catch (Exception e) {
System.err.println("Exception e=" + e);
usage();
} finally {
if (!cmd.equals(Command.LOCK)) {
stop();
}
}
}
/**
* @throws InterruptedException
* @throws IOException
* @throws CertificateException
* @throws NoSuchAlgorithmException
* @throws KeyStoreException
*
*/
private static void initKeystoreCmdHandler() throws KeyStoreException,
NoSuchAlgorithmException, CertificateException, IOException,
InterruptedException {
keystoreCmdHandler = new KeystoreCmdHandler();
}
private static void initLockCmdHandler() {
lockCmdHandler = new LockCmdHandler();
}
private static void initZkCmdHandler(String host, int port, boolean withData) {
zkCmdHandler = new ZkCmdHandler(host, port, withData);
}
private static void initZkTxnHandler() {
zkTxnHandler = new ZkTxnHandler();
}
private static void initServiceCmdHandler() {
serviceCmdHandler = new ServiceConfigCmdHandler();
}
/**
* Deal with zk cmd args.
*
* @param args
* the args from Main
*/
private static void processZkCmdArgs(String[] args) {
if (args[0].trim().equalsIgnoreCase(Command.PATH.name()) || args[0].trim().equalsIgnoreCase(Command.SET.name())) {
if (args[args.length - 1].trim().equalsIgnoreCase(WITH_DATA)
|| args[args.length - 1].trim().equalsIgnoreCase(HOST_ARG)
|| args[args.length - 1].trim().matches(REGEX_IP)
|| args[args.length - 1].trim().startsWith("-")) {
throw new IllegalArgumentException("The path substring is missing");
}
path = args[args.length - 1];
}
for (int i = 1; i < args.length; i++) {
if (args[i].equalsIgnoreCase(WITH_DATA)) {
withData = true;
}
if (args[i].equalsIgnoreCase(HOST_ARG)) {
if (args.length - 1 == i || !args[i + 1].trim().matches(REGEX_IP)) {
throw new IllegalArgumentException("Empty or Wrong IP address!");
}
host = args[i + 1];
i++;
}
}
}
/**
* Deal with lock cmd args.
*
* @param args
* the args from Main
*/
private static void processLockCmdArgs(String[] args) {
if (args.length > 2) {
throw new IllegalArgumentException("Too much arguments");
}
if (args.length == 1 || !args[args.length - 1].equalsIgnoreCase(LOCK_UPGRADE)) {
throw new IllegalArgumentException(
"The <arg> is necessary and from the 'Available Arguments'.");
}
}
/**
* Deal with service configuration cmd args.
*
* @param args
* the args from Main
* @return MigrationStatus
*
*/
private static MigrationStatus processServiceCmdArgs(String[] args) {
String argument;
if (args.length != 2) {
throw new IllegalArgumentException("Wrong arguments");
}
argument = args[1];
if (!argument.equalsIgnoreCase(RESET_MIFAIL)) {
log.error("Invalid command:{} for {}", argument, Command.RESET);
throw new IllegalArgumentException("Invalid command: " + argument);
}
return MigrationStatus.FAILED;
}
private static void stop() {
if (zkCmdHandler != null) {
zkCmdHandler.releaseConnection();
}
System.exit(0);
}
}