org.dasein.cloud.google.compute.server.DiskSupport.java Source code

Java tutorial

Introduction

Here is the source code for org.dasein.cloud.google.compute.server.DiskSupport.java

Source

/**
 * Copyright (C) 2012-2015 Dell, Inc
 * See annotations for authorship information
 *
 * ====================================================================
 * 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.google.compute.server;

import java.io.IOException;
import java.util.*;

import javax.annotation.Nonnull;

import com.google.api.client.googleapis.json.GoogleJsonResponseException;
import com.google.api.services.compute.Compute;
import com.google.api.services.compute.model.*;
import com.google.api.services.compute.model.Snapshot;

import org.dasein.cloud.CloudErrorType;
import org.dasein.cloud.CloudException;
import org.dasein.cloud.InternalException;
import org.dasein.cloud.OperationNotSupportedException;
import org.dasein.cloud.Requirement;
import org.dasein.cloud.ResourceStatus;
import org.dasein.cloud.compute.*;
import org.dasein.cloud.google.GoogleOperationType;
import org.dasein.cloud.google.GoogleException;
import org.dasein.cloud.google.GoogleMethod;
import org.dasein.cloud.google.capabilities.GCEVolumeCapabilities;
import org.dasein.cloud.google.Google;
import org.dasein.cloud.util.APITrace;
import org.dasein.util.uom.storage.Gigabyte;
import org.dasein.util.uom.storage.Storage;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;
import org.apache.log4j.Logger;

/**
 * Implements the volume services supported in the Google API.
 * @author Drew Lyall
 * @version 2014.03 initial version
 * @since 2014.03
 */
public class DiskSupport extends AbstractVolumeSupport {
    static private final Logger logger = Google.getLogger(DiskSupport.class);

    private Google provider;

    public DiskSupport(Google provider) {
        super(provider);
        this.provider = provider;
    }

    @Override
    public void attach(@Nonnull String volumeId, @Nonnull String toServerId, @Nonnull String deviceId)
            throws InternalException, CloudException {
        APITrace.begin(getProvider(), "Volume.attach");

        String toServer = provider.getComputeServices().getVirtualMachineSupport().getVmNameFromId(toServerId);
        try {
            Compute gce = provider.getGoogleCompute();

            try {
                VirtualMachine vm = provider.getComputeServices().getVirtualMachineSupport()
                        .getVirtualMachine(toServer);
                if (null == vm) {
                    throw new CloudException("Virtual machine " + toServer + " does not exist.");
                }
                Volume volume = getVolume(volumeId);

                AttachedDisk attachedDisk = new AttachedDisk();
                attachedDisk.setSource(volume.getTag("contentLink"));
                attachedDisk.setType("PERSISTENT");
                attachedDisk.setMode("READ_WRITE");
                attachedDisk.setBoot(false);
                attachedDisk.setDeviceName(deviceId);

                Operation job = gce.instances().attachDisk(provider.getContext().getAccountNumber(),
                        vm.getProviderDataCenterId(), toServer, attachedDisk).execute();

                GoogleMethod method = new GoogleMethod(provider);
                if (!method.getOperationComplete(provider.getContext(), job, GoogleOperationType.ZONE_OPERATION, "",
                        vm.getProviderDataCenterId())) {
                    throw new CloudException("An error occurred attaching the disk: Operation Timedout");
                }
            } catch (IOException ex) {
                logger.error(ex.getMessage());
                if (ex.getClass() == GoogleJsonResponseException.class) {
                    GoogleJsonResponseException gjre = (GoogleJsonResponseException) ex;
                    throw new GoogleException(CloudErrorType.GENERAL, gjre.getStatusCode(), gjre.getContent(),
                            gjre.getDetails().getMessage());
                } else
                    throw new CloudException("An error occurred while attaching the disk: " + ex.getMessage());
            } catch (Exception ex) {
                throw new CloudException("An error occurred while attaching the disk: " + ex.getMessage());
            }
        } finally {
            APITrace.end();
        }
    }

