pl.edu.agh.samm.tadapter.eucalyptus.EucalyptusTransportAdapter.java Source code

Java tutorial

Introduction

Here is the source code for pl.edu.agh.samm.tadapter.eucalyptus.EucalyptusTransportAdapter.java

Source

/**
 * This file is part of SAMM.
 *
 * SAMM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * SAMM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with SAMM.  If not, see <http://www.gnu.org/licenses/>.
 */

package pl.edu.agh.samm.tadapter.eucalyptus;

import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import pl.edu.agh.samm.api.action.Action;
import pl.edu.agh.samm.api.core.Resource;
import pl.edu.agh.samm.api.core.ResourceAlreadyRegisteredException;
import pl.edu.agh.samm.api.core.ResourceNotRegisteredException;
import pl.edu.agh.samm.api.tadapter.AbstractTransportAdapter;
import pl.edu.agh.samm.api.tadapter.ActionNotSupportedException;

import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.ec2.AmazonEC2Client;
import com.amazonaws.services.ec2.model.DescribeImagesRequest;
import com.amazonaws.services.ec2.model.DescribeInstancesResult;
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.RunInstancesRequest;
import com.amazonaws.services.ec2.model.RunInstancesResult;
import com.amazonaws.services.ec2.model.TerminateInstancesRequest;
import com.amazonaws.services.ec2.model.TerminateInstancesResult;

/**
 * @author Pawel Koperek <pkoperek@gmail.com>
 * @author Mateusz Kupisz <mkupisz@gmail.com>
 */
public class EucalyptusTransportAdapter extends AbstractTransportAdapter {

    private static final Logger logger = LoggerFactory.getLogger(EucalyptusTransportAdapter.class);

    // transport adapter resource parameters
    public static final String EUCALYPTUS_KEY_NAME = "EUCALYPTUSKEYNAME";
    public static final String EUCALYPTUS_TRANSPORT_PROPERTY_KEY = "EUCALYPTUSENDPOINT";
    public static final String EUCALYPTUS_ACCESS_KEY = "EUCALYPTUSACCESSKEY";
    public static final String EUCALYPTUS_SECRET_KEY = "EUCALYPTUSSECRETKEY";
    public static final String EUCALYPTUS_IMAGE_TO_OBSERVE_ID = "EUCALYPTUS_IMAGE_TO_OBSERVE_ID";

    // start action parameters
    public static final String EUCALYPTUS_CLUSTER_PARAMETER = "EUCALYPTUS_CLUSTER_RESOURCE";
    public static final String EUCALYPTUS_IMAGE_ID = "EUCALYPTUS_IMAGE_ID";
    public static final String EUCALYPTUS_INSTANCE_TYPE = "EUCALYPTUS_INSTANCE_TYPE";
    public static final String EUCALYPTUS_STARTVM_USERDATA = "EUCALYPTUS_STARTVM_USERDATA";
    public static final String EUCALYPTUS_MAX_VMS = "EUCALYPTUS_MAX_VMS";

    // stop action parameters
    public static final String EUCALYPTUS_INSTANCE_ID = "EUCALYPTUS_INSTANCE_ID";
    public static final String EUCALYPTUS_MIN_VMS = "EUCALYPTUS_MIN_VMS";

    // other contants
    private static final String SLAVE_NODE_TYPE = "http://www.icsr.agh.edu.pl/samm_1.owl#SlaveNode";
    private static final String VIRTUAL_NODE_TYPE = "http://www.icsr.agh.edu.pl/samm_1.owl#VirtualNode";
    private static final String VIRTUAL_NODE_NAME_PREFIX = "Instance_";
    private static final String IMAGE_TYPE_MACHINE = "machine";
    private static final String IMAGE_STATE_AVAILABLE = "available";

    private static final String ACTION_START_MVCBASIC_VM = "http://www.icsr.agh.edu.pl/samm_1.owl#StartMVCBasicVMAction";
    private static final String ACTION_START_VM = "http://www.icsr.agh.edu.pl/samm_1.owl#StartVMAction";
    private static final String ACTION_STOP_VM = "http://www.icsr.agh.edu.pl/samm_1.owl#StopVMAction";

