org.apache.stratos.cloud.controller.iaases.OpenstackNovaIaas.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.stratos.cloud.controller.iaases.OpenstackNovaIaas.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;

import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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.interfaces.Iaas;
import org.apache.stratos.cloud.controller.jcloud.ComputeServiceBuilderUtil;
import org.apache.stratos.cloud.controller.pojo.IaasProvider;
import org.apache.stratos.cloud.controller.pojo.NetworkInterface;
import org.apache.stratos.cloud.controller.util.CloudControllerConstants;
import org.apache.stratos.cloud.controller.util.CloudControllerUtil;
import org.apache.stratos.cloud.controller.validate.OpenstackNovaPartitionValidator;
import org.apache.stratos.cloud.controller.validate.interfaces.PartitionValidator;
import org.jclouds.compute.ComputeServiceContext;
import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.compute.domain.NodeMetadataBuilder;
import org.jclouds.compute.domain.Template;
import org.jclouds.compute.domain.TemplateBuilder;
import org.jclouds.compute.options.TemplateOptions;
import org.jclouds.openstack.nova.v2_0.NovaApi;
import org.jclouds.openstack.nova.v2_0.NovaApiMetadata;
import org.jclouds.openstack.nova.v2_0.NovaAsyncApi;
import org.jclouds.openstack.nova.v2_0.compute.options.NovaTemplateOptions;
import org.jclouds.openstack.nova.v2_0.domain.FloatingIP;
import org.jclouds.openstack.nova.v2_0.domain.HostAggregate;
import org.jclouds.openstack.nova.v2_0.domain.KeyPair;
import org.jclouds.openstack.nova.v2_0.domain.Network;
import org.jclouds.openstack.nova.v2_0.domain.Volume;
import org.jclouds.openstack.nova.v2_0.domain.VolumeAttachment;
import org.jclouds.openstack.nova.v2_0.domain.zonescoped.AvailabilityZone;
import org.jclouds.openstack.nova.v2_0.extensions.AvailabilityZoneAPI;
import org.jclouds.openstack.nova.v2_0.extensions.FloatingIPApi;
import org.jclouds.openstack.nova.v2_0.extensions.HostAggregateApi;
import org.jclouds.openstack.nova.v2_0.extensions.KeyPairApi;
import org.jclouds.openstack.nova.v2_0.extensions.VolumeApi;
import org.jclouds.openstack.nova.v2_0.extensions.VolumeAttachmentApi;
import org.jclouds.openstack.nova.v2_0.options.CreateVolumeOptions;
import org.jclouds.rest.RestContext;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;

@SuppressWarnings("deprecation")
public class OpenstackNovaIaas extends Iaas {

    private static final Log log = LogFactory.getLog(OpenstackNovaIaas.class);
    private static final String SUCCESSFUL_LOG_LINE = "A key-pair is created successfully in ";
    private static final String FAILED_LOG_LINE = "Key-pair is unable to create in ";

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

    @Override
    public void buildComputeServiceAndTemplate() {

        IaasProvider iaasInfo = getIaasProvider();

        // builds and sets Compute Service
        ComputeServiceBuilderUtil.buildDefaultComputeService(iaasInfo);

        // builds and sets Template
        buildTemplate();

    }

    public void buildTemplate() {
        IaasProvider iaasInfo = getIaasProvider();

        if (iaasInfo.getComputeService() == null) {
            throw new CloudControllerException("Compute service is null for IaaS provider: " + iaasInfo.getName());
        }

        TemplateBuilder templateBuilder = iaasInfo.getComputeService().templateBuilder();
        templateBuilder.imageId(iaasInfo.getImage());
        if (!(iaasInfo instanceof IaasProvider)) {
            templateBuilder.locationId(iaasInfo.getType());
        }

        // to avoid creation of template objects in each and every time, we
        // create all at once!

        String instanceType;

        // set instance type
        if (((instanceType = iaasInfo.getProperty(CloudControllerConstants.INSTANCE_TYPE)) != null)) {

            templateBuilder.hardwareId(instanceType);
        }

        Template template = templateBuilder.build();

        // In Openstack the call to IaaS should be blocking, in order to retrieve 
        // IP addresses.
        boolean blockUntilRunning = true;
        if (iaasInfo.getProperty(CloudControllerConstants.BLOCK_UNTIL_RUNNING) != null) {
            blockUntilRunning = Boolean
                    .parseBoolean(iaasInfo.getProperty(CloudControllerConstants.BLOCK_UNTIL_RUNNING));
        }
        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[] {});

