package com.sungardas.enhancedsnapshots.service.impl; import com.amazonaws.services.ec2.model.Volume; import com.sungardas.enhancedsnapshots.components.ConfigurationMediator; import com.sungardas.enhancedsnapshots.dto.CopyingTaskProgressDto; import com.sungardas.enhancedsnapshots.exception.EnhancedSnapshotsInterruptedException; import com.sungardas.enhancedsnapshots.exception.EnhancedSnapshotsTaskInterruptedException; import com.sungardas.enhancedsnapshots.exception.SDFSException; import com.sungardas.enhancedsnapshots.service.NotificationService; import com.sungardas.enhancedsnapshots.service.StorageService; import com.sungardas.enhancedsnapshots.service.TaskService; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.DependsOn; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import java.io.*; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.attribute.BasicFileAttributes; import java.util.concurrent.TimeUnit; import static java.lang.String.format; @Service @DependsOn("ConfigurationMediator") @Profile("prod") public class StorageServiceImpl implements StorageService { public static final Logger LOG = LogManager.getLogger(StorageServiceImpl.class); private String mountPoint; public static final int BYTES_IN_MEGABYTE = 1000000; private static final String devNamePrefix = "/dev/xvd"; @Autowired private ConfigurationMediator configurationMediator; @Autowired private NotificationService notificationService; @Autowired private TaskService taskService; @PostConstruct public void init() { this.mountPoint = configurationMediator.getSdfsMountPoint(); } @Override public void deleteFile(String fileName) { Path path = Paths.get(mountPoint, fileName); File file = path.toFile(); if (file.exists()) { file.delete(); } else { LOG.error("File not found " + file.getAbsolutePath()); throw new SDFSException("File not found " + file.getAbsolutePath()); } } private void copyWithCpCommand(String source, String destination, CopyingTaskProgressDto dto, final String taskId) throws IOException { try { LOG.info("Copying from {} to {} started", source, destination); File dest = new File(destination); ProcessBuilder builder = new ProcessBuilder("cp", source, destination); Process process = builder.start(); while (!process.waitFor(3, TimeUnit.SECONDS)) { if (Thread.interrupted()) { process.destroy(); throw new EnhancedSnapshotsInterruptedException("Task interrupted"); } if (taskService.isCanceled(taskId)) { process.destroy(); throw new EnhancedSnapshotsTaskInterruptedException("Task canceled"); } dto.setCopyingProgress(dest.length()); notificationService.notifyAboutTaskProgress(dto); } switch (process.exitValue()) { case 0: LOG.info("Copying from {} to {} finished: {}", source, destination, dest.length()); break; default: { LOG.error("Failed to copy data from {} to {} ", source, destination); try (BufferedReader input = new BufferedReader(new InputStreamReader(process.getErrorStream()))) { StringBuilder errorMessage = new StringBuilder(); for (String line; (line = input.readLine()) != null; ) { errorMessage.append(line); } LOG.error(errorMessage.toString()); throw new EnhancedSnapshotsInterruptedException(errorMessage.toString()); } } } } catch (InterruptedException e) { throw new EnhancedSnapshotsInterruptedException(e); } } @Override public void copyData(String source, String destination, CopyingTaskProgressDto dto, String taskId) throws IOException { // for restoring we need to use java binary copy approach, so we could determine size of copied data // since otherwise progress bar will always show that 0 mb copied till restore is finished if (destination.startsWith(devNamePrefix)){ javaBinaryCopy(source, destination, dto, taskId); return; } // copy with native commands shows best performance // so it's better to use this approach for backups copyWithCpCommand(source, destination, dto, taskId); } private void javaBinaryCopy(String source, String destination, CopyingTaskProgressDto dto, final String taskId) throws IOException { try (FileInputStream fis = new FileInputStream(source); FileOutputStream fos = new FileOutputStream(destination)) { byte[] buffer = new byte[BYTES_IN_MEGABYTE]; int noOfBytes; long total = 0; LOG.info("Copying from {} to {} started", source, destination); while ((noOfBytes = fis.read(buffer)) != -1) { if (Thread.interrupted()) { throw new EnhancedSnapshotsInterruptedException("Task interrupted"); } if (taskService.isCanceled(taskId)) { throw new EnhancedSnapshotsTaskInterruptedException("Task canceled"); } fos.write(buffer, 0, noOfBytes); total += noOfBytes; dto.setCopyingProgress(total); notificationService.notifyAboutTaskProgress(dto); } LOG.info("Copying from {} to {} finished: {}", source, destination, total); } } @Override public long getSize(String filename) { Path file = Paths.get(filename); long size = -1; try { BasicFileAttributes attrs = Files.readAttributes(file, BasicFileAttributes.class); size = attrs.size(); } catch (IOException e) { e.printStackTrace(); } return size; } @Override public long getBackupCreationTime(String filename) { Path file = Paths.get(filename); long timestamp = -1; try { BasicFileAttributes attrs = Files.readAttributes(file, BasicFileAttributes.class); timestamp = attrs.creationTime().toMillis(); } catch (IOException e) { e.printStackTrace(); } return timestamp; } @Override public String detectFsDevName(Volume volume) { String devname = volume.getAttachments().get(0).getDevice(); File volf = new File(devname); if (!volf.exists() || !volf.isFile()) { LOG.info(format("Cant find attached source: %s", volume)); devname = devNamePrefix + devname.substring(devname.length() - 1); LOG.info(format("New source path : %s", devname)); } return devname; } }