org.cloudifysource.esc.driver.provisioning.storage.openstack.OpenstackStorageDriver.java Source code

Java tutorial

Introduction

Here is the source code for org.cloudifysource.esc.driver.provisioning.storage.openstack.OpenstackStorageDriver.java

Source

/*******************************************************************************
 * Copyright (c) 2012 GigaSpaces Technologies Ltd. All rights reserved
 *
 * 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 org.cloudifysource.esc.driver.provisioning.storage.openstack;

import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;

import org.apache.commons.lang.StringUtils;
import org.cloudifysource.dsl.cloud.Cloud;
import org.cloudifysource.dsl.cloud.compute.ComputeTemplate;
import org.cloudifysource.dsl.cloud.storage.StorageTemplate;
import org.cloudifysource.esc.driver.provisioning.ProvisioningDriverListener;
import org.cloudifysource.esc.driver.provisioning.storage.BaseStorageDriver;
import org.cloudifysource.esc.driver.provisioning.storage.StorageProvisioningDriver;
import org.cloudifysource.esc.driver.provisioning.storage.StorageProvisioningException;
import org.cloudifysource.esc.driver.provisioning.storage.VolumeDetails;
import org.cloudifysource.esc.jclouds.JCloudsDeployer;
import org.jclouds.compute.ComputeServiceContext;
import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.openstack.nova.v2_0.NovaApi;
import org.jclouds.openstack.nova.v2_0.NovaAsyncApi;
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.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 com.google.common.base.Optional;
import com.google.common.collect.FluentIterable;

/**
 * Storage Provisioning implementation on Openstack.
 * @author noak
 * @since 2.5.0
 */
public class OpenstackStorageDriver extends BaseStorageDriver implements StorageProvisioningDriver {

    private static final int VOLUME_POLLING_INTERVAL_MILLIS = 10 * 1000; // 10 seconds
    private static final String VOLUME_DESCRIPTION = "Cloudify generated volume";
    private static final String OPENSTACK_CUSTOM_VOLUME_ZONE = "openstack.storage.volume.zone";
    private static final String EVENT_ATTEMPT_CONNECTION_TO_CLOUD_API = "try_to_connect_to_cloud_api";
    private static final String EVENT_ACCOMPLISHED_CONNECTION_TO_CLOUD_API = "connection_to_cloud_api_succeeded";
    private static final java.util.logging.Logger logger = java.util.logging.Logger
            .getLogger(OpenstackStorageDriver.class.getName());

    private ComputeTemplate computeTemplate;
    private ComputeServiceContext computeContext;
    private JCloudsDeployer deployer;
    private RestContext<NovaApi, NovaAsyncApi> novaContext;
    private String region;
    private Cloud cloud;

    protected final List<ProvisioningDriverListener> eventsListenersList = new LinkedList<ProvisioningDriverListener>();

    @Override
    public void setComputeContext(final Object computeContext) {
        if (computeContext != null) {
            if (computeContext instanceof ComputeServiceContext) {
                this.computeContext = (ComputeServiceContext) computeContext;
            } else {
                throw new IllegalArgumentException(
                        "ComputeContext object is not instance of " + ComputeServiceContext.class.getName());
            }
        }
    }

    @Override
    public void setConfig(final Cloud cloud, final String computeTemplateName) {
        logger.fine("Initializing storage provisioning on Openstack");
        this.cloud = cloud;
        publishEvent(EVENT_ATTEMPT_CONNECTION_TO_CLOUD_API, cloud.getProvider().getProvider());
        publishEvent(EVENT_ACCOMPLISHED_CONNECTION_TO_CLOUD_API, cloud.getProvider().getProvider());
        novaContext = this.computeContext.unwrap();
        computeTemplate = cloud.getCloudCompute().getTemplates().get(computeTemplateName);
        initDeployer();
        region = getRegionFromHardwareId(computeTemplate.getHardwareId());
    }

