/*
* Copyright (c) 2014 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.management.backup;
import com.emc.storageos.services.util.TimeUtils;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.PosixParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.*;
//Suppress Sonar violation of Lazy initialization of static fields should be synchronized
//This is a CLI application and main method will not be called by multiple threads
@SuppressWarnings("squid:S2444")
public class BackupCmd {
private static final Logger log = LoggerFactory.getLogger(BackupCmd.class);
private static final Options options = new Options();
private static final String TOOL_NAME = "bkutils";
private static final String ONLY_RESTORE_SITE_ID = "osi";
private static BackupOps backupOps;
private static CommandLine cli;
private static RestoreManager restoreManager;
private enum CommandType {
create("Create backup, default name is timestamp"),
list("List all backups"),
delete("Delete specific backup"),
restore("Purge ViPR data and restore specific backup\n" +
"with args: <backup dir> <name> osi(optional)\n" +
"If \"osi\" is used, only site id will be retored\n"),
quota("Get backup quota info, unit:GB\n"),
force("Execute operation on quorum nodes"),
purge("Purge the existing ViPR data with arg\n" +
"[ismultivdc], yes or no(default)");
private String description;
private CommandType(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
static {
Option createOption = OptionBuilder.hasOptionalArg()
.withArgName("[backup]")
.withDescription(CommandType.create.getDescription())
.withLongOpt(CommandType.create.name())
.create("c");
options.addOption(createOption);
options.addOption("l", CommandType.list.name(), false, CommandType.list.getDescription());
options.addOption("d", CommandType.delete.name(), true, CommandType.delete.getDescription());
Option restoreOption = OptionBuilder.hasArgs()
.withArgName("args")
.withDescription(CommandType.restore.getDescription())
.withLongOpt(CommandType.restore.name())
.create("r");
options.addOption(restoreOption);
options.addOption("q", CommandType.quota.name(), false, CommandType.quota.getDescription());
options.addOption("f", CommandType.force.name(), false, CommandType.force.getDescription());
Option purgeOption = OptionBuilder.hasOptionalArg()
.withArgName("[ismultivdc]")
.withDescription(CommandType.purge.getDescription())
.withLongOpt(CommandType.purge.name())
.create("p");
options.addOption(purgeOption);
}
private static void initRestoreManager() {
if (restoreManager == null) {
ApplicationContext context = new ClassPathXmlApplicationContext("backup-restore-conf.xml");
restoreManager = context.getBean("restoreManager", RestoreManager.class);
}
}
private static void initCommandLine(String[] args) {
CommandLineParser parser = new PosixParser();
HelpFormatter formatter = new HelpFormatter();
try {
if (args == null || args.length == 0) {
throw new IllegalArgumentException("");
}
cli = parser.parse(options, args);
if (cli.getOptions().length == 0) {
throw new IllegalArgumentException(
String.format("Invalid argument: %s%n", Arrays.toString(args)));
}
String[] invalidArgs = cli.getArgs();
if (invalidArgs != null && invalidArgs.length != 0) {
throw new IllegalArgumentException(
String.format("Invalid argument: %s%n", Arrays.toString(invalidArgs)));
}
} catch (Exception p) {
System.err.print(p.getMessage());
log.error("Caught Exception when parsing command line input: ", p);
formatter.printHelp(TOOL_NAME, options);
System.exit(-1);
}
}
public static void main(String[] args) {
init(args);
try {
createBackup();
listBackup();
deleteBackup();
purgeViprData();
restoreBackup();
getQuota();
} catch (Exception ex) {
System.err.println("Error: " + ex.getMessage());
System.exit(-1);
}
System.exit(0);
}
private static void init(String[] args) {
initCommandLine(args);
initRestoreManager();
if (!cli.hasOption(CommandType.restore.name()) && !cli.hasOption(CommandType.purge.name())) {
initBackupOps();
}
}
private static void initBackupOps() {
if (backupOps == null) {
ApplicationContext context = new ClassPathXmlApplicationContext("backup-client-conf.xml");
backupOps = context.getBean("backupOps", BackupOps.class);
}
}
private static void createBackup() {
if (!cli.hasOption(CommandType.create.name())) {
return;
}
String backupName = cli.getOptionValue(CommandType.create.name());
if (backupName == null || backupName.isEmpty()) {
backupName = BackupOps.createBackupName();
}
boolean force = false;
if (cli.hasOption(CommandType.force.name())) {
force = true;
}
System.out.println("Start to create backup...");
try {
backupOps.createBackup(backupName, force);
System.out.println(
String.format("Backup (%s) is created successfully", backupName));
} catch (Exception ex){
log.error("Create backup({}) failed: ", backupName, ex);
backupOps.updateBackupCreationStatus(backupName, TimeUtils.getCurrentTime(), false);
throw ex;
}
}
private static void listBackup() {
if (!cli.hasOption(CommandType.list.name())) {
return;
}
System.out.println("Start to list backup...");
List<BackupSetInfo> backupList = backupOps.listBackup();
System.out.println(
String.format("Backups are listed successfully, total: %d", backupList.size()));
if (backupList.isEmpty()) {
return;
}
int maxLength = Integer.MIN_VALUE;
for (BackupSetInfo backupSetInfo : backupList) {
if (backupSetInfo.getName().length() > maxLength) {
maxLength = backupSetInfo.getName().length();
}
}
maxLength = maxLength < 18 ? 20 : maxLength + 2;
String titleFormat = String.format(BackupConstants.LIST_BACKUP_TITLE, maxLength);
String infoFormat = String.format(BackupConstants.LIST_BACKUP_INFO, maxLength);
System.out.println(String.format(titleFormat, "NAME", "SIZE(MB)", "CREATION TIME"));
Format format = new SimpleDateFormat(BackupConstants.DATE_FORMAT);
StringBuilder builder = new StringBuilder();
for (BackupSetInfo backupSetInfo : backupList) {
double sizeMb = backupSetInfo.getSize() * 1.0 / BackupConstants.MEGABYTE;
builder.append(String.format(infoFormat,
backupSetInfo.getName(), sizeMb,
format.format(new Date(backupSetInfo.getCreateTime()))));
builder.append("\n");
}
System.out.print(builder.toString());
}
private static void deleteBackup() {
if (!cli.hasOption(CommandType.delete.name())) {
return;
}
System.out.println("Start to delete backup...");
String backupName = cli.getOptionValue(CommandType.delete.name());
backupOps.deleteBackup(backupName);
System.out.println(
String.format("Backup (%s) is deleted successfully", backupName));
}
private static void purgeViprData() {
if (!cli.hasOption(CommandType.purge.name())) {
return;
}
String multiVdcStr = cli.getOptionValue(CommandType.purge.name());
System.out.println("Start to purge ViPR data...");
if (multiVdcStr == null || "no".equalsIgnoreCase(multiVdcStr.trim())) {
restoreManager.purge(false);
} else if ("yes".equalsIgnoreCase(multiVdcStr.trim())) {
restoreManager.purge(true);
} else {
throw new IllegalArgumentException("Invalid argument, must be yes or no(default)");
}
System.out.println("ViPR data has been purged successfully!");
}
private static void restoreBackup() {
if (!cli.hasOption(CommandType.restore.name())) {
return;
}
String[] restoreArgs = cli.getOptionValues(CommandType.restore.name());
if (restoreArgs.length < 2 || restoreArgs.length > 3) {
System.out.println("Invalid number of restore args.");
new HelpFormatter().printHelp(TOOL_NAME, options);
System.exit(-1);
}
String restoreSrcDir = restoreArgs[0];
String snapshotName = restoreArgs[1];
if (restoreArgs.length == 3) {
if (ONLY_RESTORE_SITE_ID.equals(restoreArgs[2])) {
restoreManager.setOnlyRestoreSiteId(true);
} else {
System.out.println("If third parameter is specified for restore option, it can only be \"osi\"");
new HelpFormatter().printHelp(TOOL_NAME, options);
System.exit(-1);
}
}
boolean geoRestoreFromScratch = false;
if (cli.hasOption(CommandType.force.name())) {
geoRestoreFromScratch = true;
}
restoreManager.restore(restoreSrcDir, snapshotName, geoRestoreFromScratch);
System.out.println("***Important***");
System.out.println("Please start ViPR service after all nodes have been " +
"restored (command: \"/etc/storageos/storageos start\").");
System.out.println("ViPR has risk of data lost before data repair finished, " +
"please check the db repair process by service log");
}
private static void getQuota() {
if (!cli.hasOption(CommandType.quota.name())) {
return;
}
System.out.println("Start to get quota of backup...");
int quota = backupOps.getQuotaGb();
System.out.println(String.format("Quota of backup is: %d GB", quota));
}
}