        if (iaasInfo.getProperty(CloudControllerConstants.SECURITY_GROUPS) != null) {
            template.getOptions().as(NovaTemplateOptions.class)
                    .securityGroupNames(iaasInfo.getProperty(CloudControllerConstants.SECURITY_GROUPS)
                            .split(CloudControllerConstants.ENTRY_SEPARATOR));
        }

        if (iaasInfo.getProperty(CloudControllerConstants.KEY_PAIR) != null) {
            template.getOptions().as(NovaTemplateOptions.class)
                    .keyPairName(iaasInfo.getProperty(CloudControllerConstants.KEY_PAIR));
        }

        if (iaasInfo.getNetworkInterfaces() != null) {
            Set<Network> novaNetworksSet = new LinkedHashSet<Network>(iaasInfo.getNetworkInterfaces().length);
            for (NetworkInterface ni : iaasInfo.getNetworkInterfaces()) {
                novaNetworksSet.add(Network.builder().networkUuid(ni.getNetworkUuid()).fixedIp(ni.getFixedIp())
                        .portUuid(ni.getPortUuid()).build());
            }
            template.getOptions().as(NovaTemplateOptions.class).novaNetworks(novaNetworksSet);
        }

        if (iaasInfo.getProperty(CloudControllerConstants.AVAILABILITY_ZONE) != null) {
            template.getOptions().as(NovaTemplateOptions.class)
                    .availabilityZone(iaasInfo.getProperty(CloudControllerConstants.AVAILABILITY_ZONE));
        }

        //TODO
        //      if (iaas.getProperty(CloudControllerConstants.HOST) != null) {
        //            template.getOptions().as(NovaTemplateOptions.class)
        //                    .(CloudControllerConstants.HOST);
        //        }

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

    @Override
    public void setDynamicPayload() {

        IaasProvider iaasInfo = getIaasProvider();

        if (iaasInfo.getTemplate() != null && iaasInfo.getPayload() != null) {

            iaasInfo.getTemplate().getOptions().as(NovaTemplateOptions.class).userData(iaasInfo.getPayload());
        }

    }

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

        IaasProvider iaasInfo = getIaasProvider();

        String openstackNovaMsg = " Openstack-nova. Region: " + region + " - Name: ";

        ComputeServiceContext context = iaasInfo.getComputeService().getContext();
        RestContext<NovaApi, NovaAsyncApi> nova = context.unwrap();
        KeyPairApi api = nova.getApi().getKeyPairExtensionForZone(region).get();

        KeyPair keyPair = api.createWithPublicKey(keyPairName, publicKey);

        if (keyPair != null) {

            iaasInfo.getTemplate().getOptions().as(NovaTemplateOptions.class).keyPairName(keyPair.getName());

            log.info(SUCCESSFUL_LOG_LINE + openstackNovaMsg + keyPair.getName());
            return true;
        }

