Java tutorial
/** * Copyright (C) 2012 enStratus Networks 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.azure.compute.disk; import org.apache.commons.codec.binary.Base64; import org.apache.log4j.Logger; import org.dasein.cloud.*; import org.dasein.cloud.azure.Azure; import org.dasein.cloud.azure.AzureConfigException; import org.dasein.cloud.azure.AzureMethod; import org.dasein.cloud.azure.compute.vm.AzureVM; import org.dasein.cloud.compute.AbstractVolumeSupport; import org.dasein.cloud.compute.Platform; import org.dasein.cloud.compute.VirtualMachine; import org.dasein.cloud.compute.Volume; import org.dasein.cloud.compute.VolumeCreateOptions; import org.dasein.cloud.compute.VolumeFilterOptions; import org.dasein.cloud.compute.VolumeFormat; import org.dasein.cloud.compute.VolumeProduct; import org.dasein.cloud.compute.VolumeState; import org.dasein.cloud.compute.VolumeSupport; import org.dasein.cloud.compute.VolumeType; import org.dasein.cloud.dc.DataCenter; import org.dasein.cloud.identity.ServiceAction; 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; import javax.annotation.Nonnegative; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.servlet.http.HttpServletResponse; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Collections; import java.util.Locale; /** * Support for Azure block storage disks via the Dasein Cloud volume API. * <p>Created by George Reese: 6/19/12 9:25 AM</p> * @author George Reese (george.reese@imaginary.com) * @version 2012-06 * @since 2012-06 */ public class AzureDisk implements VolumeSupport { static private final Logger logger = Azure.getLogger(AzureDisk.class); static private final String DISK_SERVICES = "/services/disks"; static private final String HOSTED_SERVICES = "/services/hostedservices"; private Azure provider; public AzureDisk(Azure provider) { this.provider = provider; } @Override public void attach(@Nonnull String volumeId, @Nonnull String toServer, @Nonnull String device) throws InternalException, CloudException { if (logger.isTraceEnabled()) { logger.trace("ENTER: " + AzureDisk.class.getName() + ".attach(" + volumeId + "," + toServer + "," + device + ")"); } try { ProviderContext ctx = provider.getContext(); if (ctx == null) { throw new AzureConfigException("No context was specified for this request"); } if (device != null) { if (device.startsWith("/dev/")) { device = device.substring(5); } } Volume disk; StringBuilder xml = new StringBuilder(); if (volumeId != null) { disk = getVolume(volumeId); if (disk == null) { throw new InternalException("Can not find the source snapshot !"); } xml.append( "<DataVirtualHardDisk xmlns=\"http://schemas.microsoft.com/windowsazure\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">"); xml.append("<HostCaching>ReadWrite</HostCaching>"); xml.append("<DiskName>" + disk.getName() + "</DiskName>"); if (device != null && isWithinDeviceList(device)) { xml.append("<Lun>" + device + "</Lun>"); } xml.append("<LogicalDiskSizeInGB>" + disk.getSizeInGigabytes() + "</LogicalDiskSizeInGB>"); xml.append("<MediaLink>" + disk.getMediaLink() + "</MediaLink>"); xml.append("</DataVirtualHardDisk>"); } else { //throw new InternalException("volumeId is null !"); //dmayne: assume we are attaching a new empty disk? xml.append( "<DataVirtualHardDisk xmlns=\"http://schemas.microsoft.com/windowsazure\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">"); xml.append("<HostCaching>ReadWrite</HostCaching>"); if (device != null && isWithinDeviceList(device)) { xml.append("<Lun>" + device + "</Lun>"); } //todo actually get the disk size required xml.append("<LogicalDiskSizeInGB>" + "1" + "</LogicalDiskSizeInGB>"); xml.append("</DataVirtualHardDisk>"); } //dsn2260-dsn2260Role-0-20120619044615 VirtualMachine server = provider.getComputeServices().getVirtualMachineSupport() .getVirtualMachine(toServer); String resourceDir = HOSTED_SERVICES + "/" + server.getTag("serviceName") + "/deployments" + "/" + server.getTag("deploymentName") + "/roles" + "/" + server.getTag("roleName") + "/DataDisks"; AzureMethod method = new AzureMethod(provider); if (logger.isDebugEnabled()) { try { method.parseResponse(xml.toString(), false); } catch (Exception e) { logger.warn("Unable to parse outgoing XML locally: " + e.getMessage()); logger.warn("XML:"); logger.warn(xml.toString()); } } method.post(ctx.getAccountNumber(), resourceDir, xml.toString()); } finally { if (logger.isTraceEnabled()) { logger.trace("EXIT: " + AzureDisk.class.getName() + ".attach()"); } } } @Override public @Nonnull String createVolume(@Nonnull VolumeCreateOptions options) throws InternalException, CloudException { throw new OperationNotSupportedException("Azure does not support creating standalone volumes"); /*if( logger.isTraceEnabled() ) { logger.trace("ENTER: " + AzureDisk.class.getName() + ".createVolume(" + options + ")"); } try { ProviderContext ctx = provider.getContext(); if( ctx == null ) { throw new AzureConfigException("No context was specified for this request"); } String fromVolumeId = options.getSnapshotId(); Volume disk ; if(fromVolumeId != null){ disk = getVolume(fromVolumeId); if(disk == null ){ throw new InternalException("Can not find the source snapshot !"); } }else{ throw new InternalException("Azure needs a source snapshot Id to create a new disk volume !"); } AzureMethod method = new AzureMethod(provider); StringBuilder xml = new StringBuilder(); String label; try { label = new String(Base64.encodeBase64(options.getName().getBytes("utf-8"))); } catch( UnsupportedEncodingException e ) { throw new InternalException(e); } xml.append("<Disk xmlns=\"http://schemas.microsoft.com/windowsazure\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">"); Platform platform = disk.getGuestOperatingSystem(); if(platform.isWindows()){ xml.append("<OS>Windows</OS>"); }else{ xml.append("<OS>Linux</OS>"); } xml.append("<Label>" + label + "</Label>"); xml.append("<MediaLink>" + disk.getMediaLink()+"</MediaLink>"); xml.append("<Name>" + options.getName() + "</Name>"); xml.append("</Disk>"); if( logger.isDebugEnabled() ) { try { method.parseResponse(xml.toString(), false); } catch( Exception e ) { logger.warn("Unable to parse outgoing XML locally: " + e.getMessage()); logger.warn("XML:"); logger.warn(xml.toString()); } } String requestId = method.post(ctx.getAccountNumber(), DISK_SERVICES, xml.toString()); Volume v = null; if (requestId != null) { int httpCode = method.getOperationStatus(requestId); while (httpCode == -1) { httpCode = method.getOperationStatus(requestId); } if (httpCode == HttpServletResponse.SC_OK) { try { v = getVolume(options.getName()); return v.getProviderVolumeId(); } catch( Throwable ignore ) { } } } } finally { if( logger.isTraceEnabled() ) { logger.trace("EXIT: " + AzureDisk.class.getName() + ".launch()"); } } return null; */ } @Override public void detach(@Nonnull String volumeId, boolean force) throws InternalException, CloudException { if (logger.isTraceEnabled()) { logger.trace("ENTER: " + AzureDisk.class.getName() + ".detach(" + volumeId + ")"); } try { ProviderContext ctx = provider.getContext(); if (ctx == null) { throw new AzureConfigException("No context was specified for this request"); } Volume disk; if (volumeId != null) { disk = getVolume(volumeId); if (disk == null) { throw new InternalException("Can not find the source snapshot !"); } } else { throw new InternalException("volumeId is null !"); } String providerVirtualMachineId = disk.getProviderVirtualMachineId(); VirtualMachine vm = null; if (providerVirtualMachineId != null) { vm = provider.getComputeServices().getVirtualMachineSupport() .getVirtualMachine(providerVirtualMachineId); } if (vm == null) { logger.trace("Sorry, the disk is not attached to the VM with id " + providerVirtualMachineId + " or the VM id is not in the desired format !!!"); throw new InternalException("Sorry, the disk is not attached to the VM with id " + providerVirtualMachineId + " or the VM id is not in the desired format !!!"); } String lun = getDiskLun(disk.getProviderVolumeId(), providerVirtualMachineId); if (lun == null) { logger.trace("Can not identify the lun number"); throw new InternalException( "logical unit number of disk is null, detach operation can not be continue!"); } String resourceDir = HOSTED_SERVICES + "/" + vm.getTag("serviceName") + "/deployments" + "/" + vm.getTag("deploymentName") + "/roles" + "/" + vm.getTag("roleName") + "/DataDisks" + "/" + lun; AzureMethod method = new AzureMethod(provider); method.invoke("DELETE", ctx.getAccountNumber(), resourceDir, null); } finally { if (logger.isTraceEnabled()) { logger.trace("EXIT: " + AzureDisk.class.getName() + ".detach()"); } } } private String getDiskLun(String providerVolumeId, String providerVirtualMachineId) throws InternalException, CloudException { AzureMethod method = new AzureMethod(provider); VirtualMachine vm = provider.getComputeServices().getVirtualMachineSupport() .getVirtualMachine(providerVirtualMachineId); String resourceDir = HOSTED_SERVICES + "/" + vm.getTag("serviceName") + "/deployments" + "/" + vm.getTag("deploymentName"); Document doc = method.getAsXML(provider.getContext().getAccountNumber(), resourceDir); if (doc == null) { return null; } NodeList entries = doc.getElementsByTagName("DataVirtualHardDisk"); if (entries.getLength() < 1) { return null; } for (int i = 0; i < entries.getLength(); i++) { Node detail = entries.item(i); NodeList attributes = detail.getChildNodes(); String diskName = null, lunValue = null; for (int j = 0; j < attributes.getLength(); j++) { Node attribute = attributes.item(j); if (attribute.getNodeType() == Node.TEXT_NODE) continue; if (attribute.getNodeName().equalsIgnoreCase("DiskName") && attribute.hasChildNodes()) { diskName = attribute.getFirstChild().getNodeValue().trim(); } else if (attribute.getNodeName().equalsIgnoreCase("Lun") && attribute.hasChildNodes()) { if (diskName != null && diskName.equalsIgnoreCase(providerVolumeId)) { lunValue = attribute.getFirstChild().getNodeValue().trim(); } } } if (diskName != null && diskName.equalsIgnoreCase(providerVolumeId)) { if (lunValue == null) { lunValue = "0"; } return lunValue; } } return null; } @Override public int getMaximumVolumeCount() throws InternalException, CloudException { return 16; } @Override public @Nullable Storage<Gigabyte> getMaximumVolumeSize() throws InternalException, CloudException { return new Storage<Gigabyte>(1024, Storage.GIGABYTE); } @Override public @Nonnull Storage<Gigabyte> getMinimumVolumeSize() throws InternalException, CloudException { return new Storage<Gigabyte>(1, Storage.GIGABYTE); } @Override public @Nonnull String getProviderTermForVolume(@Nonnull Locale locale) { return "disk"; } @Override public @Nullable Volume getVolume(@Nonnull String volumeId) throws InternalException, CloudException { //To change body of implemented methods use File | Settings | File Templates. ArrayList<Volume> list = (ArrayList<Volume>) listVolumes(); if (list == null) return null; for (Volume disk : list) { if (disk.getProviderVolumeId().equals(volumeId)) { return disk; } } return null; } @Nonnull @Override public Requirement getVolumeProductRequirement() throws InternalException, CloudException { return Requirement.NONE; //To change body of implemented methods use File | Settings | File Templates. } @Override public boolean isVolumeSizeDeterminedByProduct() throws InternalException, CloudException { return false; //To change body of implemented methods use File | Settings | File Templates. } @Override public @Nonnull Iterable<String> listPossibleDeviceIds(@Nonnull Platform platform) throws InternalException, CloudException { //To change body of implemented methods use File | Settings | File Templates. ArrayList<String> list = new ArrayList<String>(); for (int i = 0; i < this.getMaximumVolumeCount(); i++) { list.add(String.valueOf(i)); } return list; } @Nonnull @Override public Iterable<VolumeFormat> listSupportedFormats() throws InternalException, CloudException { return Collections.singletonList(VolumeFormat.BLOCK); } @Nonnull @Override public Iterable<VolumeProduct> listVolumeProducts() throws InternalException, CloudException { return Collections.emptyList(); } @Nonnull @Override public Iterable<ResourceStatus> listVolumeStatus() throws InternalException, CloudException { ProviderContext ctx = provider.getContext(); if (ctx == null) { throw new AzureConfigException("No context was specified for this request"); } AzureMethod method = new AzureMethod(provider); Document doc = method.getAsXML(ctx.getAccountNumber(), DISK_SERVICES); NodeList entries = doc.getElementsByTagName("Disk"); ArrayList<ResourceStatus> list = new ArrayList<ResourceStatus>(); for (int i = 0; i < entries.getLength(); i++) { Node entry = entries.item(i); ResourceStatus status = toStatus(ctx, entry); if (status != null) { list.add(status); } } return list; } @Override public @Nonnull Iterable<Volume> listVolumes() throws InternalException, CloudException { //To change body of implemented methods use File | Settings | File Templates. ProviderContext ctx = provider.getContext(); if (ctx == null) { throw new AzureConfigException("No context was specified for this request"); } AzureMethod method = new AzureMethod(provider); Document doc = method.getAsXML(ctx.getAccountNumber(), DISK_SERVICES); NodeList entries = doc.getElementsByTagName("Disk"); ArrayList<Volume> disks = new ArrayList<Volume>(); for (int i = 0; i < entries.getLength(); i++) { Node entry = entries.item(i); Volume disk = toVolume(ctx, entry); if (disk != null) { disks.add(disk); } } return disks; } private boolean isWithinDeviceList(String device) throws InternalException, CloudException { ArrayList<String> list = (ArrayList<String>) listPossibleDeviceIds(Platform.UNIX); for (String id : list) { if (id.equals(device)) { return true; } } return false; } @Override public boolean isSubscribed() throws CloudException, InternalException { return true; //To change body of implemented methods use File | Settings | File Templates. } @Override public void remove(@Nonnull String volumeId) throws InternalException, CloudException { if (logger.isTraceEnabled()) { logger.trace("ENTER: " + AzureDisk.class.getName() + ".remove(" + volumeId + ")"); } try { ProviderContext ctx = provider.getContext(); if (ctx == null) { throw new AzureConfigException("No context was specified for this request"); } AzureMethod method = new AzureMethod(provider); method.invoke("DELETE", ctx.getAccountNumber(), DISK_SERVICES + "/" + volumeId + "?comp=media", null); } finally { if (logger.isTraceEnabled()) { logger.trace("EXIT: " + AzureDisk.class.getName() + ".remove()"); } } } @Override public @Nonnull String[] mapServiceAction(@Nonnull ServiceAction action) { return new String[0]; } private @Nullable Volume toVolume(@Nonnull ProviderContext ctx, @Nullable Node volumeNode) throws InternalException, CloudException { if (volumeNode == null) { return null; } String regionId = ctx.getRegionId(); if (regionId == null) { throw new AzureConfigException("No region ID was specified for this request"); } Volume disk = new Volume(); disk.setProviderRegionId(regionId); disk.setCurrentState(VolumeState.AVAILABLE); disk.setType(VolumeType.HDD); boolean mediaLocationFound = false; NodeList attributes = volumeNode.getChildNodes(); for (int i = 0; i < attributes.getLength(); i++) { Node attribute = attributes.item(i); if (attribute.getNodeType() == Node.TEXT_NODE) continue; if (attribute.getNodeName().equalsIgnoreCase("AttachedTo") && attribute.hasChildNodes()) { NodeList attachAttributes = attribute.getChildNodes(); String hostedServiceName = null; String deploymentName = null; String vmRoleName = null; for (int k = 0; k < attachAttributes.getLength(); k++) { Node attach = attachAttributes.item(k); if (attach.getNodeType() == Node.TEXT_NODE) continue; if (attach.getNodeName().equalsIgnoreCase("HostedServiceName") && attach.hasChildNodes()) { hostedServiceName = attach.getFirstChild().getNodeValue().trim(); } else if (attach.getNodeName().equalsIgnoreCase("DeploymentName") && attach.hasChildNodes()) { deploymentName = attach.getFirstChild().getNodeValue().trim(); } else if (attach.getNodeName().equalsIgnoreCase("RoleName") && attach.hasChildNodes()) { vmRoleName = attach.getFirstChild().getNodeValue().trim(); } } if (hostedServiceName != null && deploymentName != null && vmRoleName != null) { disk.setProviderVirtualMachineId(hostedServiceName + ":" + deploymentName + ":" + vmRoleName); } } else if (attribute.getNodeName().equalsIgnoreCase("OS") && attribute.hasChildNodes()) { disk.setGuestOperatingSystem(Platform.guess(attribute.getFirstChild().getNodeValue().trim())); } // disk may have either affinity group or location depending on how storage account is set up else if (attribute.getNodeName().equalsIgnoreCase("AffinityGroup") && attribute.hasChildNodes()) { //get the region for this affinity group String affinityGroup = attribute.getFirstChild().getNodeValue().trim(); if (affinityGroup != null && !affinityGroup.equals("")) { DataCenter dc = provider.getDataCenterServices().getDataCenter(affinityGroup); if (dc.getRegionId().equals(disk.getProviderRegionId())) { disk.setProviderDataCenterId(dc.getProviderDataCenterId()); mediaLocationFound = true; } else { // not correct region/datacenter return null; } } } else if (attribute.getNodeName().equalsIgnoreCase("Location") && attribute.hasChildNodes()) { if (!mediaLocationFound && !regionId.equals(attribute.getFirstChild().getNodeValue().trim())) { return null; } } else if (attribute.getNodeName().equalsIgnoreCase("LogicalDiskSizeInGB") && attribute.hasChildNodes()) { disk.setSize(Storage.valueOf(Integer.valueOf(attribute.getFirstChild().getNodeValue().trim()), "gigabyte")); } else if (attribute.getNodeName().equalsIgnoreCase("MediaLink") && attribute.hasChildNodes()) { disk.setMediaLink(attribute.getFirstChild().getNodeValue().trim()); } else if (attribute.getNodeName().equalsIgnoreCase("Name") && attribute.hasChildNodes()) { disk.setProviderVolumeId(attribute.getFirstChild().getNodeValue().trim()); } else if (attribute.getNodeName().equalsIgnoreCase("SourceImageName") && attribute.hasChildNodes()) { disk.setProviderSnapshotId(attribute.getFirstChild().getNodeValue().trim()); } } if (disk.getGuestOperatingSystem() == null) { disk.setGuestOperatingSystem(Platform.UNKNOWN); } if (disk.getName() == null) { disk.setName(disk.getProviderVolumeId()); } if (disk.getDescription() == null) { disk.setDescription(disk.getName()); } if (disk.getProviderDataCenterId() == null) { DataCenter dc = provider.getDataCenterServices().listDataCenters(regionId).iterator().next(); disk.setProviderDataCenterId(dc.getProviderDataCenterId()); } return disk; } private @Nullable ResourceStatus toStatus(@Nonnull ProviderContext ctx, @Nullable Node volumeNode) throws InternalException, CloudException { if (volumeNode == null) { return null; } String regionId = ctx.getRegionId(); if (regionId == null) { throw new AzureConfigException("No region ID was specified for this request"); } String id = ""; boolean mediaLocationFound = false; NodeList attributes = volumeNode.getChildNodes(); for (int i = 0; i < attributes.getLength(); i++) { Node attribute = attributes.item(i); if (attribute.getNodeType() == Node.TEXT_NODE) continue; if (attribute.getNodeName().equalsIgnoreCase("Name") && attribute.hasChildNodes()) { id = attribute.getFirstChild().getNodeValue().trim(); } else if (attribute.getNodeName().equalsIgnoreCase("AffinityGroup") && attribute.hasChildNodes()) { //get the region for this affinity group String affinityGroup = attribute.getFirstChild().getNodeValue().trim(); if (affinityGroup != null && !affinityGroup.equals("")) { DataCenter dc = provider.getDataCenterServices().getDataCenter(affinityGroup); if (dc.getRegionId().equals(regionId)) { mediaLocationFound = true; } else { // not correct region/datacenter return null; } } } else if (attribute.getNodeName().equalsIgnoreCase("Location") && attribute.hasChildNodes()) { if (!mediaLocationFound && !regionId.equals(attribute.getFirstChild().getNodeValue().trim())) { return null; } } } ResourceStatus status = new ResourceStatus(id, VolumeState.AVAILABLE); return status; } }