package com.sungardas.enhancedsnapshots.service.impl;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBScanExpression;
import com.amazonaws.services.dynamodbv2.datamodeling.IDynamoDBMapper;
import com.amazonaws.services.ec2.model.VolumeType;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.ListObjectsRequest;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.amazonaws.services.s3.model.S3ObjectSummary;
import com.sungardas.enhancedsnapshots.aws.dynamodb.model.*;
import com.sungardas.enhancedsnapshots.aws.dynamodb.repository.ConfigurationRepository;
import com.sungardas.enhancedsnapshots.aws.dynamodb.repository.NodeRepository;
import com.sungardas.enhancedsnapshots.cluster.ClusterConfigurationService;
import com.sungardas.enhancedsnapshots.cluster.ClusterEventPublisher;
import com.sungardas.enhancedsnapshots.components.ConfigurationMediatorConfigurator;
import com.sungardas.enhancedsnapshots.components.WorkersDispatcher;
import com.sungardas.enhancedsnapshots.dto.Cluster;
import com.sungardas.enhancedsnapshots.dto.SystemConfiguration;
import com.sungardas.enhancedsnapshots.dto.converter.MailConfigurationDocumentConverter;
import com.sungardas.enhancedsnapshots.exception.EnhancedSnapshotsException;
import com.sungardas.enhancedsnapshots.service.*;
import com.sungardas.enhancedsnapshots.util.SystemUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Profile;
import org.springframework.core.env.PropertySource;
import org.springframework.stereotype.Service;
import org.springframework.web.context.support.XmlWebApplicationContext;
import javax.annotation.PostConstruct;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* Implementation for {@link SystemService}
*/
@Service("SystemService")
@DependsOn({"CreateAppConfiguration", "ConfigurationMediator"})
@Profile("prod")
public class SystemServiceImpl implements SystemService {
private static final Logger LOG = LogManager.getLogger(SystemServiceImpl.class);
private static final String CURRENT_VERSION = "0.0.3";
private static final String LATEST_VERSION = "latest-version";
private static final String INFO_URL = "http://com.sungardas.releases.s3.amazonaws.com/info";
private static final String TEMP_DIRECTORY_PREFIX = "systemBackupFiles";
private static final String INFO_FILE_NAME = "info";
private static final String VERSION_KEY = "version";
private static final String TEMP_FILE_SUFFIX = "ZIP";
private static final String[] VOLUME_TYPE_OPTIONS = new String[]{VolumeType.Gp2.toString(), VolumeType.Io1.toString(), VolumeType.Standard.toString()};
@Value("${enhancedsnapshots.system.reserved.memory}")
private int systemReservedRam;
@Value("${enhancedsnapshots.sdfs.volume.size.per.1gb.ram}")
private int volumeSizePerGbOfRam;
@Value("${enhancedsnapshots.sdfs.reserved.ram}")
private int sdfsReservedRam;
@Value("${enhancedsnapshots.system.reserved.storage}")
private int systemReservedStorage;
@Value("${enhancedsnapshots.saml.sp.cert.jks}")
private String samlCertJks;
@Value("${enhancedsnapshots.saml.idp.metadata}")
private String samlIdpMetadata;
@Autowired
@Qualifier("amazonDynamoDbMapper")
private IDynamoDBMapper dynamoDBMapper;
@Autowired
private AmazonS3 amazonS3;
@Autowired
private ConfigurationRepository configurationRepository;
@Autowired
private NodeRepository nodeRepository;
@Autowired
private ConfigurationMediatorConfigurator configurationMediator;
@Autowired
private SDFSStateService sdfsStateService;
@Autowired
private NotificationService notificationService;
private final ObjectMapper objectMapper = new ObjectMapper();
private Configuration currentConfiguration;
@Autowired
private XmlWebApplicationContext applicationContext;
@Autowired
private CryptoService cryptoService;
@Autowired
private MailService mailService;
@Autowired
private ClusterEventPublisher clusterEventPublisher;
@Autowired
private ClusterConfigurationService clusterConfigurationService;
@PostConstruct
private void init() {
currentConfiguration = configurationMediator.getCurrentConfiguration();
mailService.reconnect();
}
@Override
public void backup(final String taskId) {
try {
LOG.info("System backup started");
notificationService.notifyAboutRunningTaskProgress(taskId, "System backup started", 0);
Path tempDirectory = Files.createTempDirectory(TEMP_DIRECTORY_PREFIX);
LOG.info("Add info file");
notificationService.notifyAboutRunningTaskProgress(taskId, "System information backup", 5);
addInfo(tempDirectory);
LOG.info("Backup SDFS state");
notificationService.notifyAboutRunningTaskProgress(taskId, "Backup SDFS state", 10);
backupSDFS(tempDirectory, taskId);
notificationService.notifyAboutRunningTaskProgress(taskId, "Backup system files", 55);
LOG.info("Backup files");
storeFiles(tempDirectory);
LOG.info("Backup db");
notificationService.notifyAboutRunningTaskProgress(taskId, "Backup DB", 60);
backupDB(tempDirectory, taskId);
if (configurationMediator.isSsoLoginMode()){
LOG.info("Backup up saml certificate and ipd metadata", 90);
backupSamlFiles(tempDirectory);
}
LOG.info("Upload to S3");
notificationService.notifyAboutRunningTaskProgress(taskId, "Upload to S3", 95);
uploadToS3(tempDirectory);
tempDirectory.toFile().delete();
LOG.info("System backup finished");
} catch (IOException e) {
LOG.error("System backup failed");
LOG.error(e);
throw new EnhancedSnapshotsException(e);
}
}
/**
* Adding metainfo to system backup
* @param tempDirectory directory where system backup is stored
* @throws IOException checked file system exception
*/
private void addInfo(final Path tempDirectory) throws IOException {
File dest = Paths.get(tempDirectory.toString(), INFO_FILE_NAME).toFile();
Map<String, String> info = new HashMap<>();
info.put(VERSION_KEY, CURRENT_VERSION);
objectMapper.writeValue(dest, info);
}
private void backupDB(final Path tempDirectory, final String taskId) throws IOException {
notificationService.notifyAboutRunningTaskProgress(taskId, "Backup DB", 65);
storeTable(BackupEntry.class, tempDirectory);
notificationService.notifyAboutRunningTaskProgress(taskId, "Backup DB", 70);
storeTable(Configuration.class, tempDirectory);
notificationService.notifyAboutRunningTaskProgress(taskId, "Backup DB", 75);
storeTable(RetentionEntry.class, tempDirectory);
notificationService.notifyAboutRunningTaskProgress(taskId, "Backup DB", 80);
storeTable(SnapshotEntry.class, tempDirectory);
notificationService.notifyAboutRunningTaskProgress(taskId, "Backup DB", 85);
storeTable(User.class, tempDirectory);
notificationService.notifyAboutRunningTaskProgress(taskId, "Backup DB", 90);
}
private void backupSDFS(final Path tempDirectory, final String taskId) throws IOException {
notificationService.notifyAboutRunningTaskProgress(taskId, "Backup SDFS state", 15);
copyToDirectory(Paths.get(currentConfiguration.getSdfsConfigPath()), tempDirectory);
notificationService.notifyAboutRunningTaskProgress(taskId, "Backup SDFS state", 20);
sdfsStateService.cloudSync();
notificationService.notifyAboutRunningTaskProgress(taskId, "Backup SDFS state", 45);
}
private void backupSamlFiles(final Path tempDirectory) throws IOException {
copyToDirectory(Paths.get(System.getProperty("catalina.home"), samlCertJks), tempDirectory);
copyToDirectory(Paths.get(System.getProperty("catalina.home"), samlIdpMetadata), tempDirectory);
}
private void storeFiles(Path tempDirectory) {
//nginx certificates
try {
copyToDirectory(Paths.get(currentConfiguration.getNginxCertPath()), tempDirectory);
copyToDirectory(Paths.get(currentConfiguration.getNginxKeyPath()), tempDirectory);
} catch (IOException e) {
LOG.warn("Nginx certificate not found");
}
}
private void uploadToS3(final Path tempDirectory) throws IOException {
// Compress to zip
LOG.info(" -Compress files");
File[] files = tempDirectory.toFile().listFiles();
Path zipFile = Files.createTempFile(TEMP_DIRECTORY_PREFIX, TEMP_FILE_SUFFIX);
try (FileOutputStream fos = new FileOutputStream(zipFile.toFile());
ZipOutputStream zos = new ZipOutputStream(fos)) {
for (File file : files) {
zos.putNextEntry(new ZipEntry(file.getName()));
Files.copy(file.toPath(), zos);
zos.closeEntry();
}
}
//Upload
LOG.info(" -Upload");
PutObjectRequest putObjectRequest = new PutObjectRequest(configurationMediator.getS3Bucket(),
configurationMediator.getSdfsBackupFileName(), zipFile.toFile());
amazonS3.putObject(putObjectRequest);
zipFile.toFile().delete();
}
private void storeTable(Class tableClass, Path tempDirectory) throws IOException {
LOG.info("Backup DB table: {}", tableClass.getSimpleName());
File dest = Paths.get(tempDirectory.toString(), tableClass.getName()).toFile();
List result = dynamoDBMapper.scan(tableClass, new DynamoDBScanExpression());
objectMapper.writeValue(dest, result);
}
@Override
public SystemConfiguration getSystemConfiguration() {
SystemConfiguration configuration = new SystemConfiguration();
configuration.setSystemId(configurationMediator.getConfigurationId());
configuration.setS3(new SystemConfiguration.S3());
configuration.getS3().setBucketName(configurationMediator.getS3Bucket());
configuration.setSdfs(new SystemConfiguration.SDFS());
configuration.getSdfs().setMountPoint(configurationMediator.getSdfsMountPoint());
configuration.getSdfs().setVolumeName(configurationMediator.getSdfsVolumeName());
configuration.getSdfs().setVolumeSize(currentConfiguration.getSdfsSize());
// user can only expand volume size
configuration.getSdfs().setMinVolumeSize(currentConfiguration.getSdfsSize());
configuration.getSdfs().setMaxVolumeSize(SDFSStateService.getMaxVolumeSize(systemReservedRam, volumeSizePerGbOfRam, sdfsReservedRam));
configuration.getSdfs().setSdfsLocalCacheSize(currentConfiguration.getSdfsLocalCacheSize());
configuration.getSdfs().setMaxSdfsLocalCacheSize(SDFSStateService.getFreeStorageSpace(systemReservedStorage) + configurationMediator.getSdfsLocalCacheSizeWithoutMeasureUnit());
configuration.getSdfs().setMinSdfsLocalCacheSize(configurationMediator.getSdfsLocalCacheSizeWithoutMeasureUnit());
configuration.setEc2Instance(new SystemConfiguration.EC2Instance());
configuration.getEc2Instance().setInstanceIDs(getInstanceIDs());
configuration.setLastBackup(getBackupTime());
configuration.setCurrentVersion(CURRENT_VERSION);
configuration.setLatestVersion(getLatestVersion());
SystemConfiguration.SystemProperties systemProperties = new SystemConfiguration.SystemProperties();
systemProperties.setRestoreVolumeIopsPerGb(configurationMediator.getRestoreVolumeIopsPerGb());
systemProperties.setRestoreVolumeType(configurationMediator.getRestoreVolumeType().toString());
systemProperties.setTempVolumeIopsPerGb(configurationMediator.getTempVolumeIopsPerGb());
systemProperties.setTempVolumeType(configurationMediator.getTempVolumeType().toString());
systemProperties.setVolumeTypeOptions(VOLUME_TYPE_OPTIONS);
systemProperties.setAmazonRetryCount(configurationMediator.getAmazonRetryCount());
systemProperties.setAmazonRetrySleep(configurationMediator.getAmazonRetrySleep());
systemProperties.setMaxQueueSize(configurationMediator.getMaxQueueSize());
systemProperties.setStoreSnapshots(configurationMediator.isStoreSnapshot());
systemProperties.setTaskHistoryTTS(configurationMediator.getTaskHistoryTTS());
systemProperties.setLogsBuffer(configurationMediator.getLogsBufferSize());
configuration.setSystemProperties(systemProperties);
configuration.setSsoMode(configurationMediator.isSsoLoginMode());
configuration.setDomain(configurationMediator.getDomain());
configuration.setMailConfiguration(MailConfigurationDocumentConverter.toMailConfigurationDto(configurationMediator.getMailConfiguration()));
Cluster clusterDto = new Cluster();
clusterDto.setMaxNodeNumber(configurationMediator.getMaxNodeNumberInCluster());
clusterDto.setMinNodeNumber(configurationMediator.getMinNodeNumberInCluster());
configuration.setCluster(clusterDto);
configuration.setClusterMode(configurationMediator.isClusterMode());
configuration.setUUID(configurationMediator.getUUID());
return configuration;
}
private String[] getInstanceIDs() {
List<String> ids = new ArrayList<>();
nodeRepository.findAll().stream().forEach(node -> ids.add(node.getNodeId()));
return ids.toArray(new String[ids.size()]);
}
@Override
public void setSystemConfiguration(SystemConfiguration configuration) {
LOG.info("Updating system properties.");
// mail configuration
boolean mailReconnect = false;
configuration.setUUID(currentConfiguration.getUUID());
if (configuration.getMailConfiguration() == null) {
currentConfiguration.setMailConfigurationDocument(null);
mailService.disconnect();
} else {
currentConfiguration.setMailConfigurationDocument(MailConfigurationDocumentConverter.toMailConfigurationDocument(configuration.getMailConfiguration(),
cryptoService, currentConfiguration.getConfigurationId(), currentConfiguration.getMailConfigurationDocument().getPassword()));
mailReconnect = true;
}
// update system properties
currentConfiguration.setRestoreVolumeIopsPerGb(configuration.getSystemProperties().getRestoreVolumeIopsPerGb());
currentConfiguration.setRestoreVolumeType(configuration.getSystemProperties().getRestoreVolumeType());
currentConfiguration.setTempVolumeIopsPerGb(configuration.getSystemProperties().getTempVolumeIopsPerGb());
currentConfiguration.setTempVolumeType(configuration.getSystemProperties().getTempVolumeType());
currentConfiguration.setAmazonRetryCount(configuration.getSystemProperties().getAmazonRetryCount());
currentConfiguration.setAmazonRetrySleep(configuration.getSystemProperties().getAmazonRetrySleep());
currentConfiguration.setMaxQueueSize(configuration.getSystemProperties().getMaxQueueSize());
currentConfiguration.setStoreSnapshot(configuration.getSystemProperties().isStoreSnapshots());
currentConfiguration.setTaskHistoryTTS(configuration.getSystemProperties().getTaskHistoryTTS());
currentConfiguration.setLogsBufferSize(configuration.getSystemProperties().getLogsBuffer());
if (configuration.getDomain() == null) {
currentConfiguration.setDomain("");
}
currentConfiguration.setDomain(configuration.getDomain());
// update sdfs setting
currentConfiguration.setSdfsSize(configuration.getSdfs().getVolumeSize());
currentConfiguration.setSdfsLocalCacheSize(configuration.getSdfs().getSdfsLocalCacheSize());
// update bucket
currentConfiguration.setS3Bucket(configuration.getS3().getBucketName());
if (configurationMediator.isClusterMode()) {
currentConfiguration.setMaxNodeNumber(configuration.getCluster().getMaxNodeNumber());
currentConfiguration.setMinNodeNumber(configuration.getCluster().getMinNodeNumber());
clusterConfigurationService.updateAutoScalingSettings(configuration.getCluster().getMinNodeNumber(),
configuration.getCluster().getMaxNodeNumber());
}
configurationRepository.save(currentConfiguration);
configurationMediator.setCurrentConfiguration(currentConfiguration);
clusterEventPublisher.settingsUpdated();
if (mailReconnect) {
mailService.reconnect();
}
}
@Override
public void systemUninstall(boolean removeS3Bucket) {
LOG.info("Uninstalling system. S3 bucket will be removed: {}", removeS3Bucket);
applicationContext.setConfigLocation("/WEB-INF/destroy-spring-web-config.xml");
applicationContext.getAutowireCapableBeanFactory().destroyBean(WorkersDispatcher.class);
new Thread() {
@Override
public void run() {
applicationContext.getEnvironment().getPropertySources().addLast(
new DestroyContextPropertySource(removeS3Bucket));
LOG.info("Context refresh started");
applicationContext.refresh();
}
}.start();
}
@Override
public void refreshSystemConfiguration() {
currentConfiguration = dynamoDBMapper.load(Configuration.class, getSystemId());
configurationMediator.setCurrentConfiguration(currentConfiguration);
}
/**
* Returns the latest official version of application
* Current version taken from {@link SystemServiceImpl::INFO_URL}
* @return latest version
*/
private String getLatestVersion() {
try {
URL infoURL = new URL(INFO_URL);
Properties properties = new Properties();
properties.load(infoURL.openStream());
String latestVersion = properties.getProperty(LATEST_VERSION);
if (latestVersion != null) {
return latestVersion;
}
} catch (Exception e) {
}
return CURRENT_VERSION;
}
protected String getSystemId() {
return SystemUtils.getSystemId();
}
//TODO: this should be stored in DB
private 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;
}
}
private void copyToDirectory(Path src, Path dest) throws IOException {
Path srcFileName = src.getFileName();
Files.copy(src, Paths.get(dest.toString(), srcFileName.toString()), StandardCopyOption.REPLACE_EXISTING);
}
// Passing removeS3Bucket property to context
private static class DestroyContextPropertySource extends PropertySource<String> {
private boolean removeS3Bucket;
public DestroyContextPropertySource(boolean removeS3Bucket) {
super("custom");
this.removeS3Bucket = removeS3Bucket;
}
@Override
public String getProperty(String name) {
if (name.equals("removeS3Bucket")) {
return Boolean.toString(removeS3Bucket);
}
return null;
}
}
}