/* * Copyright (c) 2014 EMC Corporation * All Rights Reserved */ package com.emc.storageos.management.backup; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.util.Properties; import java.util.Scanner; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.emc.storageos.services.util.PlatformUtils; import com.emc.storageos.coordinator.client.service.impl.DualInetAddress; import com.emc.storageos.management.backup.exceptions.BackupException; public class RestoreManager { private static final Logger log = LoggerFactory.getLogger(RestoreManager.class); private static final String OUTPUT_FORMAT = " %-40s - %s"; private String[] serviceNames = new String[] {"dbsvc", "geodbsvc", "coordinatorsvc"}; private RestoreHandler dbRestoreHandler; private RestoreHandler zkRestoreHandler; private RestoreHandler geoDbRestoreHandler; private String nodeId; private int nodeCount = 0; private String ipAddress4; private String ipAddress6; private Boolean enableChangeVersion; private boolean onlyRestoreSiteId; private enum Validation { passed, failed } public void setOnlyRestoreSiteId(boolean onlyRestoreSiteId) { this.onlyRestoreSiteId = onlyRestoreSiteId; } public void setDbRestoreHandler(RestoreHandler dbRestoreHandler) { this.dbRestoreHandler = dbRestoreHandler; } public void setZkRestoreHandler(RestoreHandler zkRestoreHandler) { this.zkRestoreHandler = zkRestoreHandler; } public void setGeoDbRestoreHandler(RestoreHandler geoDbRestoreHandler) { this.geoDbRestoreHandler = geoDbRestoreHandler; } public void setNodeId(String nodeId) { this.nodeId = nodeId; } public void setNodeCount(int nodeCount) { this.nodeCount = nodeCount; } public void setIpAddress4(String ipAddress4) { this.ipAddress4 = ipAddress4; } public void setIpAddress6(String ipAddress6) { this.ipAddress6 = ipAddress6; } public void setEnableChangeVersion(Boolean enableChangeVersion) { this.enableChangeVersion = enableChangeVersion; } /** * Purges existing ViPR data * * @param needReinit * Need to create reinit marker or not */ public void purge(final boolean needReinit) { validateViPRServiceDown(); try { dbRestoreHandler.purge(); geoDbRestoreHandler.purge(); geoDbRestoreHandler.checkReinitFile(needReinit); zkRestoreHandler.purge(); } catch (Exception ex) { log.error("Failed to purge data(needReinit={})", needReinit, ex); throw BackupException.fatals.failedToPurgeViprData(ex); } log.info(String.format(OUTPUT_FORMAT, "ViPR data purge validation", Validation.passed.name())); } /** * Restores backup data to current node, including local db, geo db and coordinator * * @param backupPath * The backup data folder * @param snapshotName * The backup which will be restored * @param geoRestoreFromScratch * True if restore geodb from scratch, or else false */ public void restore(final String backupPath, final String snapshotName, final boolean geoRestoreFromScratch) { log.info("Start to restore backup..."); try { validateBackupFolder(backupPath, snapshotName); purge(false); if (onlyRestoreSiteId) { zkRestoreHandler.setOnlyRestoreSiteId(true); zkRestoreHandler.replace(); log.info("Backup ({}) has been restored (only site id) on local successfully", snapshotName); return; } dbRestoreHandler.replace(); log.info(String.format(OUTPUT_FORMAT, "Restore data of local database", Validation.passed.name())); zkRestoreHandler.replace(); log.info(String.format(OUTPUT_FORMAT, "Restore data of coordinator", Validation.passed.name())); geoDbRestoreHandler.replace(geoRestoreFromScratch); log.info(String.format(OUTPUT_FORMAT, "Restore data of geo database", Validation.passed.name())); } catch (Exception ex) { log.error("Failed to restore with backupset({})", snapshotName, ex); throw BackupException.fatals.failedToRestoreBackup(snapshotName, ex); } log.info("Backup ({}) has been restored on local successfully", snapshotName); } /** * Checks ViPR is running or not. */ private void validateViPRServiceDown() { for (String serviceName : serviceNames) { boolean isRunning = isServiceRunning(serviceName); if (isRunning) { log.info("{} is still running", serviceName); throw new IllegalStateException(serviceName + " is running"); } } log.info(String.format(OUTPUT_FORMAT, "ViPR service down validation", Validation.passed.name())); } private boolean isServiceRunning(String serviceName) { try { int pid = PlatformUtils.getServicePid(serviceName); log.debug("Found pid({}) of {}", pid, serviceName); return pid != 0; } catch (Exception ex) { log.debug("Can't find pid of {}", serviceName); return false; } } /** * Validates data structure under backup folder * * @param backupPath * The backup folder path * @param snapshotName * The backup which will be restored */ private void validateBackupFolder(final String backupPath, final String snapshotName) throws IOException { File backupFolder = new File(backupPath); if (!backupFolder.exists()) { throw new FileNotFoundException(String.format("(%s) is not exist", backupPath)); } // Validate backup files File[] backupFiles = backupFolder.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.startsWith(snapshotName + BackupConstants.BACKUP_NAME_DELIMITER) && name.endsWith(BackupConstants.COMPRESS_SUFFIX); } }); String errorMessage = String.format("Need db, geodb and zk backup files under folder %s", backupPath); if (backupFiles == null) { throw new IllegalArgumentException(errorMessage); } if (!onlyRestoreSiteId && backupFiles.length < BackupType.values().length - 2) { throw new IllegalArgumentException(errorMessage); } int matched = 0; boolean backupInMultiVdc = false; for (File backupFile : backupFiles) { String backupFileName = backupFile.getName(); log.info("Checking backup file: {}", backupFileName); if (!backupFileName.contains(nodeId) && !backupFileName.contains(BackupType.zk.name())) { continue; } if (backupFileName.contains(BackupConstants.BACKUP_NAME_DELIMITER + BackupType.db.name())) { dbRestoreHandler.setBackupArchive(backupFile); ++matched; } else if (backupFileName.contains(BackupConstants.BACKUP_NAME_DELIMITER + BackupType.zk.name())) { zkRestoreHandler.setBackupArchive(backupFile); ++matched; } else if (backupFileName.contains(BackupConstants.BACKUP_NAME_DELIMITER + BackupType.geodb.name())) { geoDbRestoreHandler.setBackupArchive(backupFile); ++matched; if (backupFileName.contains(BackupConstants.BACKUP_NAME_DELIMITER + BackupType.geodbmultivdc.name())) { backupInMultiVdc = true; } } else { log.info("Invalid backup file: {}", backupFile.getName()); continue; } log.info("Found backup file: {}", backupFile.getName()); } // When restoring 5-node cluster using 3-node backup, should only restore site id on vipr4 and vipr5. // There's no need to validate backup files on nodes where only site id will be restored, so skip it. if (!onlyRestoreSiteId && matched != BackupType.values().length - 2) { throw new IllegalArgumentException(errorMessage); } // Check backup info checkBackupInfo(new File(backupFolder, snapshotName + BackupConstants.BACKUP_INFO_SUFFIX), backupInMultiVdc); log.info(String.format(OUTPUT_FORMAT, "ViPR backup folder validation", Validation.passed.name())); } /** * Checks version and IPs * * @param backupInfoFile The backup info file */ public void checkBackupInfo(final File backupInfoFile, boolean backupInMultiVdc) { try (InputStream fis = new FileInputStream(backupInfoFile)) { Properties properties = new Properties(); properties.load(fis); checkVersion(properties); checkHosts(properties, backupInMultiVdc); } catch (IOException ex) { // Ignore this exception log.warn("Unable to check backup Info", ex); } } private void checkVersion(Properties properties) throws IOException { String backupVersion = properties.getProperty(BackupConstants.BACKUP_INFO_VERSION); String currentVersion = PlatformUtils.getProductIdent(); log.info("Backup Version: {}\nCurrent Version: {}", backupVersion, currentVersion); if (!enableChangeVersion && !backupVersion.equals(currentVersion)) { throw new IllegalArgumentException("version is not allowed to be changed"); } } private void checkHosts(Properties properties, boolean backupInMultiVdc) throws IOException { String backupHosts = properties.getProperty(BackupConstants.BACKUP_INFO_HOSTS); log.info("Backup Hosts: {}", backupHosts); String[] backupHostArray = backupHosts.replaceAll("\\[|\\]", "").split(", "); boolean isHostValid = checkHostsCount(backupHostArray) && checkHostsIp(backupHostArray); if (backupInMultiVdc && !isHostValid) { String errMessage = "Node count and ip are not allowed to be changed when backup was taken in multi vdc environment"; log.error(errMessage); throw new IllegalArgumentException(errMessage); } } private boolean checkHostsCount(String[] backupHostArray) { log.info("Current Host Count: {}", nodeCount); boolean hostCountEqual = true; if (backupHostArray.length != nodeCount) { int backupQuorumHostCount = backupHostArray.length / 2 + 1; if (nodeCount < backupQuorumHostCount) { log.warn("Current host count is less than quorum of backup host count, the integrity of backup data can't be ensured"); } hostCountEqual = false; } return hostCountEqual; } private boolean checkHostsIp(String[] backupHostArray) throws IOException { DualInetAddress currentHostAddress = DualInetAddress.fromAddresses(ipAddress4, ipAddress6); log.info("Current Host Ip: {}", currentHostAddress.toString()); boolean inHostArray = false; for (String backupHost : backupHostArray) { DualInetAddress backupHostAddress; if (backupHost.contains(BackupConstants.HOSTS_IP_DELIMITER)) { String[] ips = backupHost.trim().split(BackupConstants.HOSTS_IP_DELIMITER); backupHostAddress = DualInetAddress.fromAddresses(ips[0], ips[1]); } else { backupHostAddress = DualInetAddress.fromAddress(backupHost.trim()); } if (currentHostAddress.equals(backupHostAddress)) { inHostArray = true; break; } } return inHostArray; } }