    @Override
    public @Nonnull String createVolume(@Nonnull VolumeCreateOptions options)
            throws InternalException, CloudException {
        APITrace.begin(getProvider(), "Volume.createVolume");
        try {
            Compute gce = provider.getGoogleCompute();

            if (options.getFormat() == VolumeFormat.NFS)
                throw new OperationNotSupportedException("NFS volumes not supported by GCE");

            try {
                Disk disk = new Disk();
                disk.setName(getCapabilities().getVolumeNamingConstraints().convertToValidName(options.getName(),
                        Locale.US));
                disk.setSizeGb(options.getVolumeSize().longValue());
                disk.setZone(options.getDataCenterId());
                if (options.getSnapshotId() != null) {
                    //disk.setSourceSnapshotId(options.getSnapshotId()); // <- does not work
                    Snapshot snapshot = gce.snapshots()
                            .get(provider.getContext().getAccountNumber(), options.getSnapshotId()).execute();
                    disk.setSourceSnapshot(snapshot.getSelfLink());
                }
                Operation job = gce.disks()
                        .insert(provider.getContext().getAccountNumber(), options.getDataCenterId(), disk)
                        .execute();

                GoogleMethod method = new GoogleMethod(provider);
                return method.getOperationTarget(provider.getContext(), job, GoogleOperationType.ZONE_OPERATION, "",
                        options.getDataCenterId(), false);
            } catch (IOException ex) {
                logger.error(ex.getMessage());
                if (ex.getClass() == GoogleJsonResponseException.class) {
                    GoogleJsonResponseException gjre = (GoogleJsonResponseException) ex;
                    throw new GoogleException(CloudErrorType.GENERAL, gjre.getStatusCode(), gjre.getContent(),
                            gjre.getDetails().getMessage());
                } else
                    throw new CloudException("An error occurred while creating the Volume: " + ex.getMessage());
            }
        } finally {
            APITrace.end();
        }
    }

    @Override
    public void detach(@Nonnull String volumeId, boolean force) throws InternalException, CloudException {
        APITrace.begin(getProvider(), "Volume.detach");
        try {
            Volume volume = getVolume(volumeId);

            Compute gce = provider.getGoogleCompute();
            Operation job = null;
            try {
                if (null != volume.getProviderVirtualMachineId()) {
                    String vmName = provider.getComputeServices().getVirtualMachineSupport()
                            .getVmNameFromId(volume.getProviderVirtualMachineId());
                    job = gce.instances().detachDisk(provider.getContext().getAccountNumber(),
                            volume.getProviderDataCenterId(), vmName, volume.getDeviceId()).execute();
                    GoogleMethod method = new GoogleMethod(provider);
                    if (!method.getOperationComplete(provider.getContext(), job, GoogleOperationType.ZONE_OPERATION,
                            "", volume.getProviderDataCenterId())) {
                        throw new CloudException(
                                "An error occurred while detaching the volume: Operation Timedout");
                    }
                } // vm already deleted, silently do nothing in this case.
            } catch (IOException ex) {
                logger.error(ex.getMessage());
                if (ex.getClass() == GoogleJsonResponseException.class) {
                    GoogleJsonResponseException gjre = (GoogleJsonResponseException) ex;
                    throw new GoogleException(CloudErrorType.GENERAL, gjre.getStatusCode(), gjre.getContent(),
                            gjre.getDetails().getMessage());
                } else
                    throw new CloudException("An error occurred while detaching the volume: " + ex.getMessage());
            } catch (Exception ex) {
                throw new CloudException(ex);
            }
        } finally {
            APITrace.end();
        }
    }

    private transient volatile GCEVolumeCapabilities capabilities;

    @Override
    public @Nonnull GCEVolumeCapabilities getCapabilities() throws CloudException, InternalException {
        if (capabilities == null) {
            capabilities = new GCEVolumeCapabilities(provider);
        }
        return capabilities;
    }

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

    @Override
    public Storage<Gigabyte> getMaximumVolumeSize() throws InternalException, CloudException {
        return new Storage<Gigabyte>(10240, Storage.GIGABYTE);
    }

    @Override
    public @Nonnull Storage<Gigabyte> getMinimumVolumeSize() throws InternalException, CloudException {
        return new Storage<Gigabyte>(200, Storage.GIGABYTE);
    }

    @Override
    public @Nonnull String getProviderTermForVolume(@Nonnull Locale locale) {
        return "disk";
    }