    @Override
    public VolumeDetails createVolume(final String templateName, final String location, final long duration,
            final TimeUnit timeUnit) throws TimeoutException, StorageProvisioningException {

        final long endTime = System.currentTimeMillis() + timeUnit.toMillis(duration);
        final VolumeDetails volumeDetails = new VolumeDetails();
        Volume volume;

        //ignoring the passed location, it's a wrong format, taking the compute location instead
        Optional<? extends VolumeApi> volumeApi = getVolumeApi();

        if (!volumeApi.isPresent()) {
            throw new StorageProvisioningException("Failed to create volume, Openstack API is not initialized.");
        }

        if (computeContext == null) {
            throw new StorageProvisioningException("Failed to create volume, compute context is not initialized.");
        }

        StorageTemplate storageTemplate = this.cloud.getCloudStorage().getTemplates().get(templateName);

        String volumeName = storageTemplate.getNamePrefix() + System.currentTimeMillis();
        CreateVolumeOptions options = CreateVolumeOptions.Builder.name(volumeName).description(VOLUME_DESCRIPTION)
                .availabilityZone(getStorageZone(templateName));

        volume = volumeApi.get().create(storageTemplate.getSize(), options);

        try {
            waitForVolumeToReachStatus(Volume.Status.AVAILABLE, volumeApi, volume.getId(), endTime);
            volume = volumeApi.get().get(volume.getId());
            volumeDetails.setId(volume.getId());
            volumeDetails.setName(volume.getName());
            volumeDetails.setSize(volume.getSize());
            volumeDetails.setLocation(volume.getZone());
            logger.fine("Volume provisioned: " + volumeDetails.toString());
        } catch (final Exception e) {
            logger.log(Level.WARNING, "volume: " + volume.getId()
                    + " failed to start up correctly. Shutting it down." + " Error was: " + e.getMessage(), e);
            try {
                deleteVolume(region, volume.getId(), duration, timeUnit);
            } catch (final Exception e2) {
                logger.log(Level.WARNING, "Error while deleting volume: " + volume.getId() + ". Error was: "
                        + e.getMessage() + ". It may be leaking.", e);
            }
            if (e instanceof TimeoutException) {
                throw (TimeoutException) e;
            } else {
                throw new StorageProvisioningException(e);
            }
        }

        return volumeDetails;
    }

    @Override
    public void attachVolume(final String volumeId, final String device, final String machineIp,
            final long duration, final TimeUnit timeUnit) throws TimeoutException, StorageProvisioningException {

        final long endTime = System.currentTimeMillis() + timeUnit.toMillis(duration);
        NodeMetadata node = deployer.getServerWithIP(machineIp);
        if (node == null) {
            throw new StorageProvisioningException("Failed to attach volume " + volumeId + " to server. Server "
                    + "with ip: " + machineIp + " not found");
        }

        Optional<? extends VolumeAttachmentApi> volumeAttachmentApi = getAttachmentApi();
        Optional<? extends VolumeApi> volumeApi = getVolumeApi();

        if (!volumeApi.isPresent() || !volumeAttachmentApi.isPresent()) {
            throw new StorageProvisioningException(
                    "Failed to attach volume " + volumeId + ", Openstack API is not initialized.");
        }
        logger.fine("Attaching volume on Openstack");

        volumeAttachmentApi.get().attachVolumeToServerAsDevice(volumeId, node.getProviderId(), device);

        try {
            waitForVolumeToReachStatus(Volume.Status.IN_USE, volumeApi, volumeId, endTime);
            logger.fine("Volume " + volumeId + " attached successfully to machine : " + machineIp);
        } catch (final Exception e) {
            logger.log(Level.WARNING, "volume: " + volumeId + " failed to attach to machine " + machineIp
                    + ". Error was: " + e.getMessage(), e);
            try {
                detachVolume(region, volumeId, duration, timeUnit);
            } catch (final Exception e2) {
                logger.log(Level.WARNING, "Error while detaching volume: " + volumeId
                        + " after a failed attachment. Error was: " + e.getMessage() + ". It may be leaking.", e);
            }
            throw new StorageProvisioningException(e);
        }

    }

    @Override
    public void detachVolume(final String volumeId, final String machineIp, final long duration,
            final TimeUnit timeUnit) throws TimeoutException, StorageProvisioningException {

        final long endTime = System.currentTimeMillis() + timeUnit.toMillis(duration);
        NodeMetadata node = deployer.getServerWithIP(machineIp);
        if (node == null) {
            throw new StorageProvisioningException(
                    "Failed to detach volume " + volumeId + " from server " + machineIp + ". Server not found.");
        }

        //TODO might be faster without the location at all
        Optional<? extends VolumeApi> volumeApi = getVolumeApi();
        Optional<? extends VolumeAttachmentApi> volumeAttachmentApi = getAttachmentApi();

        if (!volumeApi.isPresent() || !volumeAttachmentApi.isPresent()) {
            throw new StorageProvisioningException(
                    "Failed to detach volume " + volumeId + ", Openstack API is not initialized.");
        }

        volumeAttachmentApi.get().detachVolumeFromServer(volumeId, node.getProviderId());

        try {
            waitForVolumeToReachStatus(Volume.Status.AVAILABLE, volumeApi, volumeId, endTime);
            logger.fine("Volume " + volumeId + " detached successfully from machine : " + machineIp);
        } catch (final Exception e) {
            logger.log(Level.WARNING, "volume: " + volumeId + " failed to detach from machine " + machineIp
                    + ". Error was: " + e.getMessage(), e);
            throw new StorageProvisioningException(e);
        }

    }

