org.apache.stratos.cloud.controller.iaases.cloudstack.CloudStackIaas.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.stratos.cloud.controller.iaases.cloudstack.CloudStackIaas.java

Source

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you 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 org.apache.stratos.cloud.controller.iaases.cloudstack;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.stratos.cloud.controller.domain.IaasProvider;
import org.apache.stratos.cloud.controller.exception.CloudControllerException;
import org.apache.stratos.cloud.controller.exception.InvalidHostException;
import org.apache.stratos.cloud.controller.exception.InvalidRegionException;
import org.apache.stratos.cloud.controller.exception.InvalidZoneException;
import org.apache.stratos.cloud.controller.iaases.JcloudsIaas;
import org.apache.stratos.cloud.controller.iaases.PartitionValidator;
import org.apache.stratos.cloud.controller.util.CloudControllerConstants;
import org.apache.stratos.cloud.controller.util.ComputeServiceBuilderUtil;
import org.jclouds.cloudstack.CloudStackApi;
import org.jclouds.cloudstack.compute.options.CloudStackTemplateOptions;
import org.jclouds.cloudstack.domain.PublicIPAddress;
import org.jclouds.cloudstack.domain.SshKeyPair;
import org.jclouds.cloudstack.domain.Volume;
import org.jclouds.cloudstack.domain.Zone;
import org.jclouds.cloudstack.features.VolumeApi;
import org.jclouds.cloudstack.options.ListPublicIPAddressesOptions;
import org.jclouds.cloudstack.options.ListZonesOptions;
import org.jclouds.compute.ComputeService;
import org.jclouds.compute.ComputeServiceContext;
import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.compute.domain.Template;
import org.jclouds.compute.domain.TemplateBuilder;
import org.jclouds.compute.options.TemplateOptions;
import org.jclouds.domain.Location;

import java.util.*;
import java.util.concurrent.TimeoutException;

public class CloudStackIaas extends JcloudsIaas {

    private static final Log log = LogFactory.getLog(CloudStackIaas.class);

    public CloudStackIaas(IaasProvider iaasProvider) {
        super(iaasProvider);
    }

    @Override
    public void buildComputeServiceAndTemplate() {
        // builds and sets Compute Service
        ComputeService computeService = ComputeServiceBuilderUtil.buildDefaultComputeService(getIaasProvider());
        getIaasProvider().setComputeService(computeService);

        // builds and sets Template
        buildTemplate();
    }

