package com.sungardas.enhancedsnapshots.service.impl; import com.amazonaws.services.ec2.AmazonEC2; import com.amazonaws.services.ec2.model.*; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.model.*; import com.sungardas.enhancedsnapshots.components.ConfigurationMediator; import com.sungardas.enhancedsnapshots.exception.EnhancedSnapshotsException; import com.sungardas.enhancedsnapshots.service.AWSCommunicationService; 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.context.annotation.Profile; import org.springframework.stereotype.Service; import java.nio.file.Files; import java.nio.file.Paths; import java.text.SimpleDateFormat; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import static java.lang.String.format; @Service @Profile("prod") public class AWSCommunicationServiceImpl implements AWSCommunicationService { private static final Logger LOG = LogManager.getLogger(AWSCommunicationServiceImpl.class); private static final String AVAILABLE_STATE = VolumeState.Available.toString(); private final static int MIN_SIZE_OF_OI1_VOLUME = 4; private final static int MIN_IOPS_VALUE = 100; private final static int MAX_IOPS_VALUE = 20_000; @Autowired private AmazonS3 amazonS3; @Autowired private AmazonEC2 ec2client; @Autowired private ConfigurationMediator configurationMediator; @Override public List<AvailabilityZone> describeAvailabilityZonesForCurrentRegion() { return ec2client.describeAvailabilityZones().getAvailabilityZones(); } @Override public String getCurrentAvailabilityZone() { return getInstance(SystemUtils.getInstanceId()).getPlacement() .getAvailabilityZone(); } @Override public void createTemporaryTag(String resourceId, String description) { CreateTagsRequest tagsRequest = new CreateTagsRequest().withResources(resourceId).withTags( new Tag().withKey("ESTempVolume").withValue(description)); ec2client.createTags(tagsRequest); } @Override public void deleteTemporaryTag(String resourceId) { List<Volume> volumes= ec2client.describeVolumes(new DescribeVolumesRequest().withVolumeIds(resourceId)).getVolumes(); if(volumes.size()>0) { Volume volume = volumes.get(0); List<Tag> tags = volume.getTags(); boolean tagWasDeleted = false; for(Tag tag: tags) { if (tag.getKey().equals("ESTempVolume")) { DeleteTagsRequest tagsRequest = new DeleteTagsRequest().withResources(resourceId).withTags(); ec2client.deleteTags(tagsRequest); tagWasDeleted = true; break; } } if(! tagWasDeleted) { LOG.info("No temporary tag associated with volume {}",resourceId); } } else { LOG.info("Volume with id {} does not exist ",resourceId); } } private Volume createVolume(int size, int iiops, VolumeType type) { String availabilityZone = getInstance(SystemUtils.getInstanceId()).getPlacement() .getAvailabilityZone(); CreateVolumeRequest createVolumeRequest = new CreateVolumeRequest() .withSize(size).withVolumeType(type) .withAvailabilityZone(availabilityZone); if (iiops > 0) { createVolumeRequest = createVolumeRequest.withIops(iiops); } Volume result = ec2client.createVolume(createVolumeRequest).getVolume(); return result; } @Override public Volume createVolume(int size, VolumeType type) { return createVolume(size, 0, type); } @Override public Volume createIO1Volume(int size, int iopsPerGb) { // io1 volume size can not be less than 4 Gb size = size < MIN_SIZE_OF_OI1_VOLUME ? MIN_SIZE_OF_OI1_VOLUME : size; return createVolume(size < MIN_SIZE_OF_OI1_VOLUME ? MIN_SIZE_OF_OI1_VOLUME : size, getIops(iopsPerGb, size), VolumeType.Io1); } @Override public Snapshot createSnapshot(Volume volume) { SimpleDateFormat formatter = new SimpleDateFormat( "yyyy.MM.dd'_T'hh:mm:ss"); String volumeId = volume.getVolumeId(); LOG.info(format("Starting creating snapshot for %s", volumeId)); CreateSnapshotRequest snapshotRequest = new CreateSnapshotRequest( volumeId, volumeId + "__" + formatter.format(new Date(System.currentTimeMillis()))); CreateSnapshotResult crSnapshotResult = ec2client .createSnapshot(snapshotRequest); Snapshot snapshot = crSnapshotResult.getSnapshot(); return snapshot; } @Override public void deleteSnapshot(String snapshotId) { LOG.info(format("Deleting snapshot: %s", snapshotId)); DeleteSnapshotRequest deleteSnapshotRequest = new DeleteSnapshotRequest(); deleteSnapshotRequest.setSnapshotId(snapshotId); try { ec2client.deleteSnapshot(deleteSnapshotRequest); } catch (Throwable e) { LOG.info("Snapshot with id {} does not exist ", snapshotId); } } @Override public void cleanupSnapshots(String volumeId, String snapshotIdToLeave) { deleteSnapshot(snapshotIdToLeave); } @Override public Snapshot waitForCompleteState(Snapshot snapshot) { Snapshot syncSnapshot; do { sleepTillNextSync(); syncSnapshot = syncSnapshot(snapshot.getSnapshotId()); LOG.debug("Snapshot state: {}, progress: {}", syncSnapshot.getState(), syncSnapshot.getProgress()); if (syncSnapshot.getState().equals(SnapshotState.Error)) { new AWSCommunicationServiceException("Snapshot " + snapshot.getSnapshotId() + " is in error state"); } } while (syncSnapshot.getState().equals(SnapshotState.Pending) || !syncSnapshot.getProgress().equals("100%")); return syncSnapshot; } @Override public Snapshot syncSnapshot(String snapshotId) { Snapshot syncSnapshot = getSnapshot(snapshotId); if (syncSnapshot != null) { return syncSnapshot; } LOG.error("Failed to sync snapshot {}. Snapshot does not exist.", snapshotId); throw new AWSCommunicationServiceException("Can not sync snapshot. Snapshot " + snapshotId + " does not exist."); } @Override public Volume waitForVolumeState(Volume volume, VolumeState expectedState) { String state; Volume result; do { sleepTillNextSync(); result = syncVolume(volume); state = result.getState(); LOG.debug("waitForAvailableState.current state: " + state); if (state.equals(VolumeState.Error.toString())) { throw new EnhancedSnapshotsException("Volume state error, volumeID: " + volume.getVolumeId()); } } while (!state.equals(expectedState.toString()) && !state.equals(VolumeState.Deleted.toString())); return result; } @Override public Volume getVolume(String volumeId) { DescribeVolumesRequest describeVolumesRequest = new DescribeVolumesRequest().withVolumeIds(volumeId); DescribeVolumesResult result = ec2client.describeVolumes(describeVolumesRequest); if (result.getVolumes().size() == 1) { return result.getVolumes().get(0); } return null; } @Override public Instance getInstance(String instanceId) { DescribeInstancesRequest describeInstancesRequest = new DescribeInstancesRequest() .withInstanceIds(instanceId); DescribeInstancesResult describeInstancesResult = ec2client .describeInstances(describeInstancesRequest); List<Reservation> reservations = describeInstancesResult .getReservations(); for (Reservation res : reservations) { if (res.getInstances().size() > 0) { return res.getInstances().get(0); } } return null; } @Override public void detachVolume(Volume volume) { DetachVolumeRequest detachVolumeRequest = new DetachVolumeRequest(volume.getVolumeId()); ec2client.detachVolume(detachVolumeRequest); waitVolumeToDetach(volume); } public Volume createVolumeFromSnapshot(String snapshotId, String availabilityZoneName, VolumeType type, int iopsPerGb) { CreateVolumeRequest crVolumeRequest = new CreateVolumeRequest(snapshotId, availabilityZoneName); crVolumeRequest.setVolumeType(type); if (type.equals(VolumeType.Io1)) { Snapshot snapshot = getSnapshot(snapshotId); // io1 volume size can not be less than 4 Gb int size = snapshot.getVolumeSize() < MIN_SIZE_OF_OI1_VOLUME ? MIN_SIZE_OF_OI1_VOLUME : snapshot.getVolumeSize(); crVolumeRequest.setSize(size); // setting iops if (iopsPerGb != 0) { crVolumeRequest.setIops(getIops(iopsPerGb, size)); } } return ec2client.createVolume(crVolumeRequest).getVolume(); } @Override public Volume syncVolume(Volume volume) { Volume syncVolume = getVolume(volume.getVolumeId()); if (syncVolume != null) { return syncVolume; } LOG.error("Failed to sync volume {}. Volume does not exist.", volume.getVolumeId()); throw new AWSCommunicationServiceException("Can not sync volume. Volume " + volume.getVolumeId() + " does not exist."); } @Override public void deleteVolume(Volume volume) { DeleteVolumeRequest deleteVolumeRequest = new DeleteVolumeRequest(volume.getVolumeId()); ec2client.deleteVolume(deleteVolumeRequest); LOG.info(format("Volume %s deleted", volume.getVolumeId())); } @Override public synchronized void attachVolume(Instance instance, Volume volume) { String deviceName = getNextAvailableDeviceName(instance); AttachVolumeRequest attachVolumeRequest = new AttachVolumeRequest(volume.getVolumeId(), instance.getInstanceId(), deviceName); ec2client.attachVolume(attachVolumeRequest); waitForVolumeState(volume, VolumeState.InUse); LOG.info(format("\nVolume attached. check instance data\n %s", instance.toString())); } private String getNextAvailableDeviceName(Instance instance) { String deviceName = "/dev/sd"; List<String> reservedDeviceNames = new ArrayList<>(11); for (char c = 'f'; c <= 'p'; c++) { reservedDeviceNames.add(deviceName + c); } List<InstanceBlockDeviceMapping> devList = instance .getBlockDeviceMappings(); Set<String> devicesInUsage = devList.stream().map(d -> d.getDeviceName()).collect(Collectors.toSet()); String newDeviceName = reservedDeviceNames.stream().filter(r -> !devicesInUsage.contains(r) && !Files.exists(Paths.get(r))).findFirst().orElseThrow(() -> new EnhancedSnapshotsException("There are no available mount point, please detach some volume")); return newDeviceName; } @Override public void setResourceName(String resourceId, String value) { addTag(resourceId, "Name", value); } @Override public void addTag(String resourceId, String name, String value) { CreateTagsRequest r = new CreateTagsRequest().withResources(resourceId) .withTags(new Tag().withKey(name).withValue(value)); ec2client.createTags(r); } @Override public boolean snapshotExists(String snapshotId) { Snapshot snapshot = getSnapshot(snapshotId); if (snapshot != null) { return true; } return false; } public Snapshot getSnapshot(String snapshotId) { DescribeSnapshotsResult describeSnapshotsResult = ec2client.describeSnapshots(); List<Snapshot> snapshots = describeSnapshotsResult.getSnapshots(); for(Snapshot snapshot: snapshots){ if(snapshot.getSnapshotId().equals(snapshotId)){ return snapshot; } } return null; } @Override public void dropS3Bucket(String bucketName) { LOG.info("Removing bucket {}.", bucketName); ObjectListing objectListing = amazonS3.listObjects(bucketName); while (true) { for (Iterator<?> iterator = objectListing.getObjectSummaries().iterator(); iterator.hasNext(); ) { S3ObjectSummary objectSummary = (S3ObjectSummary) iterator.next(); amazonS3.deleteObject(bucketName, objectSummary.getKey()); } if (objectListing.isTruncated()) { objectListing = amazonS3.listNextBatchOfObjects(objectListing); } else { break; } } VersionListing list = amazonS3.listVersions(new ListVersionsRequest().withBucketName(bucketName)); for (Iterator<?> iterator = list.getVersionSummaries().iterator(); iterator.hasNext(); ) { S3VersionSummary s = (S3VersionSummary) iterator.next(); amazonS3.deleteVersion(bucketName, s.getKey(), s.getVersionId()); } amazonS3.deleteBucket(bucketName); } @Override public void restartAWSLogService() { Process p; try { p = Runtime.getRuntime().exec("service awslogs restart"); p.waitFor(); switch (p.exitValue()) { case 0: LOG.info("awslogs restarted successfully"); break; default: throw new AWSCommunicationServiceException("Failed to restart awslogs"); } } catch (Exception e) { LOG.error("Failed to restart awslogs"); throw new AWSCommunicationServiceException("Failed to restart awslogs"); } } @Override public String getDNSName(String instanceId) { return getInstance(instanceId).getPrivateDnsName(); } @Override public boolean volumeExists(String volumeId) { if (getVolume(volumeId) != null) { return true; } return false; } private void waitVolumeToDetach(Volume volume) { int waitTime = 0; LOG.debug("Volume {} is attached to {}", volume.getVolumeId(), volume.getAttachments().get(0).getInstanceId()); LOG.debug("Max wait time for volume detaching: {} seconds", configurationMediator.getMaxWaitTimeToDetachVolume()); while (!volume.getState().equals(AVAILABLE_STATE) && waitTime < configurationMediator.getMaxWaitTimeToDetachVolume()) { sleepTillNextSync(); volume = syncVolume(volume); waitTime += configurationMediator.getWaitTimeBeforeNewSyncWithAWS(); } if (syncVolume(volume).getState().equals(AVAILABLE_STATE)) { LOG.debug("Volume {} detached.", volume.getVolumeId()); return; } LOG.error("Failed to detach volume {}.", volume.getVolumeId()); throw new AWSCommunicationServiceException("Failed to detach volume " + volume.getVolumeId()); } public static class AWSCommunicationServiceException extends EnhancedSnapshotsException { public AWSCommunicationServiceException(String message) { super(message); } } // iops can not be less than 100 and more than 20 000 private int getIops(int iopsPerGb, int volumeSize) { int iops = volumeSize * iopsPerGb; if (iops < MIN_IOPS_VALUE) { return MIN_IOPS_VALUE; } if (iops > MAX_IOPS_VALUE) { return MAX_IOPS_VALUE; } return iops; } private void sleepTillNextSync(){ try { TimeUnit.SECONDS.sleep(configurationMediator.getWaitTimeBeforeNewSyncWithAWS()); } catch (InterruptedException e) { e.printStackTrace(); } } }