    @Override
    public void deleteVolume(final String location, final String volumeId, final long duration,
            final TimeUnit timeUnit) throws TimeoutException, StorageProvisioningException {

        Optional<? extends VolumeApi> volumeApi = getVolumeApi();
        if (!volumeApi.isPresent()) {
            throw new StorageProvisioningException(
                    "Failed to delete volume " + volumeId + ", Openstack API is not " + "initialized.");
        }

        if (!volumeApi.get().delete(volumeId)) {
            logger.log(Level.WARNING, "Error while deleting volume: " + volumeId + ".It may be leaking.");
        }

        // TODO: wait for state "Deleting"?

    }

    @Override
    public Set<VolumeDetails> listVolumes(final String machineIp, final long duration, final TimeUnit timeUnit)
            throws TimeoutException, StorageProvisioningException {

        Set<VolumeDetails> volumeDetailsSet = new HashSet<VolumeDetails>();

        NodeMetadata node = deployer.getServerWithIP(machineIp);
        if (node == null) {
            throw new StorageProvisioningException(
                    "Failed to list volumes attached to " + machineIp + ". Server not found");
        }

        Optional<? extends VolumeAttachmentApi> volumeAttachmentApi = getAttachmentApi();

        if (!volumeAttachmentApi.isPresent()) {
            throw new StorageProvisioningException("Failed to list volumes, Openstack API is not initialized.");
        }

        FluentIterable<? extends VolumeAttachment> volumesAttachmentsList = volumeAttachmentApi.get()
                .listAttachmentsOnServer(node.getProviderId());

        if (volumesAttachmentsList != null) {
            Volume volume;
            for (VolumeAttachment attachment : volumesAttachmentsList) {
                VolumeDetails details = new VolumeDetails();
                volume = getVolume(attachment.getVolumeId());
                details.setId(volume.getId());
                details.setName(volume.getName());
                details.setSize(volume.getSize());
                details.setLocation(volume.getZone());
                volumeDetailsSet.add(details);
            }
        }

        return volumeDetailsSet;
    }

    /**
     * @return .
     * @throws StorageProvisioningException .
     */
    @Override
    public Set<VolumeDetails> listAllVolumes() throws StorageProvisioningException {

        Set<VolumeDetails> volumeDetailsSet = new HashSet<VolumeDetails>();
        Optional<? extends VolumeApi> volumeApi = getVolumeApi();
        if (!volumeApi.isPresent()) {
            throw new StorageProvisioningException("Failed to list all volumes.");
        }

        FluentIterable<? extends Volume> volumesList = volumeApi.get().list();
        if (volumesList != null) {
            for (Volume volume : volumesList) {
                VolumeDetails details = new VolumeDetails();
                details.setId(volume.getId());
                details.setName(volume.getName());
                details.setSize(volume.getSize());
                details.setLocation(volume.getZone());
                volumeDetailsSet.add(details);
            }
        }

        return volumeDetailsSet;
    }

    private void waitForVolumeToReachStatus(final Volume.Status targetStatus,
            final Optional<? extends VolumeApi> volumeApi, final String volumeId, final long endTime)
            throws StorageProvisioningException, TimeoutException, InterruptedException {

        if (!volumeApi.isPresent()) {
            throw new StorageProvisioningException(
                    "Failed to get volume status, Openstack API is not initialized.");
        }

        logger.info("waiting for volume to reach status: " + targetStatus.toString());

        while (true) {
            final Volume volume = volumeApi.get().get(volumeId);
            if (volume != null) {
                Volume.Status volumeStatus = volume.getStatus();
                if (volumeStatus == targetStatus) {
                    //volume is ready
                    break;
                } else if (volumeStatus == Volume.Status.ERROR) {
                    throw new StorageProvisioningException(
                            "Storage volume management encountered an error. " + "Volume id: " + volumeId);
                }
                if (System.currentTimeMillis() > endTime) {
                    throw new TimeoutException("timeout while waiting for volume to reach status \" " + targetStatus
                            + "\". Current status is: " + volume.getStatus());
                }
            }

            Thread.sleep(VOLUME_POLLING_INTERVAL_MILLIS);
        }
    }

