Java tutorial
/** * 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; } }