    @Override
    public void buildTemplate() {

        IaasProvider iaasInfo = getIaasProvider();

        //if compute service is not available
        if (iaasInfo.getComputeService() == null) {
            String msg = "Compute service is null for IaaS provider: " + iaasInfo.getName();
            log.error(msg);
            throw new CloudControllerException(msg);
        }

        //create templateBuilder
        TemplateBuilder templateBuilder = iaasInfo.getComputeService().templateBuilder();

        //**SET PROPERTIES TO templateBuilder OBJECT**//

        /**
         * PROPERTY - 1
         * set image id specified
         */
        templateBuilder.imageId(iaasInfo.getImage());

        /**
         *  PROPERTY-2
         *  if user has specified a zone in cloud-controller.xml, set the zone into templateBuilder object
         *  (user should provide the zone id for this, because zone name is not unique in cloudstack)
         */
        if (iaasInfo.getProperty(CloudControllerConstants.AVAILABILITY_ZONE) != null) {
            Set<? extends Location> locations = iaasInfo.getComputeService().listAssignableLocations();
            for (Location location : locations) {
                if (location.getId().equals(iaasInfo.getProperty(CloudControllerConstants.AVAILABILITY_ZONE))) {
                    //if the zone is valid set the zone to templateBuilder Object
                    templateBuilder.locationId(location.getId());
                    log.info("Zone has been set as "
                            + iaasInfo.getProperty(CloudControllerConstants.AVAILABILITY_ZONE) + " with id: "
                            + location.getId());
                    break;
                }
            }
        }

        /**
         * PROPERTY-3
         * if user has specified an instance type in cloud-controller.xml, set the instance type into templateBuilder
         * object.(service offering)
         *Important:Specify the Service Offering type ID. Not the name. Because the name is not unique in cloudstack.
         */
        if (iaasInfo.getProperty(CloudControllerConstants.INSTANCE_TYPE) != null) {
            templateBuilder.hardwareId(iaasInfo.getProperty(CloudControllerConstants.INSTANCE_TYPE));
        }

        //build the template
        Template template = templateBuilder.build();

        /**if you wish to auto assign IPs, instance spawning call should be
         * blocking, but if you
         * wish to assign IPs manually, it can be non-blocking.
         * is auto-assign-ip mode or manual-assign-ip mode?
         */
        boolean blockUntilRunning = Boolean
                .parseBoolean(iaasInfo.getProperty(CloudControllerConstants.AUTO_ASSIGN_IP));
        template.getOptions().as(TemplateOptions.class).blockUntilRunning(blockUntilRunning);

        // this is required in order to avoid creation of additional security
        // groups by Jclouds.
        template.getOptions().as(TemplateOptions.class).inboundPorts(new int[] {});

        //**SET CLOUDSTACK SPECIFIC PROPERTIES TO TEMPLATE OBJECT**//

        //set security group - If you are using basic zone
        if (iaasInfo.getProperty(CloudControllerConstants.SECURITY_GROUP_IDS) != null) {
            template.getOptions().as(CloudStackTemplateOptions.class).securityGroupIds(
                    Arrays.asList(iaasInfo.getProperty(CloudControllerConstants.SECURITY_GROUP_IDS)
                            .split(CloudControllerConstants.ENTRY_SEPARATOR)));
        }

        /**
         * set network ID - If you are using advanced zone
         * in cloudstack sometimes we get unautorized exception if we didn't specify the
         * domain ID and user name
         */
        if (iaasInfo.getProperty(CloudControllerConstants.NETWORK_IDS) != null) {
            template.getOptions().as(CloudStackTemplateOptions.class)
                    .networks(Arrays.asList(iaasInfo.getProperty(CloudControllerConstants.NETWORK_IDS)
                            .split(CloudControllerConstants.ENTRY_SEPARATOR)));
        }

        //set user name
        if (iaasInfo.getProperty(CloudControllerConstants.USER_NAME) != null) {
            template.getOptions().as(CloudStackTemplateOptions.class)
                    .account(iaasInfo.getProperty(CloudControllerConstants.USER_NAME));
        }
        //set domain ID
        if (iaasInfo.getProperty(CloudControllerConstants.DOMAIN_ID) != null) {
            template.getOptions().as(CloudStackTemplateOptions.class)
                    .domainId(iaasInfo.getProperty(CloudControllerConstants.DOMAIN_ID));
        }

        /**
         *Set key pair
         * in cloudstack sometimes we get unauthorized exception if we didn't specify the
         * domain ID and user name
         */
        if (iaasInfo.getProperty(CloudControllerConstants.KEY_PAIR) != null) {
            template.getOptions().as(CloudStackTemplateOptions.class)
                    .keyPair(iaasInfo.getProperty(CloudControllerConstants.KEY_PAIR));
        }

        // ability to define tags
        if (iaasInfo.getProperty(CloudControllerConstants.TAGS) != null) {
            template.getOptions().as(CloudStackTemplateOptions.class).tags(Arrays.asList(iaasInfo
                    .getProperty(CloudControllerConstants.TAGS).split(CloudControllerConstants.ENTRY_SEPARATOR)));
        }
        //set disk offering to the instance
        if (iaasInfo.getProperty(CloudControllerConstants.DISK_OFFERING) != null) {
            template.getOptions().as(CloudStackTemplateOptions.class)
                    .diskOfferingId(iaasInfo.getProperty(CloudControllerConstants.DISK_OFFERING));
        }

        // set Template
        iaasInfo.setTemplate(template);
    }

    @Override
    public void setDynamicPayload(byte[] payload) {
        IaasProvider iaasProvider = getIaasProvider();
        if (iaasProvider.getTemplate() != null) {
            iaasProvider.getTemplate().getOptions().as(CloudStackTemplateOptions.class)
                    .userMetadata(convertByteArrayToHashMap(payload));
        }
    }

