/******************************************************************************* * Copyright 2012 Urbancode, 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://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License 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.urbancode.terraform.tasks.aws; import java.rmi.RemoteException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.PriorityQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.apache.log4j.Logger; import com.amazonaws.services.ec2.AmazonEC2; import com.amazonaws.services.ec2.model.NetworkInterface; import com.urbancode.terraform.tasks.aws.helpers.AWSHelper; import com.urbancode.terraform.tasks.aws.util.InstancePriorityComparator; import com.urbancode.terraform.tasks.common.EnvironmentTask; import com.urbancode.terraform.tasks.common.exceptions.EnvironmentCreationException; import com.urbancode.terraform.tasks.common.exceptions.EnvironmentDestructionException; import com.urbancode.terraform.tasks.common.exceptions.EnvironmentRestorationException; import com.urbancode.x2o.tasks.MultiThreadTask; public class EnvironmentTaskAWS extends EnvironmentTask { //********************************************************************************************** // CLASS //********************************************************************************************** final static private Logger log = Logger.getLogger(EnvironmentTaskAWS.class); static final private int MAX_THREADS = 30; //********************************************************************************************** // INSTANCE //********************************************************************************************** private AmazonEC2 ec2Client; private AWSHelper helper; private ContextAWS context; private VpcTask vpc; private List<InstanceTask> instances = new ArrayList<InstanceTask>(); private List<LoadBalancerTask> loadBalancers = new ArrayList<LoadBalancerTask>(); private List<Ec2SecurityGroupTask> ec2SecGroups = new ArrayList<Ec2SecurityGroupTask>(); // for timeouts private long pollInterval = 3000L; private long timeoutInterval = 15L * 60L * 1000L; private long start; //---------------------------------------------------------------------------------------------- public EnvironmentTaskAWS(ContextAWS context) { super(context); this.context = context; helper = new AWSHelper(); } //---------------------------------------------------------------------------------------------- public InstanceTask findInstanceByName(String name) { InstanceTask result = null; if (instances != null && !instances.isEmpty()) { for (InstanceTask instance : instances) { if (instance.getName().equals(name)) { result = instance; break; } } } return result; } //---------------------------------------------------------------------------------------------- public InstanceTask findInstanceById(String id) { InstanceTask result = null; if (instances != null && !instances.isEmpty()) { for (InstanceTask instance : instances) { if (instance.getId() != null && instance.getId().equals(id)) { result = instance; break; } } } return result; } //---------------------------------------------------------------------------------------------- public VpcTask getVpc() { return vpc; } //---------------------------------------------------------------------------------------------- public List<LoadBalancerTask> getLoadBalancers() { return Collections.unmodifiableList(loadBalancers); } //---------------------------------------------------------------------------------------------- public List<InstanceTask> getInstances() { return instances; } //---------------------------------------------------------------------------------------------- public List<Ec2SecurityGroupTask> getSecurityGroups() { return Collections.unmodifiableList(ec2SecGroups); } public Ec2SecurityGroupTask createEc2SecurityGroup() { Ec2SecurityGroupTask group = new Ec2SecurityGroupTask(context); ec2SecGroups.add(group); return group; } //---------------------------------------------------------------------------------------------- public VpcTask createVpc() { this.vpc = new VpcTask(context); return this.vpc; } //---------------------------------------------------------------------------------------------- public InstanceTask createInstance() { InstanceTask inst = new InstanceTask(context); instances.add(inst); return inst; } //---------------------------------------------------------------------------------------------- public LoadBalancerTask createLoadBalancer() { LoadBalancerTask balancer = new LoadBalancerTask(context); loadBalancers.add(balancer); return balancer; } //---------------------------------------------------------------------------------------------- /** * This is used for terminating a single instance by its Amazon Instance Id. This is ran in the * current thread. Use handleInstances for a multi-threaded solution. * * @param instanceId * @throws Exception */ public void terminateInstance(String instanceId) throws Exception { findInstanceById(instanceId).destroy(); } //---------------------------------------------------------------------------------------------- /** * This is used for launching or terminating instances in separate threads for parallelization. * * @param instances * @param doCreate * @throws Exception */ private void handleInstances(List<InstanceTask> instances, boolean doCreate) throws Exception { if (instances != null && !instances.isEmpty()) { // not sure if this is working as intended int threadPoolSize = instances.size(); if (threadPoolSize > MAX_THREADS) { threadPoolSize = MAX_THREADS; } // create instances - launch thread for each one List<MultiThreadTask> threadList = new ArrayList<MultiThreadTask>(); ExecutorService service = Executors.newFixedThreadPool(threadPoolSize); start = System.currentTimeMillis(); for (InstanceTask instance : instances) { if (doCreate) { if (vpc != null) { String subnetIn = getVpc().findSubnetForName(instance.getSubnetName()).getId(); instance.setSubnetId(subnetIn); } } MultiThreadTask mThread = new MultiThreadTask(instance, doCreate, context); threadList.add(mThread); service.execute(mThread); } service.shutdown(); // accept no more threads while(!service.isTerminated()) { if (System.currentTimeMillis() - start > timeoutInterval) { throw new RemoteException( "Timeout waiting for creation Instance threads to finish"); } // wait until all threads are done Thread.sleep(pollInterval); } // check for Exceptions caught in threads for (MultiThreadTask task : threadList) { if (task.getExceptions().size() != 0) { for (Exception e : task.getExceptions()) { log.error("Exception caught!", e); throw e; } } } } else { log.error("List of instances to launch was null!"); } } //---------------------------------------------------------------------------------------------- private void launchLoadBalancers(List<LoadBalancerTask> loadBalancers) throws EnvironmentCreationException { // create the loadBalancer(s) if (loadBalancers != null && !loadBalancers.isEmpty()) { for (LoadBalancerTask loadBalancer : loadBalancers) { loadBalancer.create(); } } } //---------------------------------------------------------------------------------------------- private void launchSecurityGroups(List<Ec2SecurityGroupTask> groups) throws EnvironmentCreationException { if (groups != null) { for (SecurityGroupTask group : groups) { group.create(); } } } //---------------------------------------------------------------------------------------------- private void copyInstances() { if (instances != null) { List<InstanceTask> newInstances = new ArrayList<InstanceTask>(); log.debug("Found " + instances.size() + " instances."); for (InstanceTask instance : instances) { String instanceName = ""; InstanceTask newInstance = null; // make a list of all the new instances for (int i=1; i<instance.getCount(); i++) { log.debug("Cloning instance: " + instance.getName()); newInstance = instance.clone(); instanceName = String.format(instance.getName() + "%02d", i); newInstance.setName(instanceName); newInstances.add(newInstance); } instanceName = String.format(instance.getName() + "%02d", 0); instance.setName(instanceName); instance.setCount(1); if (instance != null) { log.debug("Finished setup for instance: " + instance.getName()); } } // add new instances to old instances instances.addAll(newInstances); } } //---------------------------------------------------------------------------------------------- private void launchInstances() throws Exception { // setup launch groups if (instances != null) { log.debug("Preparing to launch instances"); Comparator<InstanceTask> comparer = new InstancePriorityComparator(); PriorityQueue<InstanceTask> queue = new PriorityQueue<InstanceTask>(3, comparer); for (InstanceTask instance : getInstances()) { log.debug("Adding instance to queue: " + instance.getName()); queue.add(instance); } log.debug("Priority Queue length: " + queue.size()); log.debug("Priority Queue first item: " + queue.peek()); InstanceTask currentInst = queue.poll(); // get instance at first of queue, continue if not null... while (currentInst != null) { log.debug("Queue iter: " + currentInst); // create new LaunchGroup List<InstanceTask> launchGroup = new ArrayList<InstanceTask>(); // add instance to launchGroup launchGroup.add(currentInst); // get current priority of instance int currentPri = currentInst.getPriority(); int nextPri = -1; // get priority of next instance, see if it matches current priority // add all instances with same priority InstanceTask nextInst = queue.poll(); log.debug("Next instance: " + nextInst); while (nextInst != null) { if (currentPri == nextInst.getPriority()) { log.debug("Same priority " + currentPri); launchGroup.add(nextInst); } else { nextPri = nextInst.getPriority(); log.debug("Differnet priority: " + nextPri); break; } nextInst = queue.poll(); } currentInst = nextInst; // otherwise we have a new priorty if (currentPri != nextPri) { log.debug("Same priority; launching group"); // if the group has members if (launchGroup != null && !launchGroup.isEmpty()) { // launch the group String msg = "Launching Instances: "; msg += "\n\tGroup: " + launchGroup.get(0).getPriority(); if (launchGroup != null) { for (InstanceTask toLaunch : launchGroup) { if (toLaunch != null) { msg += "\n\t\t" + toLaunch.getName(); } else { log.error("Instance toLaunch is null; this shouldn't happen. Priority: " + currentPri); } } log.info(msg); handleInstances(launchGroup, true); } } else { log.info("Launch group is empty! " + currentPri); } } else { log.debug("Different priority!"); } } } else { log.warn("No instances to launch"); } } //---------------------------------------------------------------------------------------------- private void detachENIs() { // detach any ENIs that we can if (getVpc() != null && getVpc().getId() != null) { List<NetworkInterface> interfaces = helper.getNetworkInterfaces(null, getVpc().getId(), ec2Client); List<String> detachIds = new ArrayList<String>(); if (interfaces != null) { // refactor this into helper? for (NetworkInterface iface : interfaces) { if (iface.getAttachment() != null && iface.getAttachment().getDeviceIndex() != 0) { detachIds.add(iface.getAttachment().getAttachmentId()); } } helper.detachNetworkInterfaces(detachIds, ec2Client); } } } //---------------------------------------------------------------------------------------------- private void destroyLoadBalancers() throws EnvironmentDestructionException { // destroy load balancers if (getLoadBalancers() != null && !getLoadBalancers().isEmpty()) { for (LoadBalancerTask loadBalancer : getLoadBalancers()) { loadBalancer.destroy(); } } } //---------------------------------------------------------------------------------------------- private void destroySecurityGroups() throws EnvironmentDestructionException { if (ec2SecGroups != null) { for (SecurityGroupTask group : ec2SecGroups) { group.destroy(); } } } //---------------------------------------------------------------------------------------------- public SecurityGroupTask findSecurityGroupByName(String groupName) { Ec2SecurityGroupTask result = null; if (ec2SecGroups != null) { for (Ec2SecurityGroupTask group : ec2SecGroups) { if (groupName != null && groupName.equals(group.getName())) { result = group; break; } } } return result; } //---------------------------------------------------------------------------------------------- @Override public void create() throws EnvironmentCreationException { log.debug("Creating EnvironmentAWS"); if (ec2Client == null) { ec2Client = context.fetchEC2Client(); } log.info("Creating Environment"); setStartTime(System.currentTimeMillis()); try { log.debug("Starting Vpc Creation"); if (getVpc() != null) { getVpc().create(); log.debug("Finished Vpc Creation"); } else { log.info("No Vpc to create"); } // Launch EC2 security groups - they are different than VPC security groups log.debug("Staring EC2 Security Groups"); launchSecurityGroups(ec2SecGroups); log.debug("Finished Security Groups"); // launch load balancers before we launch instances // we do this so we can just hold a ref to lb on the // instance and we need to register the it on the lb. log.debug("Starting Loadbalancers"); launchLoadBalancers(loadBalancers); log.debug("Finished Loadbalancers"); log.debug("Preparing instances"); copyInstances(); log.debug("Launching instances"); launchInstances(); log.debug("Finished instances"); log.info("Environment Created"); } catch (Exception e) { throw new EnvironmentCreationException("Failed to create environment!", e); } finally { ec2Client = null; } } //---------------------------------------------------------------------------------------------- @Override public void destroy() throws EnvironmentDestructionException { if (ec2Client == null) { ec2Client = context.fetchEC2Client(); } int threadPoolSize = instances.size(); if (threadPoolSize > MAX_THREADS) { threadPoolSize = MAX_THREADS; } try { // detach any ENIs that may cause issues detachENIs(); // destroy instances handleInstances(instances, false); // destroy all load balancers destroyLoadBalancers(); // destroy all EC2 sec groups we made - // vpc sec groups are destroyed when destroying the vpc destroySecurityGroups(); // destroy the vpc if (getVpc() != null) { getVpc().destroy(); } } catch (Exception e) { throw new EnvironmentDestructionException("Failed to destroy environment!", e); } finally { ec2Client = null; } } //---------------------------------------------------------------------------------------------- @Override public void restore() throws EnvironmentRestorationException { // not necessary to run AWS update commands } }