    @Override
    public Volume getVolume(@Nonnull String volumeId) throws InternalException, CloudException {
        APITrace.begin(getProvider(), "Volume.getVolume");
        try {
            Compute gce = provider.getGoogleCompute();
            try {
                DiskAggregatedList diskList = gce.disks().aggregatedList(provider.getContext().getAccountNumber())
                        .setFilter("name eq " + volumeId).execute();
                Iterator<String> zones = diskList.getItems().keySet().iterator();
                while (zones.hasNext()) {
                    String zone = zones.next();
                    if (diskList.getItems().get(zone) != null && diskList.getItems().get(zone).getDisks() != null) {
                        for (Disk disk : diskList.getItems().get(zone).getDisks()) {
                            if (disk.getName().equals(volumeId))
                                return toVolume(disk);
                        }
                    }
                }
                return null;
            } catch (IOException ex) {
                logger.error(ex.getMessage());
                if (ex.getClass() == GoogleJsonResponseException.class) {
                    GoogleJsonResponseException gjre = (GoogleJsonResponseException) ex;
                    throw new GoogleException(CloudErrorType.GENERAL, gjre.getStatusCode(), gjre.getContent(),
                            gjre.getDetails().getMessage());
                } else
                    throw new CloudException("An error occurred getting the volume: " + ex.getMessage());
            }
        } finally {
            APITrace.end();
        }
    }

    @Override
    public @Nonnull Requirement getVolumeProductRequirement() throws InternalException, CloudException {
        return Requirement.NONE;
    }

    @Override
    public boolean isVolumeSizeDeterminedByProduct() throws InternalException, CloudException {
        return true;
    }

    @Override
    public @Nonnull Iterable<String> listPossibleDeviceIds(@Nonnull Platform platform)
            throws InternalException, CloudException {
        ArrayList<String> list = new ArrayList<String>();

        if (!platform.isWindows()) {
            list.add("sdf");
            list.add("sdg");
            list.add("sdh");
            list.add("sdi");
            list.add("sdj");
            list.add("sdk");
            list.add("sdl");
            list.add("sdm");
            list.add("sdn");
            list.add("sdo");
            list.add("sdp");
            list.add("sdq");
            list.add("sdr");
            list.add("sds");
            list.add("sdt");
        }
        return list;
    }

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

    @Override
    public @Nonnull Iterable<VolumeProduct> listVolumeProducts() throws InternalException, CloudException {
        //TODO: Could implement something here - GCE doesn't charge for iops
        return Collections.emptyList();
    }

    @Override
    public @Nonnull Iterable<ResourceStatus> listVolumeStatus() throws InternalException, CloudException {
        List<ResourceStatus> status = new ArrayList<ResourceStatus>();

        Iterable<Volume> volumes = listVolumes();
        for (Volume volume : volumes) {
            VolumeState state = volume.getCurrentState();
            ResourceStatus resStatus = new ResourceStatus(volume.getProviderVolumeId(), state);
            status.add(resStatus);
        }
        return status;
    }

    @Override
    public @Nonnull Iterable<Volume> listVolumes() throws InternalException, CloudException {
        return listVolumes(null);
    }

    @Override
    public @Nonnull Iterable<Volume> listVolumes(VolumeFilterOptions options)
            throws InternalException, CloudException {
        APITrace.begin(getProvider(), "Volume.listVolumes");
        try {
            ArrayList<Volume> volumes = new ArrayList<Volume>();
            Compute gce = provider.getGoogleCompute();
            try {
                DiskAggregatedList diskList = gce.disks().aggregatedList(provider.getContext().getAccountNumber())
                        .execute();
                Iterator<String> zones = diskList.getItems().keySet().iterator();
                while (zones.hasNext()) {
                    String zone = zones.next();
                    if (diskList.getItems().get(zone) != null && diskList.getItems().get(zone).getDisks() != null) {
                        for (Disk disk : diskList.getItems().get(zone).getDisks()) {
                            Volume volume = toVolume(disk);
                            if (volume != null && (options == null || options.matches(volume))) {
                                volumes.add(volume);
                            }
                        }
                    }
                }
                return volumes;
            } catch (IOException ex) {
                logger.error(ex.getMessage());
                if (ex.getClass() == GoogleJsonResponseException.class) {
                    GoogleJsonResponseException gjre = (GoogleJsonResponseException) ex;
                    throw new GoogleException(CloudErrorType.GENERAL, gjre.getStatusCode(), gjre.getContent(),
                            gjre.getDetails().getMessage());
                } else
                    throw new CloudException("An error occurred listing Volumes: " + ex.getMessage());
            }
        } finally {
            APITrace.end();
        }
    }

