package com.sungardas.enhancedsnapshots.service.impl; import com.amazonaws.services.ec2.model.VolumeType; import com.sungardas.enhancedsnapshots.aws.dynamodb.model.BackupEntry; import com.sungardas.enhancedsnapshots.aws.dynamodb.model.EventEntry; import com.sungardas.enhancedsnapshots.aws.dynamodb.model.TaskEntry; import com.sungardas.enhancedsnapshots.aws.dynamodb.repository.BackupRepository; import com.sungardas.enhancedsnapshots.aws.dynamodb.repository.TaskRepository; import com.sungardas.enhancedsnapshots.cluster.ClusterEventListener; import com.sungardas.enhancedsnapshots.components.ConfigurationMediator; import com.sungardas.enhancedsnapshots.dto.ExceptionDto; import com.sungardas.enhancedsnapshots.dto.TaskDto; import com.sungardas.enhancedsnapshots.dto.converter.TaskDtoConverter; import com.sungardas.enhancedsnapshots.enumeration.TaskProgress; import com.sungardas.enhancedsnapshots.exception.DataAccessException; import com.sungardas.enhancedsnapshots.exception.EnhancedSnapshotsException; import com.sungardas.enhancedsnapshots.service.NotificationService; import com.sungardas.enhancedsnapshots.service.SchedulerService; import com.sungardas.enhancedsnapshots.service.Task; import com.sungardas.enhancedsnapshots.service.TaskService; import com.sungardas.enhancedsnapshots.tasks.executors.AWSRestoreVolumeStrategyTaskExecutor; import com.sungardas.enhancedsnapshots.util.SystemUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.support.CronTrigger; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import java.util.*; import java.util.stream.Collectors; import static com.sungardas.enhancedsnapshots.aws.dynamodb.model.TaskEntry.TaskEntryStatus.CANCELED; @Service public class TaskServiceImpl implements TaskService, ClusterEventListener { private static final Logger LOG = LogManager.getLogger(TaskServiceImpl.class); @Autowired private TaskRepository taskRepository; @Autowired private BackupRepository backupRepository; @Autowired private ConfigurationMediator configurationMediator; @Autowired private SchedulerService schedulerService; @Autowired private NotificationService notificationService; private Set<String> canceledTasks = new HashSet<>(); @PostConstruct private void init() { List<TaskEntry> partiallyFinished = taskRepository.findByStatusAndRegularAndWorker(TaskEntry.TaskEntryStatus.RUNNING.getStatus(), Boolean.FALSE.toString(), SystemUtils.getInstanceId()); partiallyFinished.forEach(t -> t.setStatus(TaskEntry.TaskEntryStatus.PARTIALLY_FINISHED.getStatus())); // method save(Iterable<S> var1) in spring-data-dynamodb 4.4.1 does not work correctly, that's why we have to save every taskEntry separately // this should be changed once save(Iterable<S> var1) in spring-data-dynamodb is fixed for(TaskEntry taskEntry: partiallyFinished){ taskRepository.save(taskEntry); } schedulerService.addTask(new Task() { @Override public String getId() { return "canceledTaskCheck"; } @Override public void run() { updateCanceledTasks(); } }, new CronTrigger("*/5 * * * * *")); } @Override public Map<String, String> createTask(TaskDto taskDto) { Map<String, String> messages = new HashMap<>(); String configurationId = configurationMediator.getConfigurationId(); List<TaskEntry> validTasks = new ArrayList<>(); int tasksInQueue = getTasksInQueue(); boolean regular = Boolean.valueOf(taskDto.getRegular()); for (TaskEntry taskEntry : TaskDtoConverter.convert(taskDto)) { if (!regular && tasksInQueue >= configurationMediator.getMaxQueueSize()) { notificationService.notifyAboutError(new ExceptionDto("Task creation error", "Task queue is full")); break; } if (!configurationMediator.isClusterMode()) { taskEntry.setWorker(configurationId); } taskEntry.setStatus(TaskEntry.TaskEntryStatus.QUEUED.getStatus()); taskEntry.setId(UUID.randomUUID().toString()); // set tempVolumeType and iops if required setTempVolumeAndIops(taskEntry); if (regular) { try { schedulerService.addTask(taskEntry); messages.put(taskEntry.getVolume(), getMessage(taskEntry)); validTasks.add(taskEntry); tasksInQueue++; } catch (EnhancedSnapshotsException e) { notificationService.notifyAboutError(new ExceptionDto("Task creation has failed", e.getLocalizedMessage())); LOG.error(e); messages.put(taskEntry.getVolume(), e.getLocalizedMessage()); } } else if (TaskEntry.TaskEntryType.RESTORE.getType().equals(taskEntry.getType())) { if (backupRepository.findByVolumeId(taskEntry.getVolume()).isEmpty()) { notificationService.notifyAboutError(new ExceptionDto("Restore task error", "Backup for volume: " + taskEntry.getVolume() + " not found!")); messages.put(taskEntry.getVolume(), "Restore task error"); } else { setRestoreVolumeTypeAndIops(taskEntry); messages.put(taskEntry.getVolume(), getMessage(taskEntry)); validTasks.add(taskEntry); tasksInQueue++; } } else { messages.put(taskEntry.getVolume(), getMessage(taskEntry)); validTasks.add(taskEntry); tasksInQueue++; } } // method save(Iterable<S> var1) in spring-data-dynamodb 4.4.1 does not work correctly, that's why we have to save every taskEntry separately // this should be changed once save(Iterable<S> var1) in spring-data-dynamodb is fixed for(TaskEntry taskEntry: validTasks){ taskRepository.save(taskEntry); } return messages; } private String getMessage(TaskEntry taskEntry) { switch (taskEntry.getType()) { case "restore": { List<BackupEntry> backupEntry; String sourceFile = taskEntry.getSourceFileName(); if (sourceFile == null || sourceFile.isEmpty()) { backupEntry = backupRepository.findByVolumeId(taskEntry.getVolume()); } else { backupEntry = backupRepository.findByFileName(sourceFile); } if (backupEntry == null || backupEntry.isEmpty()) { //TODO: add more messages return "Unable to execute: backup history is empty"; } else { return AWSRestoreVolumeStrategyTaskExecutor.RESTORED_NAME_PREFIX + backupEntry.get(backupEntry.size() - 1).getFileName(); } } } return "Processed"; } @Override public List<TaskDto> getAllTasks() { try { return TaskDtoConverter.convert(taskRepository.findByStatusNotAndRegular(TaskEntry.TaskEntryStatus.COMPLETE.toString(), Boolean.FALSE.toString()), taskRepository.findByRegularAndCompleteTimeGreaterThanEqual(Boolean.FALSE.toString(), System.currentTimeMillis() - configurationMediator.getTaskHistoryTTS())); } catch (RuntimeException e) { notificationService.notifyAboutError(new ExceptionDto("Getting tasks have failed", "Failed to get tasks.")); LOG.error("Failed to get tasks.", e); throw new DataAccessException("Failed to get tasks.", e); } } @Override public List<TaskDto> getAllTasks(String volumeId) { try { return TaskDtoConverter.convert(taskRepository.findByRegularAndVolume(Boolean.FALSE.toString(), volumeId)); } catch (RuntimeException e) { notificationService.notifyAboutError(new ExceptionDto("Getting tasks have failed", "Failed to get tasks.")); LOG.error("Failed to get tasks.", e); throw new DataAccessException("Failed to get tasks.", e); } } @Override public void complete(TaskEntry taskEntry) { taskEntry.setCompleteTime(System.currentTimeMillis()); taskEntry.setStatus(TaskEntry.TaskEntryStatus.COMPLETE.getStatus()); taskRepository.save(taskEntry); } @Override public boolean isQueueFull() { return getTasksInQueue() > configurationMediator.getMaxQueueSize(); } @Override public boolean isCanceled(final String taskId) { return canceledTasks.remove(taskId); } @Override public List<TaskDto> getAllRegularTasks(String volumeId) { try { return TaskDtoConverter.convert(taskRepository.findByRegularAndVolume(Boolean.TRUE.toString(), volumeId)); } catch (RuntimeException e) { notificationService.notifyAboutError(new ExceptionDto("Getting tasks have failed", "Failed to get tasks.")); LOG.error("Failed to get tasks.", e); throw new DataAccessException("Failed to get tasks.", e); } } @Override public void removeTask(String id) { if (taskRepository.exists(id)) { TaskEntry taskEntry = taskRepository.findOne(id); if (TaskEntry.TaskEntryStatus.RUNNING.getStatus().equals(taskEntry.getStatus())) { taskEntry.setStatus(CANCELED.toString()); taskRepository.save(taskEntry); canceledTasks.add(id); updateCanceledTasks(); notificationService.notifyAboutTaskProgress(id, "Canceling...", 0, CANCELED); return; } taskRepository.delete(id); if (Boolean.valueOf(taskEntry.getRegular())) { schedulerService.removeTask(taskEntry.getId()); } LOG.info("TaskEntry {} was removed successfully.", id); } else { LOG.info("TaskEntry {} can not be removed since it does not exist.", id); } } @Override public boolean exists(String id) { return taskRepository.exists(id); } @Override public void updateTask(TaskDto taskInfo) { removeTask(taskInfo.getId()); createTask(taskInfo); } private int getTasksInQueue() { return (int) (taskRepository.countByRegularAndTypeAndStatus(Boolean.FALSE.toString(), TaskEntry.TaskEntryType.BACKUP.getType(), TaskEntry.TaskEntryStatus.QUEUED.getStatus()) + taskRepository.countByRegularAndTypeAndStatus(Boolean.FALSE.toString(), TaskEntry.TaskEntryType.RESTORE.getType(), TaskEntry.TaskEntryStatus.QUEUED.getStatus()) + taskRepository.countByRegularAndTypeAndStatus(Boolean.FALSE.toString(), TaskEntry.TaskEntryType.BACKUP.getType(), TaskEntry.TaskEntryStatus.WAITING.getStatus()) + taskRepository.countByRegularAndTypeAndStatus(Boolean.FALSE.toString(), TaskEntry.TaskEntryType.RESTORE.getType(), TaskEntry.TaskEntryStatus.WAITING.getStatus())); } // for restore and backup tasks we should specify temp volume type private void setTempVolumeAndIops(TaskEntry taskEntry){ if(taskEntry.getType().equals(TaskEntry.TaskEntryType.RESTORE.getType()) || taskEntry.getType().equals(TaskEntry.TaskEntryType.BACKUP.getType())){ taskEntry.setTempVolumeType(configurationMediator.getTempVolumeType()); if (configurationMediator.getTempVolumeType().equals(VolumeType.Io1.toString())) { taskEntry.setTempVolumeIopsPerGb(configurationMediator.getTempVolumeIopsPerGb()); } } } // for restore tasks we should specify restore volume type private void setRestoreVolumeTypeAndIops(TaskEntry taskEntry){ if(taskEntry.getType().equals(TaskEntry.TaskEntryType.RESTORE.getType())){ taskEntry.setRestoreVolumeType(configurationMediator.getRestoreVolumeType()); if (configurationMediator.getRestoreVolumeType().equals(VolumeType.Io1.toString())) { taskEntry.setRestoreVolumeIopsPerGb(configurationMediator.getRestoreVolumeIopsPerGb()); } } } private void updateCanceledTasks() { canceledTasks = taskRepository.findByStatusAndRegular(CANCELED.toString(), Boolean.FALSE.toString()) .stream().map(t -> t.getId()).collect(Collectors.toSet()); } @Override public void launched(EventEntry eventEntry) { } @Override public void terminated(EventEntry eventEntry) { try { List<TaskEntry> partiallyFinishedTasks = taskRepository.findByWorkerAndProgressNot(eventEntry.getInstanceId(), TaskProgress.DONE.name()); partiallyFinishedTasks.forEach(t -> { t.setStatus(TaskEntry.TaskEntryStatus.PARTIALLY_FINISHED.toString()); t.setWorker(null); }); taskRepository.save(partiallyFinishedTasks); } catch (Exception e) { LOG.error(e); } } }