/*
* Copyright (c) 2014 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.management.backup;
import com.emc.storageos.coordinator.client.model.Constants;
import com.emc.storageos.management.backup.util.ZipUtil;
import com.google.common.base.Preconditions;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static com.emc.storageos.services.util.FileUtils.chown;
import static com.emc.storageos.services.util.FileUtils.chmod;
public class RestoreHandler {
private static final Logger log = LoggerFactory.getLogger(RestoreHandler.class);
private File rootDir;
private File viprDataDir;
private List<String> extraCleanDirs = new ArrayList<>();
private File backupArchive;
private boolean onlyRestoreSiteId;
public RestoreHandler(String rootDir, String viprDataDir) {
Preconditions.checkArgument(rootDir != null && viprDataDir != null,
"ViPR data directory is not configured");
this.rootDir = new File(rootDir);
this.viprDataDir = new File(viprDataDir);
}
RestoreHandler() {
}
public void setOnlyRestoreSiteId(boolean onlyRestoreSiteId) {
this.onlyRestoreSiteId = onlyRestoreSiteId;
}
/**
* Sets root directory of ViPR db/zk
*
* @param rootDir
* The path of ViPR db/zk root directory
*/
void setRootDir(File rootDir) {
this.rootDir = rootDir;
}
/**
* Sets ViPR service data directory
*
* @param viprDataDir
* The directory which saves ViPR service data
*/
void setViprDataDir(File viprDataDir) {
this.viprDataDir = viprDataDir;
}
/**
* Sets extra directories which should be clean before restore
*
* @param extraCleanDirs
* The extra clean directory list
*/
public void setExtraCleanDirs(List<String> extraCleanDirs) {
if (extraCleanDirs != null) {
this.extraCleanDirs = extraCleanDirs;
}
}
/**
* Sets backup compress package
*
* @param backupArchive
* The backup package
*/
public void setBackupArchive(File backupArchive) {
this.backupArchive = backupArchive;
}
/**
* Purges ViPR data files before restore.
*/
public void purge() throws Exception {
if (!viprDataDir.getParentFile().exists()) {
throw new FileNotFoundException(String.format(
"%s is not exist, please initialize ViPR first", viprDataDir.getParent()));
}
log.info("\tDelete: {}", viprDataDir.getAbsolutePath());
try {
FileUtils.deleteDirectory(viprDataDir);
for (String fileName : extraCleanDirs) {
log.info("\tDelete: {}", fileName);
File file = new File(fileName);
if (file.exists()) {
FileUtils.forceDelete(file);
}
}
}catch (Exception e) {
log.error("Purge data failed e=", e);
throw e;
}
}
/**
* Uncompresses backup file into vipr data directory.
*/
public void replace() throws IOException {
replace(false);
}
/**
* Uncompresses backup file into vipr data directory.
*/
public void replace(final boolean geoRestoreFromScratch) throws IOException {
String backupName = backupArchive.getName().substring(0,
backupArchive.getName().lastIndexOf('.'));
// Check reinit flag for multi vdc env
checkReinit(backupName, geoRestoreFromScratch);
final File tmpDir = new File(viprDataDir.getParentFile(), backupName);
log.debug("Temporary backup folder: {}", tmpDir.getAbsolutePath());
try {
ZipUtil.unpack(backupArchive, viprDataDir.getParentFile());
String backupType = backupName.split(BackupConstants.BACKUP_NAME_DELIMITER)[1];
if (BackupType.zk.name().equalsIgnoreCase(backupType)) {
replaceSiteIdFile(tmpDir);
}
if (onlyRestoreSiteId) {
return;
}
tmpDir.renameTo(viprDataDir);
//if there are more files in the data dir, chown may take more than 10 seconds to complete,
//so set timeout to 1 minute
chown(viprDataDir, BackupConstants.STORAGEOS_USER, BackupConstants.STORAGEOS_GROUP,60*1000);
restoreDrivers();
} finally {
if (tmpDir.exists()) {
FileUtils.deleteQuietly(tmpDir);
}
}
}
private void restoreDrivers() {
File backupDriverDir = new File(viprDataDir, BackupConstants.DRIVERS_FOLDER_NAME);
if (!backupDriverDir.exists() || !backupDriverDir.isDirectory()) {
return;
}
File[] drivers = backupDriverDir.listFiles();
if (drivers == null || drivers.length == 0) {
return;
}
log.info("Found drivers in backup, prepare to restore drivers ...");
File sysDriverDir = new File(BackupConstants.DRIVERS_DIR);
for (File f : drivers) {
try {
chmod(f, BackupConstants.BACKUP_FILE_PERMISSION);
FileUtils.moveFileToDirectory(f, sysDriverDir, true);
log.info("Successfully restored driver file: {}", f.getName());
} catch (IOException e) {
log.error("Error happened when moving driver file {} from backup to data directory", f.getName(), e);
}
}
try {
FileUtils.deleteDirectory(backupDriverDir);
log.info("Successfully deleted driver backup directory");
} catch (IOException e) {
log.error("Failed to delete tmp driver directory {}", backupDriverDir.getAbsolutePath(), e);
}
chown(sysDriverDir, BackupConstants.STORAGEOS_USER, BackupConstants.STORAGEOS_GROUP);
chmod(sysDriverDir, BackupConstants.DRIVER_DIR_PERMISSION, false);
}
private void replaceSiteIdFile(File siteIdFileDir) throws IOException {
log.info("Replacing site id file ...");
File unpackedSiteIdFile = new File(siteIdFileDir, BackupConstants.SITE_ID_FILE_NAME);
chown(unpackedSiteIdFile, BackupConstants.STORAGEOS_USER, BackupConstants.STORAGEOS_GROUP);
FileUtils.moveFileToDirectory(unpackedSiteIdFile, rootDir, false);
}
/**
* Checks reinit flag for (geo)db to pull data from remote vdc/nodes
*
* @param backupName
* The name of backup file
* @param geoRestoreFromScratch
* True if restore geodb from scratch, or else if false
* @throws IOException
*/
private void checkReinit(final String backupName, final boolean geoRestoreFromScratch) throws IOException {
// Add reinit file for multi vdc geodb synchronization
String backupType = backupName.split(BackupConstants.BACKUP_NAME_DELIMITER)[1];
if (BackupType.geodbmultivdc.name().equalsIgnoreCase(backupType)) {
log.info("This backup was taken in multi vdc scenario");
boolean needReinit = geoRestoreFromScratch ? false : true;
checkReinitFile(needReinit);
}
}
/**
* Checks reinit file according to argument needReinit
*
* @param needReinit
* Need to add reinit marker or not
* @throws IOException
*/
public void checkReinitFile(final boolean needReinit) throws IOException {
File bootModeFile = new File(rootDir, Constants.STARTUPMODE);
if (!needReinit) {
log.info("Reinit flag is false");
if (bootModeFile.exists()) {
bootModeFile.delete();
}
return;
}
if (!bootModeFile.exists()) {
setDbStartupModeAsRestoreReinit(rootDir);
}
chown(bootModeFile, BackupConstants.STORAGEOS_USER, BackupConstants.STORAGEOS_GROUP);
log.info("Startup mode file({}) has been created", bootModeFile.getAbsolutePath());
}
private void setDbStartupModeAsRestoreReinit(File dir) throws IOException {
File bootModeFile = new File(dir, Constants.STARTUPMODE);
try (OutputStream fos = new FileOutputStream(bootModeFile)) {
Properties properties = new Properties();
properties.setProperty(Constants.STARTUPMODE, Constants.STARTUPMODE_RESTORE_REINIT);
properties.store(fos, null);
log.info("Set startup mode as restore reinit under {} successful", dir);
}
}
}