com.carrotgarden.maven.aws.ecc.CarrotElasticCompute Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of carrot-maven-aws-plugin Show documentation
Show all versions of carrot-maven-aws-plugin Show documentation
${project.organization.name} AWS Maven Plugin
/**
* Copyright (C) 2010-2012 Andrei Pozolotin
*
* All rights reserved. Licensed under the OSI BSD License.
*
* http://www.opensource.org/licenses/bsd-license.php
*/
package com.carrotgarden.maven.aws.ecc;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.slf4j.Logger;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.services.ec2.AmazonEC2;
import com.amazonaws.services.ec2.AmazonEC2Client;
import com.amazonaws.services.ec2.model.BlockDeviceMapping;
import com.amazonaws.services.ec2.model.CreateImageRequest;
import com.amazonaws.services.ec2.model.CreateImageResult;
import com.amazonaws.services.ec2.model.CreateTagsRequest;
import com.amazonaws.services.ec2.model.DeleteSnapshotRequest;
import com.amazonaws.services.ec2.model.DeleteTagsRequest;
import com.amazonaws.services.ec2.model.DeregisterImageRequest;
import com.amazonaws.services.ec2.model.DescribeImagesRequest;
import com.amazonaws.services.ec2.model.DescribeImagesResult;
import com.amazonaws.services.ec2.model.DescribeInstancesRequest;
import com.amazonaws.services.ec2.model.DescribeInstancesResult;
import com.amazonaws.services.ec2.model.EbsBlockDevice;
import com.amazonaws.services.ec2.model.Image;
import com.amazonaws.services.ec2.model.Instance;
import com.amazonaws.services.ec2.model.InstanceStateName;
import com.amazonaws.services.ec2.model.Reservation;
import com.amazonaws.services.ec2.model.StartInstancesRequest;
import com.amazonaws.services.ec2.model.StartInstancesResult;
import com.amazonaws.services.ec2.model.StateReason;
import com.amazonaws.services.ec2.model.StopInstancesRequest;
import com.amazonaws.services.ec2.model.StopInstancesResult;
import com.amazonaws.services.ec2.model.Tag;
/**
* elastic compute controller
*
* @author Andrei Pozolotin
*/
public class CarrotElasticCompute {
private final Logger logger;
private final AWSCredentials credentials;
private final AmazonEC2 amazonClient;
/** global operation timeout */
private final long timeout;
/** time to sleep between steps inside operation, seconds */
private final long attemptPause;
/** number of step attempts inside operation before failure */
private final long attemptCount;
private final String endpoint;
public CarrotElasticCompute(final Logger logger, final long timeout,
final AWSCredentials credentials, final String endpoint) {
this.logger = logger;
this.timeout = timeout;
this.credentials = credentials;
this.attemptPause = 10; // seconds
this.attemptCount = 3; // number of times
this.endpoint = endpoint;
this.amazonClient = newClient(); // keep last
}
private AmazonEC2 newClient() {
final AmazonEC2 amazonClient = new AmazonEC2Client(credentials);
amazonClient.setEndpoint(endpoint);
return amazonClient;
}
public void tagCreate(final String resourceId, final String key,
final String value) {
final CreateTagsRequest request = new CreateTagsRequest();
final Collection resourceList = new ArrayList(1);
resourceList.add(resourceId);
final Collection tagList = new ArrayList(1);
tagList.add(new Tag(key, value));
request.setResources(resourceList);
request.setTags(tagList);
logger.info("tag create request=" + request);
amazonClient.createTags(request);
}
public void tagDelete(final String resourceId, final String key,
final String value) {
final DeleteTagsRequest request = new DeleteTagsRequest();
final Collection resourceList = new ArrayList(1);
resourceList.add(resourceId);
final Collection tagList = new ArrayList(1);
tagList.add(new Tag(key, value));
request.setResources(resourceList);
request.setTags(tagList);
logger.info("tag delete request=" + request);
amazonClient.deleteTags(request);
}
public Instance findInstance(final String instanceId) {
final List instanceIdList = new ArrayList();
instanceIdList.add(instanceId);
final DescribeInstancesRequest request = new DescribeInstancesRequest();
request.setInstanceIds(instanceIdList);
final DescribeInstancesResult result = amazonClient
.describeInstances(request);
final List reservationList = result.getReservations();
switch (reservationList.size()) {
case 0:
return null;
case 1:
final Reservation reservation = reservationList.get(0);
final List instanceList = reservation.getInstances();
switch (instanceList.size()) {
case 0:
return null;
case 1:
return instanceList.get(0);
default:
throw new IllegalStateException("duplicate instance");
}
default:
throw new IllegalStateException("duplicate reservation");
}
}
private InstanceStateName stateFrom(final String instanceId) {
final Instance instance = findInstance(instanceId);
return InstanceStateName.fromValue(instance.getState().getName());
}
private InstanceStateName stateFrom(final Instance instance) {
return InstanceStateName.fromValue(instance.getState().getName());
}
private List wrapList(final String entry) {
final List list = new ArrayList();
list.add(entry);
return list;
}
/**
* http://shlomoswidler.com/2009/07/ec2-instance-life-cycle.html
*/
public void instanceStart(final String instanceId) throws Exception {
final Instance instance = findInstance(instanceId);
final InstanceStateName state = stateFrom(instance);
logger.info("start: current state=" + state);
switch (state) {
case Running:
return;
case Pending:
waitForIstanceState(instanceId, InstanceStateName.Running);
return;
case Stopped:
break;
case Stopping:
waitForIstanceState(instanceId, InstanceStateName.Stopped);
break;
case ShuttingDown:
case Terminated:
throw new IllegalStateException("start: dead instance");
default:
throw new IllegalStateException("start: unknown state");
}
final StartInstancesRequest request = new StartInstancesRequest();
request.setInstanceIds(wrapList(instanceId));
final StartInstancesResult result = amazonClient
.startInstances(request);
waitForIstanceState(instanceId, InstanceStateName.Running);
}
/**
* http://shlomoswidler.com/2009/07/ec2-instance-life-cycle.html
*/
public void instanceStop(final String instanceId) throws Exception {
final Instance instance = findInstance(instanceId);
final InstanceStateName state = stateFrom(instance);
logger.info("stop: current state=" + state);
switch (state) {
case Pending:
waitForIstanceState(instanceId, InstanceStateName.Running);
case Running:
break;
case Stopping:
waitForIstanceState(instanceId, InstanceStateName.Stopped);
case Stopped:
case Terminated:
case ShuttingDown:
return;
default:
throw new IllegalStateException("start: unknown state");
}
final StopInstancesRequest request = new StopInstancesRequest();
request.setInstanceIds(wrapList(instanceId));
final StopInstancesResult result = amazonClient.stopInstances(request);
waitForIstanceState(instanceId, InstanceStateName.Stopped);
}
/**
* stop instance and take image snapshot
*/
public Image imageCreate(final String instanceId, final String name,
final String description) throws Exception {
logger.info("ensure instance state : instanceId=" + instanceId);
final InstanceStateName state = stateFrom(instanceId);
final boolean wasRunning;
switch (state) {
case Pending:
waitForIstanceState(instanceId, InstanceStateName.Running);
case Running:
wasRunning = true;
break;
case Stopping:
waitForIstanceState(instanceId, InstanceStateName.Stopped);
case Stopped:
wasRunning = false;
break;
default:
throw new Exception("image create : invalid instance state="
+ state);
}
if (wasRunning) {
instanceStop(instanceId);
}
final CreateImageRequest request = new CreateImageRequest();
request.setInstanceId(instanceId);
request.setName(name);
request.setDescription(description);
final CreateImageResult result = amazonClient.createImage(request);
final String imageId = result.getImageId();
logger.info("ensure image state: imageId=" + imageId);
final Image image = waitForImageCreate(imageId);
if (wasRunning) {
instanceStart(instanceId);
}
return image;
}
/**
* @return valid image or null if missing
*/
public Image findImage(final String imageId) throws Exception {
/**
* work around for image entry not being immediately available right
* after create/register operation
*/
for (int index = 0; index < attemptCount; index++) {
try {
final DescribeImagesRequest request = new DescribeImagesRequest();
request.setImageIds(wrapList(imageId));
final DescribeImagesResult result = amazonClient
.describeImages(request);
final List imageList = result.getImages();
switch (imageList.size()) {
case 0:
logger.info("image find : missing imageId=" + imageId);
break;
case 1:
logger.info("image find : success imageId=" + imageId);
return imageList.get(0);
default:
logger.info("image find : duplicate imageId=" + imageId);
break;
}
} catch (final Exception e) {
logger.info("image find : exception imageId={} / {}", //
imageId, e.getMessage());
}
logger.info("image find : attempt=" + index);
sleep();
}
logger.error("image find : failure imageId=" + imageId);
return null;
}
/** unregister EBS snapshot; will fail if snapshot still in use */
public void snapshotDelete(final String snapshotId) throws Exception {
final DeleteSnapshotRequest request = new DeleteSnapshotRequest();
request.setSnapshotId(snapshotId);
amazonClient.deleteSnapshot(request);
logger.info("removed snapshotId = " + snapshotId);
}
/** delete AMI image and related EBS snapshots */
public void imageDelete(final String imageId) throws Exception {
final Image image = findImage(imageId);
if (image == null) {
logger.info("missing imageId = " + imageId);
return;
} else {
logger.info("present imageId = " + imageId);
}
imageUnregister(imageId);
for (final BlockDeviceMapping blockDevice : image
.getBlockDeviceMappings()) {
final EbsBlockDevice elasticDevice = blockDevice.getEbs();
if (elasticDevice == null) {
continue;
}
final String snapshotId = elasticDevice.getSnapshotId();
if (snapshotId == null) {
continue;
}
snapshotDelete(snapshotId);
}
logger.info("removed imageId = " + imageId);
}
public void imageUnregister(final String imageId) throws Exception {
final DeregisterImageRequest request = new DeregisterImageRequest();
request.setImageId(imageId);
amazonClient.deregisterImage(request);
}
public static enum ImageState {
AVAILABLE("available"), //
DEREGISTERED("deregistered"), //
PENDING("pending"), //
UNKNOWN("unknown"), //
;
public final String value;
ImageState(final String value) {
this.value = value;
}
public static ImageState fromValue(final String value) {
for (final ImageState known : values()) {
if (known.value.equals(value)) {
return known;
}
}
return UNKNOWN;
}
}
private Image newImageWithStatus(final String state, final String code,
final String message) {
final StateReason reason = new StateReason();
reason.setCode(code);
reason.setMessage(message);
final Image image = new Image();
image.setState(state);
image.setStateReason(reason);
return image;
}
private void waitForIstanceState(final String instanceId,
final InstanceStateName stateName) throws Exception {
final long timeStart = System.currentTimeMillis();
while (true) {
final Instance instance = findInstance(instanceId);
if (isTimeoutPending(timeStart)) {
logger.error("instance state : timeout");
throw new Exception("timeout");
}
if (instance == null) {
logger.error("instance state : missing");
throw new Exception("missing instance");
}
if (stateName == stateFrom(instance)) {
logger.info("instance state : done");
break;
} else {
final long timeThis = System.currentTimeMillis();
final long timeDiff = timeThis - timeStart;
logger.info("instance state; time=" + timeDiff / 1000);
sleep();
continue;
}
}
}
private Image waitForImageCreate(final String imageId) throws Exception {
if (findImage(imageId) == null) {
throw new IllegalStateException("image create: missing imageId="
+ imageId);
}
final long timeStart = System.currentTimeMillis();
final List imageIdList = new ArrayList();
imageIdList.add(imageId);
final DescribeImagesRequest request = new DescribeImagesRequest();
request.setImageIds(imageIdList);
while (true) {
final DescribeImagesResult result = amazonClient
.describeImages(request);
final List imageList = result.getImages();
final Image image;
if (isTimeoutPending(timeStart)) {
image = newImageWithStatus(ImageState.UNKNOWN.value, "timeout",
"image create: timeout while waiting");
} else if (imageList == null || imageList.isEmpty()) {
image = newImageWithStatus(ImageState.UNKNOWN.value, "missing",
"image create: missing in descriptions");
} else {
image = imageList.get(0);
}
final String value = image.getState();
final ImageState state = ImageState.fromValue(value);
switch (state) {
case AVAILABLE:
logger.info("image create: success");
return image;
case PENDING:
final long timeThis = System.currentTimeMillis();
final long timeDiff = timeThis - timeStart;
logger.info("image create: in progress; time=" + timeDiff
/ 1000);
sleep();
break;
default:
logger.error("image create: failure");
return image;
}
}
}
private void sleep() throws Exception {
try {
Thread.sleep(attemptPause * 1000);
} catch (final InterruptedException ie) {
throw new IllegalStateException("operation interrupted; "
+ "resources are left in inconsistent state; "
+ "requires manual intervention");
}
}
private boolean isTimeoutPending(final long startTime) {
return (System.currentTimeMillis() - startTime) > (timeout * 1000);
}
}