    private static final String IMAGE_INSTANCES_CAPABILITY = "http://www.icsr.agh.edu.pl/samm_1.owl#ImageInstancesCapability";

    private static final String EUCALYPTUS_CLUSTER_TYPE = "http://www.icsr.agh.edu.pl/samm_1.owl#EucalyptusCluster";

    private static Set<String> supportedActions;

    static {
        supportedActions = new HashSet<String>();
        supportedActions.add(ACTION_START_MVCBASIC_VM);
        supportedActions.add(ACTION_START_VM);
        supportedActions.add(ACTION_STOP_VM);
    }

    private String mvcBasicImageId;

    private Map<Resource, AmazonEC2Client> ec2Clients = new HashMap<Resource, AmazonEC2Client>();

    /**
     * Find children of cluster controller in fact... Reacts only when in
     * <code>types</code> list the type of VirtualNode appears.
     *
     * @see pl.edu.agh.samm.api.tadapter.ITransportAdapter#discoverChildren(pl.edu.agh.samm.api.core.Resource,
     *      java.util.List)
     */
    @Override
    public void discoverChildren(Resource resource, List<String> types) throws Exception {

        if (types.contains(VIRTUAL_NODE_TYPE)) {
            AmazonEC2Client ec2Client = ec2Clients.get(resource);
            if (ec2Client == null) {
                throw new ResourceNotRegisteredException(resource.getUri());
            }

            DescribeInstancesResult result = ec2Client.describeInstances();
            List<Reservation> reservations = result.getReservations();
            Set<Instance> instances = new HashSet<Instance>();

            for (Reservation reservation : reservations) {
                List<Instance> instancesFromReservation = reservation.getInstances();
                for (Instance instance : instancesFromReservation) {
                    if (instance.getState().getName().equals(InstanceStateName.Running.toString())) {
                        instances.add(instance);
                    }
                }
            }

            Map<String, String> childrenTypes = new HashMap<String, String>();
            Map<String, Map<String, Object>> childrenProperties = new HashMap<String, Map<String, Object>>();

            for (Instance instance : instances) {
                String child = resource.getUri() + "/" + VIRTUAL_NODE_NAME_PREFIX + instance.getInstanceId();
                Map<String, Object> newProperties = new HashMap<String, Object>(resource.getProperties());
                childrenProperties.put(child, newProperties);
                childrenTypes.put(child, VIRTUAL_NODE_TYPE);
            }
            fireNewResourcesEvent(resource.getUri(), childrenTypes, childrenProperties);
        }
    }

