/* * Copyright 2008-2012 Amazon Technologies, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://aws.amazon.com/apache2.0 * * This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES * OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and * limitations under the License. */ package com.amazonaws.eclipse.ec2; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Logger; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import com.amazonaws.AmazonClientException; import com.amazonaws.eclipse.core.AWSClientFactory; import com.amazonaws.eclipse.core.AwsToolkitCore; import com.amazonaws.services.ec2.AmazonEC2; import com.amazonaws.services.ec2.model.DescribeInstancesRequest; import com.amazonaws.services.ec2.model.IamInstanceProfileSpecification; import com.amazonaws.services.ec2.model.Instance; import com.amazonaws.services.ec2.model.Placement; import com.amazonaws.services.ec2.model.Reservation; import com.amazonaws.services.ec2.model.RunInstancesRequest; /** * Responsible for launching EC2 instances and watching them to see if they * come up correctly. The big value of this class is that it handles monitoring * the launched instances to see if they started correctly or if they failed. */ public class Ec2InstanceLauncher { /** Factory providing EC2 clients */ private final AWSClientFactory clientFactory = AwsToolkitCore.getClientFactory(); /** The name of the key pair to launch instances with */ private String keyPairName; /** The name of the security group to launch instances with */ private String securityGroupName; /** The number of instances to launch */ private int numberOfInstances; /** The user data to pass to the launched instances */ private String userData; /** The name of the zone to bring up instances in */ private String availabilityZoneName; /** The id of the AMI to launch instances with */ private String imageId; /** The type of instances to launch */ private String instanceType; /** The endpoint of the EC2 region in which instances should be launched */ private String regionEndpoint; /** The arn of the instance profile to launch with */ private String instanceProfileArn; /** * Optional progress monitor so that this launcher can poll to see if the * user has canceled the launch request. */ private IProgressMonitor progressMonitor; /** Shared logger */ private static final Logger logger = Logger.getLogger(Ec2InstanceLauncher.class.getName()); /** * Constructs a new EC2 instance launcher, ready to launch the specified * image with the specified key pair. Additional, optional launch parameters * can be configured by calling the setters. * * @param imageId * The id of the image to launch. * @param keyPairName * The name of the key pair with which to launch instances. */ public Ec2InstanceLauncher(String imageId, String keyPairName) { this.imageId = imageId; this.keyPairName = keyPairName; this.numberOfInstances = 1; this.instanceType = InstanceTypes.getDefaultInstanceType().id; } /* * Setters */ /** * Sets the EC2 region endpoint that should be used when launching these * instances. * * @param regionEndpoint * The EC2 region endpoint that should be used when launching * these instances. */ public void setEc2RegionEndpoint(String regionEndpoint) { this.regionEndpoint = regionEndpoint; } /** * Sets the security group with which this launcher will launch instances. * * @param securityGroupName * The name of the security group with which to launch instances. */ public void setSecurityGroup(String securityGroupName) { this.securityGroupName = securityGroupName; } /** * Sets the number of instances this launcher will launch. * * @param numberOfInstances * The number of instances to be launched. */ public void setNumberOfInstances(int numberOfInstances) { this.numberOfInstances = numberOfInstances; } /** * Sets the user data with which this launcher will launch instances. * * @param userData * The user data to launch instances with. */ public void setUserData(String userData) { this.userData = userData; } /** * Sets the isntance profile arn to launch with. */ public void setInstanceProfileArn(String instanceProfileArn) { this.instanceProfileArn = instanceProfileArn; } /** * Sets the availability zone in which to launch instances. * * @param availabilityZoneName * The availability zone in which to launch instances. */ public void setAvailabilityZone(String availabilityZoneName) { this.availabilityZoneName = availabilityZoneName; } /** * The String ID representing the type of instances to launch. * * @param instanceType * The instance type ID representing the type of instances to * launch. */ public void setInstanceType(String instanceType) { this.instanceType = instanceType; } /** * Sets the optional progress monitor to use to check to see if the user has * canceled this launch. * * @param progressMonitor * The progress monitor to use when checking to see if the user * has canceled this launch. */ public void setProgressMonitor(IProgressMonitor progressMonitor) { this.progressMonitor = progressMonitor; } /* * Launch methods */ /** * Launches EC2 instances with the parameters configured in this launcher * and waits for them to all come online before returning. * * @throws AmazonClientException * If any problems prevented the instances from starting up. * @throws OperationCanceledException * If the user canceled the request for which these instances * were being launched. */ public List<Instance> launchAndWait() throws AmazonClientException, OperationCanceledException { return startInstances(true); } /** * Launches EC2 instances with the parameters configured in this launcher * and returns immediately, without waiting for the new instances to come * up. * * @throws AmazonClientException * If any problems prevented the instances from starting up. */ public void launch() throws AmazonClientException { startInstances(false); } /* * Private Interface */ /** * Returns an AWS EC2 client ready to be used. If the caller specified an * EC2 region endpoint, this client will be fully configured to talk to that * region, otherwise it will be configured to work with the default region. * * @return An AWS EC2 client ready to be used. */ private AmazonEC2 getEc2Client() { if (regionEndpoint != null) { return clientFactory.getEC2ClientByEndpoint(regionEndpoint); } return Ec2Plugin.getDefault().getDefaultEC2Client(); } /** * Starts up instances and optionally waits for them to come online. * * @param waitForInstances * True if the caller wants this method to block until the * instances are online, in which case a List of instances will * be returned, otherwise this method will request that the * instances be launched and immediately return. * * @throws AmazonClientException * If any problems were encountered communicating with Amazon * EC2. * @throws OperationCanceledException * If the user canceled the request for which these instances * were being launched. */ private List<Instance> startInstances(boolean waitForInstances) throws AmazonClientException, OperationCanceledException { logger.info("Requested to start " + numberOfInstances + " new instances."); AmazonEC2 ec2 = getEc2Client(); List<String> securityGroupList = null; if (this.securityGroupName != null) { securityGroupList = Arrays.asList(new String[] {securityGroupName}); } RunInstancesRequest request = new RunInstancesRequest(); request.setImageId(imageId); request.setSecurityGroups(securityGroupList); request.setMinCount(numberOfInstances); request.setMaxCount(numberOfInstances); request.setKeyName(keyPairName); request.setInstanceType(instanceType); if ( instanceProfileArn != null ) { request.setIamInstanceProfile(new IamInstanceProfileSpecification().withArn(instanceProfileArn)); } Placement placement = new Placement(); placement.setAvailabilityZone(availabilityZoneName); request.setPlacement(placement); if (userData != null) { request.setUserData(userData); } Reservation initialReservation = ec2.runInstances(request).getReservation(); // If the caller doesn't care about waiting for the instances to come up, go // ahead and return. if (!waitForInstances) { return null; } List<Instance> instances = initialReservation.getInstances(); final Map<String, Instance> pendingInstancesById = new HashMap<String, Instance>(); for (Instance instance : instances) { pendingInstancesById.put(instance.getInstanceId(), instance); } final List<String> instanceIds = new ArrayList<String>(); for (Instance instance : instances) { instanceIds.add(instance.getInstanceId()); } Map<String, Instance> startedInstancesById = new HashMap<String, Instance>(); do { pauseForInstancesToStartUp(); DescribeInstancesRequest describeRequest = new DescribeInstancesRequest(); describeRequest.setInstanceIds(instanceIds); List<Reservation> reservations = ec2.describeInstances(describeRequest).getReservations(); for (Reservation reservation : reservations) { for (Instance instance : reservation.getInstances()) { // TODO: state codes would be much better... if (instance.getState().getName().equalsIgnoreCase("pending")) { continue; } String instanceId = instance.getInstanceId(); pendingInstancesById.remove(instanceId); startedInstancesById.put(instanceId, instance); } } // TODO: error detection for startup failures } while (!pendingInstancesById.isEmpty()); return new ArrayList<Instance>(startedInstancesById.values()); } /** * Pauses for a few seconds so that instances have a chance to start up. * This method also examines the progress monitor (if set) to see if the * user has canceled the request for which these instances are being * launched. If the request has been canceled, this method will throw an * OperationCanceledException. */ protected void pauseForInstancesToStartUp() throws OperationCanceledException { for (int i = 0; i < 5; i++) { if (progressMonitor != null) { if (progressMonitor.isCanceled()) { throw new OperationCanceledException("Operation canceled while " + "waiting for requested EC2 instances to come online."); } } try {Thread.sleep(1000);} catch (InterruptedException ie) {} } } }