        log.error(FAILED_LOG_LINE + openstackNovaMsg);
        return false;

    }

    @Override
    public synchronized String associateAddress(NodeMetadata node) {

        IaasProvider iaasInfo = getIaasProvider();

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

        String region = ComputeServiceBuilderUtil.extractRegion(iaasInfo);

        RestContext<NovaApi, NovaAsyncApi> nova = context.unwrap();
        FloatingIPApi floatingIp = nova.getApi().getFloatingIPExtensionForZone(region).get();

        String ip = null;
        // first try to find an unassigned IP.
        ArrayList<FloatingIP> unassignedIps = Lists
                .newArrayList(Iterables.filter(floatingIp.list(), new Predicate<FloatingIP>() {

                    @Override
                    public boolean apply(FloatingIP arg0) {
                        return arg0.getInstanceId() == null;
                    }

                }));

        if (!unassignedIps.isEmpty()) {
            // try to prevent multiple parallel launches from choosing the same
            // ip.
            Collections.shuffle(unassignedIps);
            ip = Iterables.getLast(unassignedIps).getIp();
        }

        // if no unassigned IP is available, we'll try to allocate an IP.
        if (ip == null || ip.isEmpty()) {
            FloatingIP allocatedFloatingIP = floatingIp.create();
            if (allocatedFloatingIP == null) {
                String msg = "Failed to allocate an IP address.";
                log.error(msg);
                throw new CloudControllerException(msg);
            }
            ip = allocatedFloatingIP.getIp();
        }

        // wait till the fixed IP address gets assigned - this is needed before
        // we associate a public IP
        while (node.getPrivateAddresses() == null) {
            CloudControllerUtil.sleep(1000);
        }

        if (node.getPublicAddresses() != null && node.getPublicAddresses().iterator().hasNext()) {
            log.info("A public IP (" + node.getPublicAddresses().iterator().next()
                    + ") is already allocated to the instance [id] : " + node.getId());
            return null;
        }

        int retries = 0;
        //TODO make 5 configurable
        while (retries < 5 && !associateIp(floatingIp, ip, node.getProviderId())) {

            // wait for 5s
            CloudControllerUtil.sleep(5000);
            retries++;
        }

        log.info("Successfully associated an IP address " + ip + " for node with id: " + node.getId());

        return ip;
    }

    @Override
    public synchronized String associatePredefinedAddress(NodeMetadata node, String ip) {
        if (log.isDebugEnabled()) {
            log.debug("OpenstackNovaIaas:associatePredefinedAddress:ip:" + ip);
        }

        IaasProvider iaasInfo = getIaasProvider();

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

        @SuppressWarnings("deprecation")
        NovaApi novaClient = context.unwrap(NovaApiMetadata.CONTEXT_TOKEN).getApi();
        String region = ComputeServiceBuilderUtil.extractRegion(iaasInfo);

        FloatingIPApi floatingIp = novaClient.getFloatingIPExtensionForZone(region).get();

        if (log.isDebugEnabled()) {
            log.debug("OpenstackNovaIaas:associatePredefinedAddress:floatingip:" + floatingIp);
        }

        // get the list of all unassigned IP.
        ArrayList<FloatingIP> unassignedIps = Lists
                .newArrayList(Iterables.filter(floatingIp.list(), new Predicate<FloatingIP>() {

                    @Override
                    public boolean apply(FloatingIP arg0) {
                        // FIXME is this the correct filter?
                        return arg0.getFixedIp() == null;
                    }

                }));

        boolean isAvailable = false;
        for (FloatingIP fip : unassignedIps) {
            if (log.isDebugEnabled()) {
                log.debug(
                        "OpenstackNovaIaas:associatePredefinedAddress:iterating over available floatingip:" + fip);
            }
            if (ip.equals(fip.getIp())) {
                if (log.isDebugEnabled()) {
                    log.debug("OpenstackNovaIaas:associatePredefinedAddress:floating ip in use:" + fip + " /ip:"
                            + ip);
                }
                isAvailable = true;
                break;
            }
        }

        if (isAvailable) {
            // assign ip
            if (log.isDebugEnabled()) {
                log.debug("OpenstackNovaIaas:associatePredefinedAddress:assign floating ip:" + ip);
            }
            // exercise same code as in associateAddress()
            // wait till the fixed IP address gets assigned - this is needed before
            // we associate a public IP

            while (node.getPrivateAddresses() == null) {
                CloudControllerUtil.sleep(1000);
            }

            int retries = 0;
            while (retries < 5 && !associateIp(floatingIp, ip, node.getProviderId())) {

                // wait for 5s
                CloudControllerUtil.sleep(5000);
                retries++;
            }

            NodeMetadataBuilder.fromNodeMetadata(node).publicAddresses(ImmutableSet.of(ip)).build();

            log.info("OpenstackNovaIaas:associatePredefinedAddress:Successfully associated an IP address " + ip
                    + " for node with id: " + node.getId());
        } else {
            // unable to allocate predefined ip,
            log.info("OpenstackNovaIaas:associatePredefinedAddress:Unable to allocate predefined ip:"
                    + " for node with id: " + node.getId());
            return "";
        }

        NodeMetadataBuilder.fromNodeMetadata(node).publicAddresses(ImmutableSet.of(ip)).build();

        log.info("OpenstackNovaIaas:associatePredefinedAddress::Successfully associated an IP address " + ip
                + " for node with id: " + node.getId());

        return ip;

    }

    @Override
    public synchronized void releaseAddress(String ip) {

        IaasProvider iaasInfo = getIaasProvider();

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

        String region = ComputeServiceBuilderUtil.extractRegion(iaasInfo);

        @SuppressWarnings("deprecation")
        RestContext<NovaApi, NovaAsyncApi> nova = context.unwrap();
        @SuppressWarnings("deprecation")
        FloatingIPApi floatingIPApi = nova.getApi().getFloatingIPExtensionForZone(region).get();

        for (FloatingIP floatingIP : floatingIPApi.list()) {
            if (floatingIP.getIp().equals(ip)) {
                floatingIPApi.delete(floatingIP.getId());
                break;
            }
        }

    }

    private boolean associateIp(FloatingIPApi api, String ip, String id) {
        try {
            api.addToServer(ip, id);
            return true;
        } catch (RuntimeException ex) {
            return false;
        }
    }

    @Override
    public boolean isValidRegion(String region) throws InvalidRegionException {
        IaasProvider iaasInfo = getIaasProvider();

        // jclouds' zone = region in openstack
        if (region == null || iaasInfo == null) {
            String msg = "Region or IaaSProvider is null: region: " + region + " - IaaSProvider: " + iaasInfo;
            log.error(msg);
            throw new InvalidRegionException(msg);
        }

        ComputeServiceContext context = iaasInfo.getComputeService().getContext();
        RestContext<NovaApi, NovaAsyncApi> nova = context.unwrap();
        Set<String> zones = nova.getApi().getConfiguredZones();
        for (String configuredZone : zones) {
            if (region.equalsIgnoreCase(configuredZone)) {
                if (log.isDebugEnabled()) {
                    log.debug("Found a matching region: " + region);
                }
                return true;
            }
        }

        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();

        // jclouds availability zone = stratos zone
        if (region == null || zone == null || iaasInfo == null) {
            String msg = "Host or Zone or IaaSProvider is null: region: " + region + " - zone: " + zone
                    + " - IaaSProvider: " + iaasInfo;
            log.error(msg);
            throw new InvalidZoneException(msg);
        }
        ComputeServiceContext context = iaasInfo.getComputeService().getContext();
        RestContext<NovaApi, NovaAsyncApi> nova = context.unwrap();
        AvailabilityZoneAPI zoneApi = nova.getApi().getAvailabilityZoneApi(region);
        for (AvailabilityZone z : zoneApi.list()) {

            if (zone.equalsIgnoreCase(z.getName())) {
                if (log.isDebugEnabled()) {
                    log.debug("Found a matching availability zone: " + zone);
                }
                return true;
            }
        }

        String msg = "Invalid zone: " + zone + " in the region: " + region + " and of the iaas: "
                + iaasInfo.getType();
        log.error(msg);
        throw new InvalidZoneException(msg);

    }

    @Override
    public boolean isValidHost(String zone, String host) throws InvalidHostException {
        IaasProvider iaasInfo = getIaasProvider();

        if (host == null || zone == null || iaasInfo == null) {
            String msg = "Host or Zone or IaaSProvider is null: host: " + host + " - zone: " + zone
                    + " - IaaSProvider: " + iaasInfo;
            log.error(msg);
            throw new InvalidHostException(msg);
        }
        ComputeServiceContext context = iaasInfo.getComputeService().getContext();
        RestContext<NovaApi, NovaAsyncApi> nova = context.unwrap();
        HostAggregateApi hostApi = nova.getApi().getHostAggregateExtensionForZone(zone).get();
        for (HostAggregate hostAggregate : hostApi.list()) {
            for (String configuredHost : hostAggregate.getHosts()) {
                if (host.equalsIgnoreCase(configuredHost)) {
                    if (log.isDebugEnabled()) {
                        log.debug("Found a matching host: " + host);
                    }
                    return true;
                }
            }
        }

        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 OpenstackNovaPartitionValidator();
    }

    @Override
    public String createVolume(int sizeGB) {
        IaasProvider iaasInfo = getIaasProvider();
        String region = ComputeServiceBuilderUtil.extractRegion(iaasInfo);
        String zone = ComputeServiceBuilderUtil.extractZone(iaasInfo);

        if (region == null || iaasInfo == null) {
            log.fatal("Cannot create a new volume in the [region] : " + region + " of Iaas : " + iaasInfo);
            return null;
        }
        ComputeServiceContext context = iaasInfo.getComputeService().getContext();

        RestContext<NovaApi, NovaAsyncApi> nova = context.unwrap();
        VolumeApi api = nova.getApi().getVolumeExtensionForZone(region).get();
        Volume volume = api.create(sizeGB, CreateVolumeOptions.Builder.availabilityZone(zone));
        if (volume == null) {
            log.fatal("Volume creation was unsuccessful. [region] : " + region + " [zone] : " + zone + " of Iaas : "
                    + iaasInfo);
            return null;
        }

        log.info("Successfully created a new volume [id]: " + volume.getId() + " in [region] : " + region
                + " [zone] : " + zone + " of Iaas : " + iaasInfo);
        return volume.getId();
    }

    @Override
    public String attachVolume(String instanceId, String volumeId, String deviceName) {
        IaasProvider iaasInfo = getIaasProvider();

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

        String region = ComputeServiceBuilderUtil.extractRegion(iaasInfo);
        String device = deviceName == null ? "/dev/vdc" : deviceName;

        if (region == null) {
            log.fatal("Cannot attach the volume [id]: " + volumeId + " in the [region] : " + region + " of Iaas : "
                    + iaasInfo);
            return null;
        }

        RestContext<NovaApi, NovaAsyncApi> nova = context.unwrap();
        VolumeAttachmentApi api = nova.getApi().getVolumeAttachmentExtensionForZone(region).get();
        VolumeAttachment attachment = api.attachVolumeToServerAsDevice(volumeId, instanceId, device);

        if (attachment == null) {
            log.fatal("Volume [id]: " + volumeId + " attachment for instance [id]: " + instanceId
                    + " was unsuccessful. [region] : " + region + " of Iaas : " + iaasInfo);
            return null;
        }

        log.info("Volume [id]: " + volumeId + " attachment for instance [id]: " + instanceId
                + " was successful [status]: " + "Attaching" + ". [region] : " + region + " of Iaas : " + iaasInfo);
        return "Attaching";
    }

    @Override
    public void detachVolume(String instanceId, String volumeId) {
        IaasProvider iaasInfo = getIaasProvider();

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

        String region = ComputeServiceBuilderUtil.extractRegion(iaasInfo);

        if (region == null) {
            log.fatal("Cannot detach the volume [id]: " + volumeId + " from the instance [id]: " + instanceId
                    + " of the [region] : " + region + " of Iaas : " + iaasInfo);
            return;
        }

        RestContext<NovaApi, NovaAsyncApi> nova = context.unwrap();
        VolumeAttachmentApi api = nova.getApi().getVolumeAttachmentExtensionForZone(region).get();
        if (api.detachVolumeFromServer(volumeId, instanceId)) {
            log.info("Detachment of Volume [id]: " + volumeId + " from instance [id]: " + instanceId
                    + " was successful. [region] : " + region + " of Iaas : " + iaasInfo);
        }

    }

    @Override
    public void deleteVolume(String volumeId) {
        IaasProvider iaasInfo = getIaasProvider();

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

        String region = ComputeServiceBuilderUtil.extractRegion(iaasInfo);

        if (region == null) {
            log.fatal("Cannot delete the volume [id]: " + volumeId + " of the [region] : " + region + " of Iaas : "
                    + iaasInfo);
            return;
        }

        RestContext<NovaApi, NovaAsyncApi> nova = context.unwrap();
        VolumeApi api = nova.getApi().getVolumeExtensionForZone(region).get();
        if (api.delete(volumeId)) {
            log.info("Deletion of Volume [id]: " + volumeId + " was successful. [region] : " + region
                    + " of Iaas : " + iaasInfo);
        }
    }

    @Override
    public String getIaasDevice(String device) {
        return device;
    }

}