    @Override
    public void executeAction(Action actionToExecute) throws Exception {
        if (!supportedActions.contains(actionToExecute.getActionURI())) {
            throw new ActionNotSupportedException();
        }

        logger.info("Executing: " + actionToExecute.getActionURI());
        if (ACTION_START_MVCBASIC_VM.equalsIgnoreCase(actionToExecute.getActionURI())) {
            Resource resource = getClusterResource(actionToExecute);
            String instanceType = getInstanceType(actionToExecute);
            try {
                startMVCBasicTomcatInstanceAction(resource, mvcBasicImageId, instanceType);
            } catch (Exception e) {
                logger.error(e.toString(), e);
                throw new RuntimeException(e);
            }
        } else if (ACTION_START_VM.equalsIgnoreCase(actionToExecute.getActionURI())) {
            Resource clusterResource = getClusterResource(actionToExecute);
            String imageId = getImageId(actionToExecute);
            String instanceType = getInstanceType(actionToExecute);
            String userData = getUserData(actionToExecute);

            Integer maxVMs = getMaxVMs(actionToExecute);
            int instancesNumber = getInstancesNumber(clusterResource, imageId);

            Instance instance = null;
            if (maxVMs != null) {
                logger.info("Currently running instances: " + instancesNumber + " max: " + maxVMs);
                if (instancesNumber < maxVMs) {
                    logger.info("Starting new instance!");
                    instance = startOneInstanceAction(clusterResource, imageId, instanceType, userData);
                } else {
                    logger.info("Too much instances running! Can't start a new one!");
                }
            } else {
                logger.info("Maximum number of instances not defined!");
                instance = startOneInstanceAction(clusterResource, imageId, instanceType, userData);
            }

            if (instance != null) {
                logger.info("Registering instance: " + instance);
                registerNewInstance(clusterResource, instance, SLAVE_NODE_TYPE);
            } else {
                logger.info("No instance to register!");
            }

        } else if (ACTION_STOP_VM.equalsIgnoreCase(actionToExecute.getActionURI())) {
            Resource clusterResource = getClusterResource(actionToExecute);
            // String instanceId = getInstanceId(actionToExecute);
            String imageId = getImageId(actionToExecute);
            String instanceId = getRandomInstance(clusterResource, imageId);
            if (instanceId == null) {
                logger.warn(
                        "Can't stop any instance! There are no instances running with imageId = '" + imageId + "'");
            } else {

                Integer minVMs = getMinVMs(actionToExecute);
                int instancesNumber = getInstancesNumber(clusterResource, imageId);

                if (minVMs != null) {
                    if (instancesNumber > minVMs) {
                        logger.info("Currently running instances: " + instancesNumber + " min: " + minVMs
                                + " Stopping!");
                        stopOneInstanceAction(clusterResource, instanceId);
                    } else {
                        logger.info("Can't stop more instances! (currently runnning: " + instancesNumber + " min: "
                                + minVMs + ")");
                    }
                } else {
                    logger.info("No minimum number of instances defined! Stopping...");
                    stopOneInstanceAction(clusterResource, instanceId);
                }
            }
        }
        logger.info("Executed: " + actionToExecute.getActionURI());
    }

    private Integer getMinVMs(Action actionToExecute) {
        String minVMs = actionToExecute.getParameterValues().get(EUCALYPTUS_MIN_VMS);
        Integer retVal = null;
        if (minVMs != null) {
            retVal = Integer.valueOf(minVMs);
        }
        return retVal;
    }

    private Integer getMaxVMs(Action actionToExecute) {
        String maxVMs = actionToExecute.getParameterValues().get(EUCALYPTUS_MAX_VMS);
        Integer retVal = null;
        if (maxVMs != null) {
            retVal = Integer.valueOf(maxVMs);
        }
        return retVal;
    }

    private String getUserData(Action actionToExecute) {
        return actionToExecute.getParameterValues().get(EUCALYPTUS_STARTVM_USERDATA);
    }

    private int getInstancesNumber(Resource clusterResource, String imageId) {
        AmazonEC2Client client = this.ec2Clients.get(clusterResource);

        DescribeInstancesResult result = client.describeInstances();
        List<Instance> instances = getRunningInstancesByImageId(result, imageId);

        return instances.size();
    }

    private String getRandomInstance(Resource clusterResource, String imageId) {
        AmazonEC2Client client = this.ec2Clients.get(clusterResource);

        DescribeInstancesResult result = client.describeInstances();
        List<Instance> instances = getRunningInstancesByImageId(result, imageId);

        String instanceId = null;

        if (instances.size() > 0) {
            int idx = (int) Math.round((Math.random() * (instances.size() - 1)));
            Instance instance = instances.get(idx);
            instanceId = instance.getInstanceId();
        }

        return instanceId;
    }

    private List<Instance> getRunningInstancesByImageId(DescribeInstancesResult result, String imageId) {
        List<Instance> instances = new LinkedList<Instance>();
        for (Reservation reservation : result.getReservations()) {
            for (Instance instance : reservation.getInstances()) {
                if (imageId.equals(instance.getImageId()) && EC2Util.isInstanceRunning(instance)) {
                    instances.add(instance);
                }
            }
        }
        return instances;
    }

    private String getInstanceId(Action actionToExecute) {
        return actionToExecute.getParameterValues().get(EUCALYPTUS_INSTANCE_ID);
    }

