package com.sungardas.enhancedsnapshots.service.impl; import com.amazonaws.regions.Regions; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.model.ListObjectsRequest; import com.amazonaws.services.s3.model.S3ObjectSummary; import com.sungardas.enhancedsnapshots.aws.dynamodb.model.BackupEntry; import com.sungardas.enhancedsnapshots.aws.dynamodb.model.BackupState; import com.sungardas.enhancedsnapshots.components.ConfigurationMediator; import com.sungardas.enhancedsnapshots.exception.ConfigurationException; import com.sungardas.enhancedsnapshots.exception.EnhancedSnapshotsException; import com.sungardas.enhancedsnapshots.exception.SDFSException; import com.sungardas.enhancedsnapshots.service.SDFSStateService; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Profile; import org.springframework.core.io.ResourceLoader; import org.springframework.stereotype.Service; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; @Service @Profile("prod") public class SDFSStateServiceImpl implements SDFSStateService { private static final Logger LOG = LogManager.getLogger(SDFSStateServiceImpl.class); private boolean reconfigurationInProgressFlag = false; private static final String MOUNT_CMD = "--mount"; private static final String UNMOUNT_CMD = "--unmount"; private static final String GET_SATE_CMD = "--state"; private static final String CONFIGURE_CMD = "--configure"; private static final String CONFIGURE_CLUSTER_CMD = "--configurenode"; private static final String EXPAND_VOLUME_CMD = "--expandvolume"; private static final String CLOUD_SYNC_CMD = "--cloudsync"; private static final String SHOW_VOLUME_ID_CMD = "--showvolumes"; private static final String SYNC_CLUSTER_VOLUMES = "--syncvolumes"; @Value("${enhancedsnapshots.default.sdfs.mount.time}") private int sdfsMountTime; @Value("${enhancedsnapshots.sdfs.script.path}") private String sdfsScript; private String bucketLocation; private static final int VOLUME_ID_INDEX = 0; private static final int TIME_INDEX = 1; private static final int TYPE_INDEX = 2; private static final int IOPS_INDEX = 3; private static final long BYTES_IN_GIB = 1073741824l; @Autowired private ResourceLoader resourceLoader; @Autowired private AmazonS3 amazonS3; @Autowired private ConfigurationMediator configurationMediator; @Override public void restoreSDFS() { File file = null; try { stopSDFS(); startSDFS(true); //SDFS mount time TimeUnit.SECONDS.sleep(sdfsMountTime); LOG.info("SDFS state restored."); } catch (Exception e) { if (file != null && file.exists()) { file.delete(); } throw new SDFSException("Can't restore sdfs state", e); } } @Override public Long getBackupTime() { ListObjectsRequest request = new ListObjectsRequest() .withBucketName(configurationMediator.getS3Bucket()).withPrefix(configurationMediator.getSdfsBackupFileName()); List<S3ObjectSummary> list = amazonS3.listObjects(request).getObjectSummaries(); if (list.size() > 0) { return list.get(0).getLastModified().getTime(); } else { return null; } } @Override public synchronized void reconfigureAndRestartSDFS() { try { reconfigurationInProgressFlag = true; stopSDFS(); removeSdfsConfFile(); configureSDFS(); startSDFS(false); } catch (Exception e) { LOG.error("Failed to reconfigure and restart SDFS", e); throw new ConfigurationException("Failed to reconfigure and restart SDFS"); } finally { reconfigurationInProgressFlag = false; } } private void startSDFS(Boolean restore) { try { if (sdfsIsRunnig()) { LOG.info("SDFS is already running"); return; } if (!new File(configurationMediator.getSdfsConfigPath()).exists()) { configureSDFS(); } String[] parameters = {getSdfsScriptFile(sdfsScript).getAbsolutePath(), MOUNT_CMD, restore.toString()}; Process p = executeScript(parameters); switch (p.exitValue()) { case 0: LOG.info("SDFS is running"); break; default: throw new ConfigurationException("Failed to start SDFS"); } if (configurationMediator.isClusterMode()) { p = executeScript(new String[]{getSdfsScriptFile(sdfsScript).getAbsolutePath(), SYNC_CLUSTER_VOLUMES, configurationMediator.getSdfsCliPsw()}); switch (p.exitValue()) { case 0: LOG.info("SDFS is synchronized"); break; default: throw new ConfigurationException("Failed to synchronize SDFS"); } } } catch (Exception e) { LOG.error(e); throw new ConfigurationException("Failed to start SDFS"); } } @Override public void startSDFS() { startSDFS(false); } private void configureSDFS() throws IOException, InterruptedException { String[] parameters; if (configurationMediator.isClusterMode()) { LOG.info("Configuring SDFS in cluster mode..."); parameters = new String[]{getSdfsScriptFile(sdfsScript).getAbsolutePath(), CONFIGURE_CLUSTER_CMD, configurationMediator.getSdfsVolumeSize(), configurationMediator.getS3Bucket(), getBucketLocation(configurationMediator.getS3Bucket()), configurationMediator.getSdfsLocalCacheSize(), configurationMediator.getChunkStoreEncryptionKey(), configurationMediator.getChunkStoreIV(), configurationMediator.getSdfsCliPsw()}; } else { LOG.info("Configuring SDFS in standalone mode..."); parameters = new String[]{getSdfsScriptFile(sdfsScript).getAbsolutePath(), CONFIGURE_CMD, configurationMediator.getSdfsVolumeSize(), configurationMediator.getS3Bucket(), getBucketLocation(configurationMediator.getS3Bucket()), configurationMediator.getSdfsLocalCacheSize()}; } Process p = executeScript(parameters); switch (p.exitValue()) { case 0: LOG.info("SDFS is configured"); break; default: throw new ConfigurationException("Failed to configure SDFS"); } } @Override public void stopSDFS() { try { if (!sdfsIsRunnig()) { LOG.info("SDFS is already stopped"); return; } String[] parameters = {getSdfsScriptFile(sdfsScript).getAbsolutePath(), UNMOUNT_CMD}; Process p = executeScript(parameters); switch (p.exitValue()) { case 0: LOG.info("SDFS is currently stopped"); break; default: throw new ConfigurationException("Failed to stop SDFS"); } } catch (Exception e) { LOG.error(e); throw new ConfigurationException("Failed to stop SDFS"); } } private boolean sdfsIsRunnig() { try { String[] parameters = {getSdfsScriptFile(sdfsScript).getAbsolutePath(), GET_SATE_CMD}; Process p = executeScript(parameters); switch (p.exitValue()) { case 0: LOG.debug("SDFS is currently running"); return true; case 1: LOG.debug("SDFS is currently stopped"); return false; default: throw new ConfigurationException("Failed to stop SDFS"); } } catch (Exception e) { LOG.error(e); throw new ConfigurationException("Failed to determine SDFS state"); } } public boolean sdfsIsAvailable() { try { if (reconfigurationInProgressFlag) { LOG.debug("SDFS is unavailable. Reconfiguration is in progress ... "); return false; } return sdfsIsRunnig(); } catch (Exception e) { LOG.error(e); throw new ConfigurationException("Failed to determine SDFS state"); } } @Override public void expandSdfsVolume(String newVolumeSize) { String[] parameters; try { parameters = new String[]{getSdfsScriptFile(sdfsScript).getAbsolutePath(), EXPAND_VOLUME_CMD, configurationMediator.getSdfsMountPoint(), newVolumeSize}; Process p = executeScript(parameters); switch (p.exitValue()) { case 0: LOG.debug("SDFS volume was expanded successfully"); break; default: throw new ConfigurationException("Failed to expand SDFS volume"); } } catch (Exception e) { LOG.error(e); throw new ConfigurationException("Failed to expand SDFS volume"); } } @Override public void cloudSync() { String[] parameters; try { parameters = new String[]{getSdfsScriptFile(sdfsScript).getAbsolutePath(), CLOUD_SYNC_CMD}; Process p = executeScript(parameters); switch (p.exitValue()) { case 0: LOG.debug("SDFS metadata sync successfully"); break; default: throw new ConfigurationException("Failed to sync SDFS metadata"); } } catch (Exception e) { LOG.error(e); throw new ConfigurationException("Failed to sync SDFS metadata"); } } private void removeSdfsConfFile() { File sdfsConf = new File(configurationMediator.getSdfsConfigPath()); if (sdfsConf.exists()) { sdfsConf.delete(); LOG.info("SDFS conf file was successfully removed."); } } private Process executeScript(String[] parameters) throws IOException, InterruptedException { LOG.info("Executing script: {}", Arrays.toString(parameters)); Process p = Runtime.getRuntime().exec(parameters); p.waitFor(); print(p); return p; } private String getBucketLocation(String bucket) { if (bucketLocation != null) { return bucketLocation; } if (amazonS3.doesBucketExist(bucket)) { bucketLocation = amazonS3.getBucketLocation(bucket); } else { bucketLocation = Regions.getCurrentRegion().getName(); } return bucketLocation; } private void print(Process p) throws IOException { String line; BufferedReader input = new BufferedReader(new InputStreamReader(p.getInputStream())); while ((line = input.readLine()) != null) { System.out.println(line); } input = new BufferedReader(new InputStreamReader(p.getErrorStream())); while ((line = input.readLine()) != null) { System.out.println(line); } } private File getSdfsScriptFile(String scriptName) throws IOException { File sdfsScript = resourceLoader.getResource(scriptName).getFile(); sdfsScript.setExecutable(true); return sdfsScript; } public List<BackupEntry> getBackupsFromSDFSMountPoint() { File[] files = new File(configurationMediator.getSdfsMountPoint()).listFiles(); LOG.info("Found {} files in system backup", files.length); List<BackupEntry> backupEntryList = new ArrayList<>(); for (File file : files) { BackupEntry entry = getBackupFromFile(file); if (entry != null) { backupEntryList.add(entry); } } LOG.info("All backups restored."); return backupEntryList; } @Override public long getSDFSVolumeId() { try { String[] parameters = new String[]{getSdfsScriptFile(sdfsScript).getAbsolutePath(), SHOW_VOLUME_ID_CMD, configurationMediator.getSdfsCliPsw()}; LOG.info("Executing script: {}", Arrays.toString(parameters)); Process p = Runtime.getRuntime().exec(parameters); p.waitFor(); String line = new BufferedReader(new InputStreamReader(p.getInputStream())).lines().skip(3).findFirst().get(); return Long.parseLong(line); } catch (Exception e) { LOG.error(e); throw new EnhancedSnapshotsException("Unable to get SDFS volumeId", e); } } private BackupEntry getBackupFromFile(File file) { String fileName = file.getName(); String[] props = fileName.split("\\."); if (props.length != 5) { return null; } else { BackupEntry backupEntry = new BackupEntry(); backupEntry.setFileName(fileName); backupEntry.setIops(props[IOPS_INDEX]); backupEntry.setSizeGiB(String.valueOf((int) (file.length() / BYTES_IN_GIB))); backupEntry.setTimeCreated(props[TIME_INDEX]); backupEntry.setVolumeType(props[TYPE_INDEX]); backupEntry.setState(BackupState.COMPLETED.getState()); backupEntry.setVolumeId(props[VOLUME_ID_INDEX]); backupEntry.setSize(String.valueOf(file.length())); return backupEntry; } } }