    /**
     * Publish a storage provisioning event occurred for the listeners registered on
     * this class.
     * 
     * @param eventName
     *            The name of the event (must be in the message bundle)
     * @param args
     *            Arguments that complement the event message
     */
    protected void publishEvent(final String eventName, final Object... args) {
        for (final ProvisioningDriverListener listener : this.eventsListenersList) {
            listener.onProvisioningEvent(eventName, args);
        }
    }

    private void initDeployer() {

        if (deployer != null) {
            return;
        }

        try {
            logger.fine("Creating JClouds context deployer for Openstack with user: " + cloud.getUser().getUser());
            final Properties props = new Properties();
            props.putAll(computeTemplate.getOverrides());

            deployer = new JCloudsDeployer(cloud.getProvider().getProvider(), cloud.getUser().getUser(),
                    cloud.getUser().getApiKey(), props);
        } catch (final Exception e) {
            publishEvent("connection_to_cloud_api_failed", cloud.getProvider().getProvider());
            throw new IllegalStateException("Failed to create cloud Deployer", e);
        }
    }

    @Override
    public void close() {
        if (novaContext != null) {
            novaContext.close();
        }
    }

    @Override
    public String getVolumeName(final String volumeId) throws StorageProvisioningException {
        Volume volume = getVolume(volumeId);
        if (volume == null) {
            throw new StorageProvisioningException(
                    "Failed to get volume with id: " + volumeId + ", volume not found");
        }

        return volume.getName();
    }

    /**
     * Returns the volume by its id if exists or null otherwise.
     * @param volumeId The of the requested volume
     * @return The Volume matching the given id
     * @throws StorageProvisioningException Indicates the storage APIs are not available
     */
    private Volume getVolume(final String volumeId) throws StorageProvisioningException {
        Optional<? extends VolumeApi> volumeApi = getVolumeApi();
        if (!volumeApi.isPresent()) {
            throw new StorageProvisioningException(
                    "Failed to get volume by id " + volumeId + ", Openstack API is not " + "initialized.");
        }

        final Volume volume = volumeApi.get().get(volumeId);

        return volume;
    }

    private String getStorageZone(final String templateName) throws IllegalArgumentException {
        String zone;
        Map<String, Object> customSettings = cloud.getCloudStorage().getTemplates().get(templateName).getCustom();

        if (customSettings != null) {
            Object zoneObj = customSettings.get(OPENSTACK_CUSTOM_VOLUME_ZONE);
            if (zoneObj instanceof String) {
                zone = (String) zoneObj;
                if (StringUtils.isBlank(zone)) {
                    throw new IllegalArgumentException("Storate template custom property is missing or empty: "
                            + OPENSTACK_CUSTOM_VOLUME_ZONE);
                }
            } else {
                throw new IllegalArgumentException("Storate template custom property \""
                        + OPENSTACK_CUSTOM_VOLUME_ZONE + "\" is missing or not a String");
            }
        } else {
            throw new IllegalArgumentException("Storate template is missing a \"custom\" section with the required "
                    + "property \"" + OPENSTACK_CUSTOM_VOLUME_ZONE + "\"");
        }

        return zone;
    }

    private Optional<? extends VolumeApi> getVolumeApi() {
        if (novaContext == null) {
            throw new IllegalStateException("Nova context is null");
        }

        return novaContext.getApi().getVolumeExtensionForZone(region);
    }

    private Optional<? extends VolumeAttachmentApi> getAttachmentApi() {
        if (novaContext == null) {
            throw new IllegalStateException("Nova context is null");
        }

        return novaContext.getApi().getVolumeAttachmentExtensionForZone(region);
    }

    private String getRegionFromHardwareId(final String hardwareId) {
        String region = "";
        if (hardwareId.indexOf("/") == -1) {
            logger.info("HardwareId is: " + hardwareId + ". It must be formatted " + "as region / profile id");
            throw new IllegalArgumentException(
                    "HardwareId is: " + hardwareId + ". It must be formatted " + "as region / profile id");
        }

        region = StringUtils.substringBefore(hardwareId, "/");
        if (StringUtils.isBlank(region)) {
            logger.info("HardwareId " + hardwareId + " is missing the region name. It must be formatted "
                    + "as region / profile id");
            throw new IllegalArgumentException(
                    "HardwareId is: " + hardwareId + ". It must be formatted " + "as region / profile id");
        }

        logger.fine("region: " + region);
        return region;
    }

}