All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.dasein.cloud.cloudstack.compute.Volumes Maven / Gradle / Ivy

Go to download

Implements the Dasein Cloud API for Cloud.com Cloudstack-based public and private clouds.

There is a newer version: 2015.10.5
Show newest version
/**
 * Copyright (C) 2009-2015 Dell, 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 org.dasein.cloud.cloudstack.compute;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import org.apache.log4j.Logger;
import org.dasein.cloud.CloudException;
import org.dasein.cloud.InternalException;
import org.dasein.cloud.OperationNotSupportedException;
import org.dasein.cloud.ProviderContext;
import org.dasein.cloud.Requirement;
import org.dasein.cloud.ResourceStatus;
import org.dasein.cloud.Tag;
import org.dasein.cloud.cloudstack.CSCloud;
import org.dasein.cloud.cloudstack.CSException;
import org.dasein.cloud.cloudstack.CSMethod;
import org.dasein.cloud.cloudstack.CSServiceProvider;
import org.dasein.cloud.cloudstack.Param;
import org.dasein.cloud.compute.AbstractVolumeSupport;
import org.dasein.cloud.compute.Platform;
import org.dasein.cloud.compute.Snapshot;
import org.dasein.cloud.compute.VirtualMachine;
import org.dasein.cloud.compute.VmState;
import org.dasein.cloud.compute.Volume;
import org.dasein.cloud.compute.VolumeCapabilities;
import org.dasein.cloud.compute.VolumeCreateOptions;
import org.dasein.cloud.compute.VolumeFormat;
import org.dasein.cloud.compute.VolumeProduct;
import org.dasein.cloud.compute.VolumeState;
import org.dasein.cloud.compute.VolumeType;
import org.dasein.cloud.util.APITrace;
import org.dasein.cloud.util.Cache;
import org.dasein.cloud.util.CacheLevel;
import org.dasein.util.CalendarWrapper;
import org.dasein.util.uom.storage.Gigabyte;
import org.dasein.util.uom.storage.Storage;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class Volumes extends AbstractVolumeSupport {
    static private final Logger logger = Logger.getLogger(Volumes.class);
    
    static private final String ATTACH_VOLUME = "attachVolume";
    static private final String CREATE_VOLUME = "createVolume";
    static private final String DELETE_VOLUME = "deleteVolume";
    static private final String DETACH_VOLUME = "detachVolume";
    static private final String LIST_DISK_OFFERINGS     = "listDiskOfferings";

    static private final String LIST_VOLUMES  = "listVolumes";

    static public class DiskOffering {
        public String id;
        public long diskSize;
        public String name;
        public String description;
        public String type;

        public String toString() {return "DiskOffering ["+id+"] of size "+diskSize;}
    }
    
    private CSCloud provider;
    
    Volumes(CSCloud provider) {
        super(provider);
        this.provider = provider;
    }
    
    @Override
    public void attach(@Nonnull String volumeId, @Nonnull String serverId, @Nullable String deviceId) throws InternalException, CloudException {
        APITrace.begin(getProvider(), "Volume.attach");
        try {
            if( logger.isInfoEnabled() ) {
                logger.info("attaching " + volumeId + " to " + serverId + " as " + deviceId);
            }
            VirtualMachine vm = provider.getComputeServices().getVirtualMachineSupport().getVirtualMachine(serverId);

            if( vm == null ) {
                throw new CloudException("No such virtual machine: " + serverId);
            }
            long timeout = System.currentTimeMillis() + (CalendarWrapper.MINUTE * 10L);

            while( timeout > System.currentTimeMillis() ) {
                if( VmState.RUNNING.equals(vm.getCurrentState()) || VmState.STOPPED.equals(vm.getCurrentState()) ) {
                    break;
                }
                try { Thread.sleep(15000L); }
                catch( InterruptedException ignore ) { }
                try { vm = provider.getComputeServices().getVirtualMachineSupport().getVirtualMachine(serverId); }
                catch( Throwable ignore ) { }
                if( vm == null ) {
                    throw new CloudException("Virtual machine " + serverId + " disappeared waiting for it to enter an attachable state");
                }
            }
            List params = new ArrayList();
            params.add(new Param("id", volumeId));
            params.add(new Param("virtualMachineId", serverId));

            if( deviceId != null ) {
                deviceId = toDeviceNumber(deviceId);
                if( logger.isDebugEnabled() ) {
                    logger.debug("Device mapping is: " + deviceId);
                }
                params.add(new Param("deviceId", deviceId));
            }
            Document doc = new CSMethod(provider).get(ATTACH_VOLUME, params);

            if( doc == null ) {
                throw new CloudException("No such volume or server");
            }
            provider.waitForJob(doc, "Attach Volume");
        }
        finally {
            APITrace.end();
        }
    }

    @Override
    public @Nonnull String createVolume(@Nonnull VolumeCreateOptions options) throws InternalException, CloudException {
        APITrace.begin(getProvider(), "Volume.createVolume");
        try {
            if( options.getFormat().equals(VolumeFormat.NFS) || !provider.hasApi("createVolume")) {
                throw new OperationNotSupportedException("NFS volumes are not currently supported in " + getProvider().getCloudName());
            }
            String snapshotId = options.getSnapshotId();
            String productId = options.getVolumeProductId();
            VolumeProduct product = null;

            if( productId != null ) {
                for( VolumeProduct prd : listVolumeProducts() ) {
                    if( productId.equals(prd.getProviderProductId()) ) {
                        product = prd;
                        break;
                    }
                }
            }
            Storage size;

            if( snapshotId == null ) {
                if( product == null ) {
                    size = options.getVolumeSize();
                    if( size.intValue() < getMinimumVolumeSize().intValue() ) {
                        size = getMinimumVolumeSize();
                    }
                    Iterable products = listVolumeProducts();
                    VolumeProduct best = null;
                    VolumeProduct custom = null;

                    for( VolumeProduct p : products ) {
                        Storage s = p.getVolumeSize();

                        if( s  == null || s.intValue() == 0 ) {
                            if (custom == null) {
                                custom = p;
                            }
                            continue;
                        }
                        long currentSize = s.getQuantity().longValue();

                        s = (best == null ? null : best.getVolumeSize());

                        long bestSize = (s == null ? 0L : s.getQuantity().longValue());

                        if( size.longValue() > 0L && size.longValue() == currentSize ) {
                            product = p;
                            break;
                        }
                        if( best == null ) {
                            best = p;
                        }
                        else if( bestSize > 0L || currentSize > 0L ) {
                            if( size.longValue() > 0L ) {
                                if( bestSize < size.longValue() && bestSize >0L && (currentSize > size.longValue() || currentSize > bestSize) ) {
                                    best = p;
                                }
                                else if( bestSize > size.longValue() && currentSize > size.longValue() && currentSize < bestSize ) {
                                    best = p;
                                }
                            }
                            else if( currentSize > 0L && currentSize < bestSize ) {
                                best = p;
                            }
                        }
                    }
                    if( product == null ) {
                        if (custom != null) {
                            product = custom;
                        }
                        else {
                            product = best;
                        }
                    }
                }
                else {
                    size = product.getVolumeSize();
                    if( size == null || size.intValue() < 1 ) {
                        size = options.getVolumeSize();
                    }
                }
                if( product == null && size.longValue() < 1L ) {
                    throw new CloudException("No offering matching " + options.getVolumeProductId());
                }
            }
            else {
                Snapshot snapshot = provider.getComputeServices().getSnapshotSupport().getSnapshot(snapshotId);

                if( snapshot == null ) {
                    throw new CloudException("No such snapshot: " + snapshotId);
                }
                int s = snapshot.getSizeInGb();

                if( s < 1 || s < getMinimumVolumeSize().intValue() ) {
                    size = getMinimumVolumeSize();
                }
                else {
                    size = new Storage(s, Storage.GIGABYTE);
                }
            }
            List params = new ArrayList();
            params.add(new Param("name", options.getName()));
            params.add(new Param("zoneId", getContext().getRegionId()));

            if( product == null && snapshotId == null ) {
                /*params = new Param[] {
                        new Param("name", options.getName()),
                        new Param("zoneId", ctx.getRegionId()),
                        new Param("size", String.valueOf(size.longValue()))
                }; */
                throw new CloudException("A suitable snapshot or disk offering could not be found to pass to CloudStack createVolume request");
            }
            else if( snapshotId != null ) {
                params.add(new Param("snapshotId", snapshotId));
                params.add(new Param("size", String.valueOf(size.longValue())));
            }
            else {
                Storage s = product.getVolumeSize();
                params.add(new Param("diskOfferingId", product.getProviderProductId()));

                if( s == null || s.intValue() < 1 ) {
                    params.add(new Param("size", String.valueOf(size.longValue())));
                }
            }

            Document doc = new CSMethod(provider).get(CREATE_VOLUME, params);
            NodeList matches = doc.getElementsByTagName("volumeid"); // v2.1
            String volumeId = null;

            if( matches.getLength() > 0 ) {
                volumeId = matches.item(0).getFirstChild().getNodeValue();
            }
            if( volumeId == null ) {
                matches = doc.getElementsByTagName("id"); // v2.2
                if( matches.getLength() > 0 ) {
                    volumeId = matches.item(0).getFirstChild().getNodeValue();
                }
            }
            if( volumeId == null ) {
                matches = doc.getElementsByTagName("jobid"); // v4.1
                if( matches.getLength() > 0 ) {
                    volumeId = matches.item(0).getFirstChild().getNodeValue();
                }
            }
            if( volumeId == null ) {
                throw new CloudException("Failed to create volume");
            }
            Document responseDoc = provider.waitForJob(doc, "Create Volume");
            if (responseDoc != null){
                NodeList nodeList = responseDoc.getElementsByTagName("volume");
                if (nodeList.getLength() > 0) {
                    Node volume = nodeList.item(0);
                    NodeList attributes = volume.getChildNodes();
                    for (int i = 0; i 0 ) {
                            value = attribute.getFirstChild().getNodeValue();
                        }
                        else {
                            value = null;
                        }
                        if (name.equalsIgnoreCase("id")) {
                            volumeId = value;
                            break;
                        }
                    }
                }
            }
            
            // Set tags
            List tags = new ArrayList();
            Map meta = options.getMetaData();
            for( Map.Entry entry : meta.entrySet() ) {
            	if( entry.getKey().equalsIgnoreCase("name") || entry.getKey().equalsIgnoreCase("description") ) {
            		continue;
            	}
            	if (entry.getValue() != null && !entry.getValue().equals("")) {
            		tags.add(new Tag(entry.getKey(), entry.getValue().toString()));
            	}
            }
            tags.add(new Tag("Name", options.getName()));
            tags.add(new Tag("Description", options.getDescription()));
            provider.createTags(new String[] { volumeId }, "Volume", tags.toArray(new Tag[tags.size()]));
            return volumeId;
        }
        finally {
            APITrace.end();
        }
    }

    @Override
    public void detach(@Nonnull String volumeId, boolean force) throws InternalException, CloudException {
        APITrace.begin(getProvider(), "Volume.detach");
        try {
            CSMethod method = new CSMethod(provider);
            Document doc = method.get(DETACH_VOLUME, new Param("id", volumeId));

            provider.waitForJob(doc, "Detach Volume");
        }
        finally {
            APITrace.end();
        }
    }

    private transient volatile CSVolumeCapabilities capabilities;
    @Override
    public VolumeCapabilities getCapabilities() throws CloudException, InternalException {
        if( capabilities == null ) {
            capabilities = new CSVolumeCapabilities(provider);
        }
        return capabilities;
    }

    @Override
    public int getMaximumVolumeCount() throws InternalException, CloudException {
        return -2;
    }

    @Override
    public @Nonnull Storage getMaximumVolumeSize() throws InternalException, CloudException {
        return new Storage(5000, Storage.GIGABYTE);
    }

    @Override
    public @Nonnull Storage getMinimumVolumeSize() throws InternalException, CloudException {
        return new Storage(1, Storage.GIGABYTE);
    }

    @Nonnull Collection getDiskOfferings() throws InternalException, CloudException {
        final Document doc = new CSMethod(provider).get(LIST_DISK_OFFERINGS);
        List offerings = new ArrayList();
        NodeList matches = doc.getElementsByTagName("diskoffering");
        
        for( int i=0; i 0 ) {
                    value = n.getFirstChild().getNodeValue();
                }
                else {
                    value = null;
                }
                if( n.getNodeName().equals("id") ) {
                    offering.id = value;
                }
                else if( n.getNodeName().equals("disksize") ) {
                    offering.diskSize = Long.parseLong(value);
                }
                else if( n.getNodeName().equalsIgnoreCase("name") ) {
                    offering.name = value;
                }
                else if( n.getNodeName().equalsIgnoreCase("displayText") ) {
                    offering.description = value;
                }
                else if( n.getNodeName().equalsIgnoreCase("storagetype") ) {
                    offering.type = value;
                }
            }
            if( offering.id != null ) {
                if( offering.name == null ) {
                    if( offering.diskSize > 0 ) {
                        offering.name = offering.diskSize + " GB";
                    }
                    else {
                        offering.name = "Custom #" + offering.id;
                    }
                }
                if( offering.description == null ) {
                    offering.description = offering.name;
                }
                offerings.add(offering);
            }
        }
        return offerings;
    }
    
    @Override
    public @Nonnull String getProviderTermForVolume(@Nonnull Locale locale) {
        return "volume";
    }

    @Nullable
    String getRootVolumeId(@Nonnull String serverId) throws InternalException, CloudException {
        final Volume volume = getRootVolume(serverId);
        
        return (volume == null ? null : volume.getProviderVolumeId());
    }

    private @Nullable Volume getRootVolume(@Nonnull String serverId) throws InternalException, CloudException {
        final Document doc = new CSMethod(provider).get(LIST_VOLUMES, new Param("virtualMachineId", serverId));
        NodeList matches = doc.getElementsByTagName("volume");
        
        for( int i=0; i listPossibleDeviceIds(@Nonnull Platform platform) throws InternalException, CloudException {
        Cache cache;

        if( platform.isWindows() ) {
            cache = Cache.getInstance(getProvider(), "windowsDeviceIds", String.class, CacheLevel.CLOUD);
        }
        else {
            cache = Cache.getInstance(getProvider(), "unixDeviceIds", String.class, CacheLevel.CLOUD);
        }
        Iterable ids = cache.get(getContext());

        if( ids == null ) {
            ArrayList list = new ArrayList();

            if( platform.isWindows() ) {
                list.add("hde");
                list.add("hdf");
                list.add("hdg");
                list.add("hdh");
                list.add("hdi");
                list.add("hdj");
            }
            else {
                list.add("/dev/xvdc");
                list.add("/dev/xvde");
                list.add("/dev/xvdf");
                list.add("/dev/xvdg");
                list.add("/dev/xvdh");
                list.add("/dev/xvdi");
                list.add("/dev/xvdj");
            }
            ids = Collections.unmodifiableList(list);
            cache.put(getContext(), ids);
        }
        return ids;
    }

    @Override
    public @Nonnull Iterable listSupportedFormats() throws InternalException, CloudException {
        return Collections.singletonList(VolumeFormat.BLOCK);
    }

    @Override
    public @Nonnull Iterable listVolumeProducts() throws InternalException, CloudException {
        APITrace.begin(getProvider(), "Volume.listVolumeProducts");
        try {
            Cache cache = Cache.getInstance(getProvider(), "volumeProducts", VolumeProduct.class, CacheLevel.REGION_ACCOUNT);
            Iterable products = cache.get(getContext());

            if( products == null ) {
                ArrayList list = new ArrayList();

                for( DiskOffering offering : getDiskOfferings() ) {
                    VolumeProduct p = toProduct(offering);

                    if( p != null && (!provider.getServiceProvider().equals(CSServiceProvider.DEMOCLOUD) || "local".equals(offering.type)) ) {
                        list.add(p);
                    }
                }
                products = Collections.unmodifiableList(list);
                cache.put(getContext(), products);
            }
            return products;
        }
        finally {
            APITrace.end();
        }
    }

    @Override
    public @Nonnull Iterable listVolumeStatus() throws InternalException, CloudException {
        APITrace.begin(getProvider(), "Volume.listVolumeStatus");
        try {
            CSMethod method = new CSMethod(provider);
            Document doc = method.get(LIST_VOLUMES, new Param("zoneId", getContext().getRegionId()));
            List volumes = new ArrayList();

            int numPages = 1;
            NodeList nodes = doc.getElementsByTagName("count");
            Node n = nodes.item(0);
            if (n != null) {
                String value = n.getFirstChild().getNodeValue().trim();
                int count = Integer.parseInt(value);
                numPages = count/500;
                int remainder = count % 500;
                if (remainder > 0) {
                    numPages++;
                }
            }

            for (int page = 1; page <= numPages; page++) {
                if (page > 1) {
                    String nextPage = String.valueOf(page);
                    doc = method.get(LIST_VOLUMES, new Param("zoneId", getContext().getRegionId()), new Param("pagesize", "500"), new Param("page", nextPage));
                }
                NodeList matches = doc.getElementsByTagName("volume");

                for( int i=0; i listVolumes() throws InternalException, CloudException {
        APITrace.begin(getProvider(), "Volume.listVolumes");
        try {
            return listVolumes(false);
        }
        finally {
            APITrace.end();
        }
    }
     
    private @Nonnull Collection listVolumes(boolean rootOnly) throws InternalException, CloudException {
        CSMethod method = new CSMethod(provider);
        Document doc = method.get(LIST_VOLUMES, new Param("zoneId", getContext().getRegionId()));
        ArrayList volumes = new ArrayList();
        int numPages = 1;
        NodeList nodes = doc.getElementsByTagName("count");
        Node n = nodes.item(0);
        if (n != null) {
            String value = n.getFirstChild().getNodeValue().trim();
            int count = Integer.parseInt(value);
            numPages = count/500;
            int remainder = count % 500;
            if (remainder > 0) {
                numPages++;
            }
        }

        for (int page = 1; page <= numPages; page++) {
            if (page > 1) {
                String nextPage = String.valueOf(page);
                doc = method.get(LIST_VOLUMES, new Param("zoneId", getContext().getRegionId()), new Param("pagesize", "500"), new Param("page", nextPage));
            }
            NodeList matches = doc.getElementsByTagName("volume");

            for( int i=0; i(offering.diskSize, Storage.GIGABYTE));
        }
    }

    private @Nullable ResourceStatus toStatus(@Nullable Node node) throws InternalException, CloudException {
        if( node == null ) {
            return null;
        }
        NodeList attributes = node.getChildNodes();
        VolumeState volumeState = null;
        String volumeId = null;

        for( int i=0; i(size, Storage.GIGABYTE));
                }
                else if( name.equals("state") && attribute.hasChildNodes() ) {
                    String state = attribute.getFirstChild().getNodeValue();

                    if( state == null ) {
                        volume.setCurrentState(VolumeState.PENDING);
                    }
                    else if( state.equalsIgnoreCase("created") || state.equalsIgnoreCase("ready")
                            || state.equalsIgnoreCase("allocated") || state.equalsIgnoreCase("uploaded")) {
                        volume.setCurrentState(VolumeState.AVAILABLE);
                    }
                    else {
                        logger.warn("DEBUG: Unknown state for CloudStack volume: " + state);
                        volume.setCurrentState(VolumeState.PENDING);
                    }
                }
                else if( name.equals("created") && attribute.hasChildNodes() ) {
                    String date = attribute.getFirstChild().getNodeValue();
                    DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); //2009-02-03T05:26:32.612278
                    
                    try {
                        volume.setCreationTimestamp(df.parse(date).getTime());
                    }
                    catch( ParseException e ) {
                        volume.setCreationTimestamp(0L);
                    }
                }
            }
        }
        if( !root && rootOnly ) {
            return null;
        }
        if( volume.getProviderVolumeId() == null ) {
            return null;
        }
        if( volumeName == null ) {
            volume.setName(volume.getProviderVolumeId());
        }
        else {
            volume.setName(volumeName);
        }
        if( description == null ) {
            volume.setDescription(volume.getName());
        }
        else {
            volume.setDescription(description);
        }
        if( offeringId != null ) {
            volume.setProviderProductId(offeringId);
        }
        volume.setProviderRegionId(provider.getContext().getRegionId());
        volume.setProviderDataCenterId(provider.getContext().getRegionId());

        volume.setDeviceId(deviceNumber);
        volume.setRootVolume(root);
        volume.setType(VolumeType.HDD);
        if( root ) {
            volume.setGuestOperatingSystem(Platform.guess(volume.getName() + " " + volume.getDescription()));
        }
        return volume;
    }
    
    @Override
    public void setTags(@Nonnull String volumeId, @Nonnull Tag... tags) throws CloudException, InternalException {
    	setTags(new String[] { volumeId }, tags);
    }
    
    @Override
    public void setTags(@Nonnull String[] volumeIds, @Nonnull Tag... tags) throws CloudException, InternalException {
    	APITrace.begin(getProvider(), "Volume.setTags");
    	try {
    		removeTags(volumeIds);
    		provider.createTags(volumeIds, "Volume", tags);
    	}
    	finally {
    		APITrace.end();
    	}
    }
    
    @Override
    public void updateTags(@Nonnull String volumeId, @Nonnull Tag... tags) throws CloudException, InternalException {
    	updateTags(new String[] { volumeId }, tags);
    }
    
    @Override
    public void updateTags(@Nonnull String[] volumeIds, @Nonnull Tag... tags) throws CloudException, InternalException {
    	APITrace.begin(getProvider(), "Volume.updateTags");
    	try {
    		provider.updateTags(volumeIds, "Volume", tags);
    	}
    	finally {
    		APITrace.end();
    	}
    }
    
    @Override
    public void removeTags(@Nonnull String volumeId, @Nonnull Tag... tags) throws CloudException, InternalException {
    	removeTags(new String[] { volumeId }, tags);
    }
    
    @Override
    public void removeTags(@Nonnull String[] volumeIds, @Nonnull Tag... tags) throws CloudException, InternalException {
    	APITrace.begin(getProvider(), "Volume.removeTags");
    	try {
    		provider.removeTags(volumeIds, "Volume", tags);
    	}
    	finally {
    		APITrace.end();
    	}
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy