org.dasein.cloud.cloudstack.compute.Volumes Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of dasein-cloud-cloudstack Show documentation
Show all versions of dasein-cloud-cloudstack Show documentation
Implements the Dasein Cloud API for Cloud.com Cloudstack-based public and private clouds.
/**
* 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();
}
}
}