    private void stopOneInstanceAction(Resource clusterResource, String instanceId) {
        AmazonEC2Client client = ec2Clients.get(clusterResource);

        logger.info("Terminating instance: ");
        TerminateInstancesRequest stopInstancesRequest = new TerminateInstancesRequest();
        List<String> instancesToStop = new LinkedList<String>();
        instancesToStop.add(instanceId);
        stopInstancesRequest.setInstanceIds(instancesToStop);

        TerminateInstancesResult result = client.terminateInstances(stopInstancesRequest);
        logger.info("Instances terminated: " + instancesToStop);
    }

    private String getInstanceType(Action actionToExecute) {
        return actionToExecute.getParameterValues().get(EUCALYPTUS_INSTANCE_TYPE);
    }

    private String getImageId(Action actionToExecute) {
        return actionToExecute.getParameterValues().get(EUCALYPTUS_IMAGE_ID);
    }

    private Resource getClusterResource(Action actionToExecute) {
        String clusterInstanceUri = actionToExecute.getParameterValues().get(EUCALYPTUS_CLUSTER_PARAMETER);
        Resource resource = null;
        for (Map.Entry<Resource, AmazonEC2Client> entry : ec2Clients.entrySet()) {
            if (entry.getKey().getUri().equalsIgnoreCase(clusterInstanceUri)) {
                resource = entry.getKey();
                break;
            }
        }

        if (resource == null) {
            String msg = "Tried to execute " + actionToExecute.getActionURI() + " action but node "
                    + clusterInstanceUri + " is not Eucalyptus-enabled!";
            logger.info(msg);
            throw new IllegalArgumentException(msg);
        }

        return resource;
    }

    @Override
    public Object getCapabilityValue(Resource resource, String capabilityType) throws Exception {
        Object retVal = null;
        if (capabilityType.equals(IMAGE_INSTANCES_CAPABILITY)) {
            if (resource.getType().equals(EUCALYPTUS_CLUSTER_TYPE)) {
                AmazonEC2Client ec2Client = ec2Clients.get(resource);
                String imageId = (String) resource.getProperty(EUCALYPTUS_IMAGE_TO_OBSERVE_ID);
                if (imageId == null) {
                    throw new IllegalArgumentException(
                            "Provided resource doesn't have a defined image ID to observe!");
                }
                retVal = new Integer(getImageInstancesCapability(ec2Client, imageId));
            }
        }
        return retVal;
    }

    private int getImageInstancesCapability(AmazonEC2Client ec2Client, String imageId) {
        int imageCount = 0;

        DescribeInstancesResult result = ec2Client.describeInstances();
        for (Reservation reservation : result.getReservations()) {
            for (Instance instance : reservation.getInstances()) {
                if (instance.getImageId().equals(imageId) && EC2Util.isInstanceRunning(instance)) {
                    imageCount++;
                }
            }
        }

        return imageCount;
    }

    @Override
    public boolean hasCapability(Resource resource, String capabilityType) throws Exception {

        if (resource.getType().equals(EUCALYPTUS_CLUSTER_TYPE)) {
            if (capabilityType.equals(IMAGE_INSTANCES_CAPABILITY)) {
                return true;
            }
        }

        return false;
    }

    @Override
    public boolean isActionSupported(String actionUri) {
        return supportedActions.contains(actionUri);
    }

    @Override
    public boolean isURISupported(Resource resource) {
        return resource.hasProperty(EUCALYPTUS_TRANSPORT_PROPERTY_KEY);
    }

    @Override
    public void registerResource(Resource resource) throws Exception {
        Object endpointURL = resource.getProperty(EUCALYPTUS_TRANSPORT_PROPERTY_KEY);

        Object accessKey = resource.getProperty(EUCALYPTUS_ACCESS_KEY);
        Object secretKey = resource.getProperty(EUCALYPTUS_SECRET_KEY);

        AWSCredentials ec2Credentials = new BasicAWSCredentials(accessKey.toString(), secretKey.toString());

        AmazonEC2Client ec2Client = new AddressTypeAddingAmazonEC2Client(ec2Credentials);
        ec2Client.setEndpoint(endpointURL.toString());

        ec2Clients.put(resource, ec2Client);
    }

    @Override
    public void unregisterResource(Resource resource) {
        AmazonEC2Client ec2Client = ec2Clients.get(resource);
        if (ec2Client != null) {
            // not expected to be invoked ;)
            ec2Client.shutdown();
        }
        ec2Clients.remove(resource);
    }

