/* * Copyright (c) 2014 EMC Corporation * All Rights Reserved */ package com.emc.storageos.management.backup; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.nio.file.Files; import java.nio.file.Path; import java.net.Socket; import java.util.List; import java.util.Arrays; import java.util.Scanner; import java.util.Comparator; import org.apache.commons.io.FileUtils; import com.emc.storageos.management.backup.util.ValidationUtil; import com.emc.storageos.management.backup.util.ValidationUtil.*; import com.emc.storageos.management.backup.exceptions.BackupException; /** * This handler is used to backup not only ZK data files, but also site id and * storage drivers. Site id and storage drivers only need to be backed up once, * exactly like ZK data files, so we put these 2 tasks inside this handler. */ public class ZkBackupHandler extends BackupHandler { private static final Logger log = LoggerFactory.getLogger(ZkBackupHandler.class); private static final String ZK_ACCEPTED_EPOCH = "acceptedEpoch"; private static final String ZK_CURRENT_EPOCH = "currentEpoch"; private static final String CONNECT_ZK_HOST = "localhost"; private static final int CONNECT_ZK_PORT = 2181; private int nodeCount = 0; private File zkDir; private List<String> fileTypeList; private File siteIdFile; private File driverPath; public File getDriverPath() { return driverPath; } public void setDriverPath(File driverPath) { this.driverPath = driverPath; } public void setNodeCount(int nodeCount) { this.nodeCount = nodeCount; } public File getSiteIdFile() { return siteIdFile; } public void setSiteIdFile(File siteIdFile) { this.siteIdFile = siteIdFile; } /** * Sets zk file location * * @param zkDir * The path of ZK location */ public void setZkDir(File zkDir) { this.zkDir = zkDir; } /** * Gets zk file location * * @return zkDir * The path of ZK location */ public File getZkDir() { return zkDir; } /** * Sets file type list that need to be backuped * * @param fileTypeList * The list of zk file type */ public void setFileTypeList(List<String> fileTypeList) { this.fileTypeList = fileTypeList; } /** * Gets file type list that need to be backuped * * @return fileTypeList * The list of zk file type */ public List<String> getFileTypeList() { return fileTypeList; } /** * Make sure the quorum service is ready */ public void validateQuorumStatus() { String result = readZkInfo("ruok", "imok"); log.info("Validate Zookeeper status result = {}", result); if (result == null) { throw BackupException.retryables.quorumServiceNotReady(); } } /** * Just backup the zk files in the leader node */ public boolean isEligibleForBackup() { // on DR active site (1+0), the node may be 'follower' mode when telneting localhost with port 2181 // because of a trick which was to solve a zk limitation in 3.4.6 if(nodeCount == 1) { return true; } String result = readZkInfo("stat", "Mode"); if (result == null || !result.contains(": ")) { throw BackupException.fatals.failedToParseLeaderStatus(result); } String mode = (result.split(": "))[1]; if (mode.equals("leader") || mode.equals("standalone") || mode.equals("observer")) { return true; } else { log.info("Status mode is: {}", mode); return false; } } /** * Get zk info by zk cli */ private String readZkInfo(String cmd, String matchPattern) { String result = null; Socket socket = null; BufferedReader in = null; PrintWriter out = null; log.debug("cmd={}, match={}", cmd, matchPattern); try { socket = new Socket(CONNECT_ZK_HOST, CONNECT_ZK_PORT); in = new BufferedReader(new InputStreamReader( socket.getInputStream())); out = new PrintWriter(new BufferedWriter( new OutputStreamWriter(socket.getOutputStream())), true); out.println(cmd); String line = null; while ((line = in.readLine()) != null) { log.debug(line); if (line.contains(matchPattern)) { result = line; } } } catch (IOException e) { throw BackupException.fatals.failedToReadZkInfo(e); } finally { if (in != null) { try { in.close(); } catch (Exception e) { log.error("close input stream failed. e=", e); } } if (out != null) { try { out.close(); } catch (Exception e) { log.error("close output stream failed. e=", e); } } if (socket != null) { try { socket.close(); } catch (Exception e) { log.error("close socket failed. e=", e); } } } return result; } /** * Make sure the accepted epoch is equal to current epoch */ public boolean checkEpochEqual() { int acceptedEpoch = readEpoch(new File(zkDir, ZK_ACCEPTED_EPOCH)); int currentEpoch = readEpoch(new File(zkDir, ZK_CURRENT_EPOCH)); return (acceptedEpoch == currentEpoch); } /** * Read epoch from epoch file */ private int readEpoch(File epochFile) { int epoch = -1; if (!epochFile.exists()) { return epoch; } try { Scanner scanner = new Scanner(epochFile); epoch = scanner.nextInt(); log.debug("Got epoch {} from file {}", epoch, epochFile.getName()); } catch (IOException e) { // TODO: error handling log.error("read epoch from file({}) failed. e=", epochFile.getName(), e); } return epoch; } /** * Sanity check after backup */ private boolean checkZkConditionAfterBackup() { validateQuorumStatus(); if (!isEligibleForBackup()) { log.error("This node is not leader any more"); return false; } if (!checkEpochEqual()) { log.error("The accepted and current epoch are not equal"); return false; } return true; } /** * Backup all the files with type in the defined type list */ public void backupFolder(File targetDir, File sourceDir) throws IOException { for (String type : fileTypeList) { backupFolderByType(targetDir, sourceDir, type); } } /** * Backup files with specific type in a folder: * copy the latest file and create hard link for the rest */ private void backupFolderByType(File targetDir, File sourceDir, final String type) throws IOException { File[] sourceFileList = sourceDir.listFiles(new FilenameFilter() { @Override public boolean accept(File file, String name) { log.debug("file name={}, type={}", name, type); return name.contains(type); } }); if (sourceFileList == null || sourceFileList.length == 0) { log.debug("No file with type equals to {} in directory({})", type, sourceDir); return; } Arrays.sort(sourceFileList, new Comparator<File>() { public int compare(File f1, File f2) { return Long.valueOf(f2.lastModified()).compareTo(f1.lastModified()); } }); boolean latest = true; for (File zkFile : sourceFileList) { log.debug("file name={}, time={}", zkFile.getName(), zkFile.lastModified()); if (zkFile.isDirectory()) { continue; } File targetFile = new File(targetDir, zkFile.getName()); if (latest) { FileUtils.copyFile(zkFile, targetFile); latest = false; continue; } createFileLink(targetFile.toPath(), zkFile.toPath()); } } /** * Create file hard link */ private void createFileLink(Path targetDir, Path sourceFile) { try { Files.createLink(targetDir, sourceFile); log.debug("The link({} to {}) was successfully created!", sourceFile.toString(), targetDir.toString()); } catch (IOException | UnsupportedOperationException | SecurityException e) { throw BackupException.fatals.failedToCreateFileLink( sourceFile.toString(), targetDir.toString(), e); } } @Override public boolean isNeed() { validateQuorumStatus(); boolean ret = isEligibleForBackup(); if (!ret) { log.info("Skip current zk instance during backup"); } return ret; } @Override public String createBackup(final String backupTag) { String fullBackupTag = backupTag + BackupConstants.BACKUP_NAME_DELIMITER + backupType.name(); checkBackupFileExist(backupTag, fullBackupTag); return fullBackupTag; } private void backupSiteId(File targetDir) throws IOException { FileUtils.copyFileToDirectory(siteIdFile, targetDir); } private void backupDrivers(File targetDir) throws IOException { if (driverPath == null || !driverPath.exists() || !driverPath.isDirectory()) { log.error("Driver path is not configured, or does not exist, or is not a directory"); return; } File[] drivers = driverPath.listFiles(); if (drivers == null || drivers.length == 0) { log.info("Found no driver, skip backing up drivers"); return; } File driverFolder = new File(targetDir, BackupConstants.DRIVERS_FOLDER_NAME); driverFolder.mkdir(); for (File driver : driverPath.listFiles()) { if (!com.emc.storageos.services.util.FileUtils.isJarFile(driver.getName())) { continue; } FileUtils.copyFileToDirectory(driver, driverFolder); } } @Override public File dumpBackup(final String backupTag, final String fullBackupTag) { File targetDir = new File(backupContext.getBackupDir(), backupTag); File targetFolder = new File(targetDir, fullBackupTag); try { ValidationUtil.validateFile(zkDir, FileType.Dir, NotExistEnum.NOT_EXSIT_ERROR); ValidationUtil.validateFile(targetDir, FileType.Dir, NotExistEnum.NOT_EXSIT_CREATE); ValidationUtil.validateFile(targetFolder, FileType.Dir, NotExistEnum.NOT_EXSIT_CREATE); backupFolder(targetFolder, zkDir); backupSiteId(targetFolder); backupDrivers(targetFolder); } catch (IOException ex) { throw BackupException.fatals.failedToDumpZkData(fullBackupTag, ex); } if (!checkZkConditionAfterBackup()) { throw BackupException.retryables.leaderHasBeenChanged(); } log.info("ZK backup files have been moved to ({}) successfully", targetFolder.getAbsolutePath()); return targetFolder; } }