package com.sungardas.enhancedsnapshots.service.impl;
import com.amazonaws.AmazonClientException;
import com.sungardas.enhancedsnapshots.aws.dynamodb.model.BackupEntry;
import com.sungardas.enhancedsnapshots.aws.dynamodb.model.RetentionEntry;
import com.sungardas.enhancedsnapshots.aws.dynamodb.repository.BackupRepository;
import com.sungardas.enhancedsnapshots.aws.dynamodb.repository.RetentionRepository;
import com.sungardas.enhancedsnapshots.components.ConfigurationMediator;
import com.sungardas.enhancedsnapshots.dto.RetentionDto;
import com.sungardas.enhancedsnapshots.exception.DataAccessException;
import com.sungardas.enhancedsnapshots.service.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*;
import static com.sungardas.enhancedsnapshots.dto.converter.RetentionConverter.toDto;
import static com.sungardas.enhancedsnapshots.dto.converter.RetentionConverter.toEntry;
@Service
public class RetentionServiceImpl implements RetentionService, MasterInitialization {
public static final String RETENTION_USER = "RETENTION POLICY";
private static final long BYTES_IN_GB = 1073741824;
private static final Logger LOG = LogManager.getLogger(RetentionServiceImpl.class);
@Autowired
private RetentionRepository retentionRepository;
@Autowired
private BackupRepository backupRepository;
@Autowired
private BackupService backupService;
@Autowired
private VolumeService volumeService;
@Autowired
private SchedulerService schedulerService;
@Autowired
private ConfigurationMediator configurationMediator;
@Override
public void init() {
schedulerService.addTask(getJob(this), configurationMediator.getRetentionCronExpression());
try {
apply();
} catch (AmazonClientException e) {
LOG.error(e);
}
}
@Override
public void putRetention(RetentionDto retentionDto) {
if (volumeService.volumeExists(retentionDto.getVolumeId())) {
retentionRepository.save(toEntry(retentionDto));
apply();
} else {
LOG.error("Volume with id: {} not found", retentionDto.getVolumeId());
throw new DataAccessException("Volume with id:" + retentionDto.getVolumeId() + " not found");
}
}
@Override
public RetentionDto getRetentionDto(String volumeId) {
try {
return toDto(retentionRepository.findOne(volumeId));
} catch (Exception e) {
if (volumeService.volumeExists(volumeId)) {
return new RetentionDto(volumeId);
} else {
LOG.error("Volume with id: {} not found", volumeId);
throw new DataAccessException("Volume with id:" + volumeId + " not found");
}
}
}
@Override
public void apply() {
LOG.debug("Retention started");
Map<String, Set<BackupEntry>> backups = getBackups();
Map<String, RetentionEntry> retentions = getRetentions();
Set<BackupEntry> backupsToRemove = new LinkedHashSet<>();
for (Map.Entry<String, Set<BackupEntry>> entry : backups.entrySet()) {
RetentionEntry retentionEntry = retentions.get(entry.getKey());
if (retentionEntry != null) {
if (isEmpty(retentionEntry)) {
retentionRepository.delete(retentionEntry.getVolumeId());
} else {
BackupEntry[] values = entry.getValue().toArray(new BackupEntry[entry.getValue().size()]);
applySizeRetention(backupsToRemove, values, retentionEntry);
applyCountRetention(backupsToRemove, values, retentionEntry);
applyDayRetention(backupsToRemove, values, retentionEntry);
}
}
}
for (Map.Entry<String, RetentionEntry> entry : retentions.entrySet()) {
if (!backups.containsKey(entry.getKey())) {
retentionRepository.delete(entry.getValue().getVolumeId());
}
}
if (!backupsToRemove.isEmpty()) {
LOG.debug("Found backup to remove: {}", backupsToRemove);
backupService.deleteBackup(backupsToRemove, RETENTION_USER);
LOG.debug("Backups successfully removed");
}
LOG.debug("Finished");
}
private boolean isEmpty(RetentionEntry retentionEntry) {
return retentionEntry.getCount() < 1 && retentionEntry.getDays() < 1 && retentionEntry.getSize() < 1;
}
private void applySizeRetention(Set<BackupEntry> backupsToRemove, BackupEntry[] backups, RetentionEntry retention) {
if (retention.getSize() > 0 && backups.length > 0) {
long retentionSize = retention.getSize() * BYTES_IN_GB;
long size = 0;
int i;
for (i = 0; i < backups.length && size <= retentionSize; i++) {
size += parseLong(backups[i].getSize());
}
if (size > retentionSize) {
i--;
for (; i < backups.length; i++) {
backupsToRemove.add(backups[i]);
}
}
}
}
private void applyCountRetention(Set<BackupEntry> backupsToRemove, BackupEntry[] backups, RetentionEntry retention) {
if (retention.getCount() > 0) {
for (int i = retention.getCount(); i < backups.length; i++) {
backupsToRemove.add(backups[i]);
}
}
}
private void applyDayRetention(Set<BackupEntry> backupsToRemove, BackupEntry[] backups, RetentionEntry retention) {
if (retention.getDays() > 0) {
DateTime currentDateTime = new DateTime();
for (int i = 0; i < backups.length; i++) {
DateTime creationDate = new DateTime(parseLong(backups[i].getTimeCreated()));
DateTime expireTime = creationDate.plusDays(retention.getDays());
if (currentDateTime.isAfter(expireTime)) {
backupsToRemove.add(backups[i]);
}
}
}
}
private Map<String, Set<BackupEntry>> getBackups() {
Map<String, Set<BackupEntry>> backupsSortedByCreationTime = new HashMap<>();
for (BackupEntry backupEntry : backupRepository.findAll()) {
Set<BackupEntry> backups = backupsSortedByCreationTime.get(backupEntry.getVolumeId());
if (backups == null) {
backups = new TreeSet<>(backupComparatorByCreationTime);
backupsSortedByCreationTime.put(backupEntry.getVolumeId(), backups);
}
backups.add(backupEntry);
}
return backupsSortedByCreationTime;
}
private Map<String, RetentionEntry> getRetentions() {
Map<String, RetentionEntry> retentionEntryMap = new HashMap<>();
for (RetentionEntry entry : retentionRepository.findAll()) {
retentionEntryMap.put(entry.getVolumeId(), entry);
}
return retentionEntryMap;
}
private long parseLong(String value) {
try {
return Long.parseLong(value);
} catch (NumberFormatException e) {
LOG.debug(e);
return 0;
}
}
private static final Comparator<BackupEntry> backupComparatorByCreationTime = new Comparator<BackupEntry>() {
@Override
public int compare(BackupEntry o1, BackupEntry o2) {
return o2.getTimeCreated().compareTo(o1.getTimeCreated());
}
};
public Task getJob(final RetentionService retentionService) {
return new Task() {
@Override
public void run() {
retentionService.apply();
}
@Override
public String getId() {
return retentionService.getClass().toString();
}
};
}
}