    /**
     * IMPORTANT
     * In cloudstack we can assign public IPs, if we are using an advanced zone only. If we are using a basic zone
     * we cannot assign public ips.
     * <p/>
     * When we use an advanced zone, a public IP address will get automatically assigned to the vm. So we don't need
     * to find an unallocated IP address and assign that address to the vm. If you are using a basic zone you cannot
     * assign public IPs
     * <p/>
     * So  this method will find the IP that has been assigned to the vm and return it.
     */
    @Override
    public List<String> associateAddresses(NodeMetadata node) {

        IaasProvider iaasInfo = getIaasProvider();
        ComputeServiceContext context = iaasInfo.getComputeService().getContext();
        CloudStackApi cloudStackApi = context.unwrapApi(CloudStackApi.class);
        String ip = null;

        // get all allocated IPs
        ListPublicIPAddressesOptions listPublicIPAddressesOptions = new ListPublicIPAddressesOptions();
        listPublicIPAddressesOptions.zoneId(iaasInfo.getProperty(CloudControllerConstants.AVAILABILITY_ZONE));

        Set<PublicIPAddress> publicIPAddresses = cloudStackApi.getAddressApi()
                .listPublicIPAddresses(listPublicIPAddressesOptions);

        String id = node.getProviderId(); //vm ID

        for (PublicIPAddress publicIPAddress : publicIPAddresses) {
            if (publicIPAddress.getVirtualMachineId().equals(id)) { //check whether this instance has
                // already got an public ip or not
                ip = publicIPAddress.getIPAddress(); //A public ip has been successfully assigned to the vm
                log.info("Successfully associated an IP address " + ip + " for node with id: " + node.getId());
                break;
            }

        }

        if (ip == null || ip.isEmpty()) { //IP has not been successfully assigned to VM(That means there are
            //  no more IPs  available for the VM)
            String msg = "No address associated for node with id: " + node.getId();
            log.warn(msg);
            throw new CloudControllerException(msg);
        }

        List<String> associatedIPs = new ArrayList<String>();
        associatedIPs.add(ip);

        return associatedIPs;
    }

    @Override
    public String associatePredefinedAddress(NodeMetadata node, String ip) {
        return "";
    }

    @Override
    public void releaseAddress(String ip) {
        IaasProvider iaasInfo = getIaasProvider();
        ComputeServiceContext context = iaasInfo.getComputeService().getContext();
        CloudStackApi cloudStackApi = context.unwrapApi(CloudStackApi.class);
        cloudStackApi.getAddressApi().disassociateIPAddress(ip);
    }

    @Override
    public boolean createKeyPairFromPublicKey(String region, String keyPairName, String publicKey) {

        IaasProvider iaasInfo = getIaasProvider();
        ComputeServiceContext context = iaasInfo.getComputeService().getContext();
        CloudStackApi cloudStackApi = context.unwrapApi(CloudStackApi.class);
        SshKeyPair sshKeyPair = cloudStackApi.getSSHKeyPairApi().createSSHKeyPair(keyPairName);

        if (sshKeyPair != null) {

            iaasInfo.getTemplate().getOptions().as(CloudStackTemplateOptions.class).keyPair(sshKeyPair.getName());

            log.info("A key-pair is created successfully - Key Pair Name: " + sshKeyPair.getName());
            return true;
        }
        log.error("Key-pair is unable to create");
        return false;
    }

    @Override
    public boolean isValidRegion(String region) throws InvalidRegionException {

        IaasProvider iaasInfo = getIaasProvider();
        //no such method in Jclouds cloudstack api
        String msg = "Invalid region: " + region + " in the iaas: " + iaasInfo.getType();
        log.error(msg);
        throw new InvalidRegionException(msg);
    }

    @Override
    public boolean isValidZone(String region, String zone) throws InvalidZoneException {

        IaasProvider iaasInfo = getIaasProvider();
        ComputeServiceContext context = iaasInfo.getComputeService().getContext();
        CloudStackApi cloudStackApi = context.unwrapApi(CloudStackApi.class);
        ListZonesOptions listZonesOptions = new ListZonesOptions();
        listZonesOptions.available(true);
        Set<Zone> zoneSet = cloudStackApi.getZoneApi().listZones(listZonesOptions);

        for (org.jclouds.cloudstack.domain.Zone configuredZone : zoneSet) {
            if (configuredZone.getName().equalsIgnoreCase(zone)) {
                return true;
            }
        }
        String msg = "Invalid zone: " + zone + " in the iaas: " + iaasInfo.getType();
        log.error(msg);
        throw new InvalidZoneException(msg);
    }