    private void startMVCBasicTomcatInstanceAction(Resource resource, String machineInstanceId, String instanceType)
            throws Exception {
        Instance instance = startOneInstanceAction(resource, machineInstanceId, instanceType, null);
        EC2Util.waitForURL(instance.getPublicDnsName(), 8080, "/mvc-basic");
        logger.info("Tomcat started on instance " + instance.getInstanceId());
        registerNewInstance(resource, instance, VIRTUAL_NODE_TYPE);
    }

    private void registerNewInstance(Resource clusterResource, Instance instance, String type)
            throws ResourceAlreadyRegisteredException {
        Map<String, Object> parameters = new HashMap<String, Object>();
        parameters.put("JMXURL", "service:jmx:rmi://" + instance.getPublicDnsName() + ":60001/jndi/rmi://"
                + instance.getPublicDnsName() + ":60000/jmxrmi");

        parameters.put(EUCALYPTUS_INSTANCE_ID, instance.getInstanceId());

        String childName = VIRTUAL_NODE_NAME_PREFIX + instance.getInstanceId();
        Map<String, String> types = new HashMap<String, String>();
        types.put(childName, type);
        Map<String, Map<String, Object>> childrenProperties = new HashMap<String, Map<String, Object>>();
        childrenProperties.put(childName, parameters);
        fireNewResourcesEvent(clusterResource.getUri(), types, childrenProperties);
    }

    /**
     * starts new VM instance, <b>does not add new resource to core</b>
     *
     * @param resource
     * @param imageId
     * @param instanceType
     * @return
     * @throws Exception
     */
    private Instance startOneInstanceAction(Resource resource, final String imageId, String instanceType,
            String userData) throws Exception {
        AmazonEC2Client client = ec2Clients.get(resource);

        Image image = getImageByImageID(client, imageId);
        if (!IMAGE_TYPE_MACHINE.equals(image.getImageType())) {
            throw new RuntimeException("Provided image type is not machine!");
        }
        if (!IMAGE_STATE_AVAILABLE.equals(image.getState())) {
            throw new RuntimeException("Provided image state is not " + IMAGE_STATE_AVAILABLE);
        }

        RunInstancesRequest command = new RunInstancesRequest();

        if (userData != null) {
            command.setUserData(userData);
        }

        command.setImageId(image.getImageId());
        command.setInstanceType(instanceType);
        command.setKernelId(image.getKernelId());
        command.setMaxCount(1);
        command.setMinCount(1);
        command.setRamdiskId(image.getRamdiskId());
        command.setKeyName(resource.getProperty(EUCALYPTUS_KEY_NAME).toString());

        RunInstancesResult result = client.runInstances(command);
        List<Instance> instances = result.getReservation().getInstances();
        if (instances.size() < 1) {
            logger.error("Something bad happend while running VM instance");
        }
        Instance instance = instances.get(0);
        instance = EC2Util.waitForRunningState(client, instance);

        logger.info("Started new instance of image " + imageId + "! InstanceId = " + instance.getInstanceId());
        instance = EC2Util.waitForPublicDNS(client, instance);
        logger.info("Instance IP address is: " + instance.getPublicDnsName());
        return instance;
    }

    private Image getImageByImageID(AmazonEC2Client client, String imageId) {
        DescribeImagesRequest describeImagesRequest = new DescribeImagesRequest();
        List<String> imageIds = new LinkedList<String>();
        imageIds.add(imageId);
        describeImagesRequest.setImageIds(imageIds);
        List<Image> images = client.describeImages(describeImagesRequest).getImages();
        if (images.size() != 1) {
            throw new RuntimeException("Found " + images.size() + " images of id: " + imageId);
        }
        return images.get(0);
    }

    /**
     * @param mvcBasicImageId the petClinicImageId to set
     */
    public void setMVCBasicImageId(String mvcBasicImageId) {
        this.mvcBasicImageId = mvcBasicImageId;
    }

    @Override
    public boolean isResourceRegistered(Resource resource) {
        return ec2Clients.containsKey(resource);
    }
}