    @Override
    public boolean isSubscribed() throws CloudException, InternalException {
        return true;
    }

    @Override
    public void remove(@Nonnull String volumeId) throws InternalException, CloudException {
        APITrace.begin(getProvider(), "Volume.remove");
        try {
            Compute gce = provider.getGoogleCompute();
            Volume volume = getVolume(volumeId);
            if (null != volume) {
                try {
                    Operation job = gce.disks().delete(provider.getContext().getAccountNumber(),
                            volume.getProviderDataCenterId(), volume.getProviderVolumeId()).execute();
                    GoogleMethod method = new GoogleMethod(provider);
                    if (!method.getOperationComplete(provider.getContext(), job, GoogleOperationType.ZONE_OPERATION,
                            "", volume.getProviderDataCenterId())) {
                        throw new CloudException("An error occurred while deleting the Volume: Operation Timedout");
                    }
                } catch (IOException ex) {
                    logger.error(ex.getMessage());
                    if (ex.getClass() == GoogleJsonResponseException.class) {
                        GoogleJsonResponseException gjre = (GoogleJsonResponseException) ex;
                        throw new GoogleException(CloudErrorType.GENERAL, gjre.getStatusCode(), gjre.getContent(),
                                gjre.getDetails().getMessage());
                    } else
                        throw new CloudException("An error occurred while deleting the volume: " + ex.getMessage());
                }
            }
        } finally {
            APITrace.end();
        }
    }

    public Volume toVolume(Disk disk) throws InternalException, CloudException {
        Volume volume = new Volume();
        volume.setProviderVolumeId(disk.getName());
        volume.setName(disk.getName());
        volume.setMediaLink(disk.getSelfLink());
        if (disk.getDescription() == null)
            volume.setDescription(disk.getName());
        else
            volume.setDescription(disk.getDescription());
        volume.setProviderRegionId(provider.getDataCenterServices()
                .getRegionFromZone(disk.getZone().substring(disk.getZone().lastIndexOf("/") + 1)));

        DateTimeFormatter fmt = ISODateTimeFormat.dateTime();
        DateTime dt = DateTime.parse(disk.getCreationTimestamp(), fmt);
        volume.setCreationTimestamp(dt.toDate().getTime());
        volume.setProviderDataCenterId(disk.getZone().substring(disk.getZone().lastIndexOf("/") + 1));
        if (disk.getStatus().equals("DONE") || disk.getStatus().equals("READY")) {
            volume.setCurrentState(VolumeState.AVAILABLE);
        } else if (disk.getStatus().equals("FAILED")) {
            volume.setCurrentState(VolumeState.ERROR);
        } else {
            volume.setCurrentState(VolumeState.PENDING);
        }
        volume.setType(VolumeType.HDD);
        volume.setFormat(VolumeFormat.BLOCK);
        volume.setSize(new Storage<Gigabyte>(disk.getSizeGb(), Storage.GIGABYTE));
        if (disk.getSourceSnapshotId() != null && !disk.getSourceSnapshotId().equals(""))
            volume.setProviderSnapshotId(disk.getSourceSnapshotId());
        volume.setTag("contentLink", disk.getSelfLink());

        //In order to list volumes with the attached VM, VMs must be listed. Doing it for now but, ick!
        Compute gce = provider.getGoogleCompute();
        try {
            //We only care about instances in the same zone as the disk
            InstanceList list = gce.instances().list(provider.getContext().getAccountNumber(),
                    disk.getZone().substring(disk.getZone().lastIndexOf("/") + 1)).execute();
            if (list.getItems() != null) {
                for (Instance instance : list.getItems()) {
                    for (AttachedDisk attachedDisk : instance.getDisks()) {
                        if (attachedDisk.getSource().equals(disk.getSelfLink())) {
                            volume.setDeviceId(attachedDisk.getDeviceName());
                            volume.setProviderVirtualMachineId(instance.getName() + "_" + instance.getId());
                            break;
                        }
                    }
                }
            }
        } catch (IOException ex) {
            logger.error(ex.getMessage());
            return null;
        }
        return volume;
    }
}