    @Override
    public boolean isValidHost(String zone, String host) throws InvalidHostException {

        IaasProvider iaasInfo = getIaasProvider();
        // there's no such method in jclouds cloustack api
        String msg = "Invalid host: " + host + " in the zone: " + zone + " and of the iaas: " + iaasInfo.getType();
        log.error(msg);
        throw new InvalidHostException(msg);

    }

    @Override
    public PartitionValidator getPartitionValidator() {
        return new CloudStackPartitionValidator();
    }

    @Override
    public String createVolume(int sizeGB, String snapshotId) {

        // Snapshot id is not there in IaaS.createVolume() method in stratos 4.0.0
        //todo return volume ID if volume is created
        IaasProvider iaasInfo = getIaasProvider();
        ComputeServiceContext context = iaasInfo.getComputeService().getContext();

        String zone = ComputeServiceBuilderUtil.extractZone(iaasInfo);
        String diskOfferingID = iaasInfo.getTemplate().getOptions().as(CloudStackTemplateOptions.class)
                .getDiskOfferingId();
        if (zone == null && diskOfferingID == null) {
            log.error("Could not create a volume in the , [zone] : " + zone + " of Iaas : " + iaasInfo);
            return null;
        }

        VolumeApi volumeApi = context.unwrapApi(CloudStackApi.class).getVolumeApi();

        Volume volume;
        if (StringUtils.isEmpty(snapshotId)) {
            if (log.isInfoEnabled()) {
                log.info("Creating a volume in the zone " + zone);
            }

            //cloudstack jcloud api does not return a volume object
            volumeApi.createVolumeFromCustomDiskOfferingInZone(null, diskOfferingID, zone, sizeGB);

            //  volume = blockStoreApi.createVolumeInAvailabilityZone(zone, sizeGB);
        } else {
            if (log.isInfoEnabled()) {
                log.info("Creating a volume in the zone " + zone + " from the snapshot " + snapshotId);
            }
            volumeApi.createVolumeFromSnapshotInZone(null, diskOfferingID, zone);
        }

        return null;
    }

    @Override
    public String attachVolume(String instanceId, String volumeId, String deviceName) {
        IaasProvider iaasInfo = getIaasProvider();
        ComputeServiceContext context = iaasInfo.getComputeService().getContext();
        CloudStackApi cloudStackApi = context.unwrapApi(CloudStackApi.class);

        //get volume
        org.jclouds.cloudstack.domain.Volume volume = cloudStackApi.getVolumeApi().getVolume(volumeId);

        //get current volume state
        Volume.State volumeState = volume.getState();

        if (log.isDebugEnabled()) {
            log.debug("Volume " + volumeId + " is in state " + volumeState);
        }

        //if volume is not available, not allocated or cannot use
        //volume state ALLOCATED   means that volume has not been attached to any instance.

        //TODO there is an error with logic.
        if (!(volumeState == Volume.State.ALLOCATED || volumeState == Volume.State.CREATING
                || volumeState == Volume.State.READY)) {
            log.error(String.format("Volume %s can not be attached. Volume status is %s", volumeId, volumeState));
        }

        //check whether the account of volume and instance is same
        if (!volume.getAccount()
                .equals(cloudStackApi.getVirtualMachineApi().getVirtualMachine(instanceId).getAccount())) {
            log.error(String.format(
                    "Volume %s can not be attached. Instance account and Volume account " + "are not the same ",
                    volumeId));
        }

        boolean volumeBecameAvailable = false, volumeBecameAttached = false;

        try {
            if (volumeState == Volume.State.CREATING) {

                volumeBecameAvailable = waitForStatus(volumeId, Volume.State.ALLOCATED, 5);

            } else if (volumeState == Volume.State.READY) {
                volumeBecameAvailable = true;
            }

        } catch (TimeoutException e) {
            log.error("[Volume ID] " + volumeId + "did not become ALLOCATED within expected timeout");
        }

        //if volume state is 'ALLOCATED'
        if (volumeBecameAvailable) {

            //attach volume into instance
            cloudStackApi.getVolumeApi().attachVolume(volumeId, instanceId);

            try {
                volumeBecameAttached = waitForStatus(volumeId, Volume.State.READY, 2);
            } catch (TimeoutException e) {
                log.error("[Volume ID] " + volumeId + "did not become READY within expected timeout");
            }
        }

        try {
            // waiting 5seconds till volumes are actually attached.
            Thread.sleep(5000);
        } catch (InterruptedException ignored) {

        }

        //If volume state is not 'READY'
        if (!volumeBecameAttached) {
            log.error(String.format("[Volume ID] %s attachment is called, but not yet became attached", volumeId));
        }

        log.info(
                String.format("Volume [id]: %s attachment for instance [id]: %s was successful [status]: Attaching."
                        + " of Iaas : %s", volumeId, instanceId, iaasInfo));

        return "Attaching";

    }

    @Override
    public void detachVolume(String instanceId, String volumeId) {

        IaasProvider iaasInfo = getIaasProvider();

        ComputeServiceContext context = iaasInfo.getComputeService().getContext();

        if (log.isDebugEnabled()) {
            log.debug(String.format("Starting to detach volume %s from the instance %s", volumeId, instanceId));
        }

        CloudStackApi cloudStackApi = context.unwrapApi(CloudStackApi.class);

        cloudStackApi.getVolumeApi().detachVolume(volumeId);

        try {
            //TODO this is true only for newly created volumes
            if (waitForStatus(volumeId, Volume.State.ALLOCATED, 5)) {
                log.info(String.format(
                        "Detachment of Volume [id]: %s from instance [id]: %s was successful of Iaas : %s",
                        volumeId, instanceId, iaasInfo));
            }
        } catch (TimeoutException e) {
            log.error(String.format(
                    "Detachment of Volume [id]: %s from instance [id]: %s was unsuccessful. [volume Status] : %s",
                    volumeId, instanceId, iaasInfo));
        }

    }

    @Override
    public void deleteVolume(String volumeId) {
        IaasProvider iaasInfo = getIaasProvider();
        ComputeServiceContext context = iaasInfo.getComputeService().getContext();
        CloudStackApi cloudStackApi = context.unwrapApi(CloudStackApi.class);
        cloudStackApi.getVolumeApi().deleteVolume(volumeId);
        log.info("Deletion of Volume [id]: " + volumeId + " was successful. " + " of Iaas : " + iaasInfo);
    }

    @Override
    public String getIaasDevice(String device) {//todo implement this method(auto generated method)
        return null;
    }

    private boolean waitForStatus(String volumeId, Volume.State expectedStatus, int timeoutInMilliseconds)
            throws TimeoutException {
        int timeout = 1000 * 60 * timeoutInMilliseconds;
        long timout = System.currentTimeMillis() + timeout;

        IaasProvider iaasInfo = getIaasProvider();
        ComputeServiceContext context = iaasInfo.getComputeService().getContext();
        CloudStackApi cloudStackApi = context.unwrapApi(CloudStackApi.class);

        //get volume
        org.jclouds.cloudstack.domain.Volume volume = cloudStackApi.getVolumeApi().getVolume(volumeId);

        Volume.State volumeState = volume.getState();

        while (volumeState != expectedStatus) {
            try {
                if (log.isDebugEnabled()) {
                    log.debug(String.format("Volume %s is still NOT in %s. Current State=%s", volumeId,
                            expectedStatus, volumeState));
                }
                if (volumeState == Volume.State.FAILED || volumeState == Volume.State.DESTROYED
                        || volumeState == Volume.State.UNRECOGNIZED) {
                    log.error("Volume " + volumeId + " is in state" + volumeState);
                    return false;
                }

                Thread.sleep(1000);
                volumeState = volume.getState();
                if (System.currentTimeMillis() > timout) {
                    throw new TimeoutException();
                }
            } catch (InterruptedException e) {
                // Ignoring the exception
            }
        }
        if (log.isDebugEnabled()) {
            log.debug(String.format("Volume %s status became %s", volumeId, expectedStatus));
        }

        return true;
    }

    private Map<String, String> convertByteArrayToHashMap(byte[] byteArray) {

        Map<String, String> map = new HashMap<String, String>();

        String stringFromByteArray = new String(byteArray);
        String[] keyValuePairs = stringFromByteArray.split(",");

        for (String keyValuePair : keyValuePairs) {
            String[] keyValue = keyValuePair.split("=");
            if (keyValue.length > 1) {
                map.put(keyValue[0], keyValue[1]);
            }
        }

        return map;
    }
}