Support for local data disk (part 1)

Following changes are made:
- Create disk offering API now takes an extra parameter to denote storage type (local or shared). This is similar to storage type in service offering.
- Create/delete of data volume on local storage
- Attach/detach for local data volumes. Re-attach is allowed as long as vm host and data volume storage pool host is same.
- Migration of VM instance is not supported if it uses local root or data volumes.
- Migrate is not supported for local volumes.

Reviewed-by: Abhi
This commit is contained in:
Koushik Das 2012-07-25 15:16:42 +05:30
parent c4dff1e204
commit 266b8e5ee8
8 changed files with 96 additions and 37 deletions

View File

@ -53,6 +53,9 @@ public class CreateDiskOfferingCmd extends BaseCmd {
@Parameter(name=ApiConstants.DOMAIN_ID, type=CommandType.LONG, description="the ID of the containing domain, null for public offerings")
private Long domainId;
@Parameter(name=ApiConstants.STORAGE_TYPE, type=CommandType.STRING, description="the storage type of the disk offering. Values are local and shared.")
private String storageType = "shared";
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
@ -80,6 +83,11 @@ public class CreateDiskOfferingCmd extends BaseCmd {
public Long getDomainId(){
return domainId;
}
public String getStorageType() {
return storageType;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////

View File

@ -89,7 +89,7 @@ public interface ConfigurationManager extends ConfigurationService, Manager {
* @param isCustomized
* @return newly created disk offering
*/
DiskOfferingVO createDiskOffering(Long domainId, String name, String description, Long numGibibytes, String tags, boolean isCustomized);
DiskOfferingVO createDiskOffering(Long domainId, String name, String description, Long numGibibytes, String tags, boolean isCustomized, boolean localStorageRequired);
/**
* Creates a new pod

View File

@ -1853,7 +1853,7 @@ public class ConfigurationManagerImpl implements ConfigurationManager, Configura
@Override
@ActionEvent(eventType = EventTypes.EVENT_DISK_OFFERING_CREATE, eventDescription = "creating disk offering")
public DiskOfferingVO createDiskOffering(Long domainId, String name, String description, Long numGibibytes, String tags, boolean isCustomized) {
public DiskOfferingVO createDiskOffering(Long domainId, String name, String description, Long numGibibytes, String tags, boolean isCustomized, boolean localStorageRequired) {
long diskSize = 0;// special case for custom disk offerings
if (numGibibytes != null && (numGibibytes <= 0)) {
throw new InvalidParameterValueException("Please specify a disk size of at least 1 Gb.", null);
@ -1871,6 +1871,7 @@ public class ConfigurationManagerImpl implements ConfigurationManager, Configura
tags = cleanupTags(tags);
DiskOfferingVO newDiskOffering = new DiskOfferingVO(domainId, name, description, diskSize, tags, isCustomized);
newDiskOffering.setUseLocalStorage(localStorageRequired);
UserContext.current().setEventDetails("Disk offering id=" + newDiskOffering.getId());
DiskOfferingVO offering = _diskOfferingDao.persist(newDiskOffering);
if (offering != null) {
@ -1900,7 +1901,17 @@ public class ConfigurationManagerImpl implements ConfigurationManager, Configura
throw new InvalidParameterValueException("Disksize is required for non-customized disk offering", null);
}
return createDiskOffering(domainId, name, description, numGibibytes, tags, isCustomized);
boolean localStorageRequired = false;
String storageType = cmd.getStorageType();
if (storageType != null) {
if (storageType.equalsIgnoreCase(ServiceOffering.StorageType.local.toString())) {
localStorageRequired = true;
} else if (!storageType.equalsIgnoreCase(ServiceOffering.StorageType.shared.toString())) {
throw new InvalidParameterValueException("Invalid storage type " + storageType + " specified, valid types are: 'local' and 'shared'", null);
}
}
return createDiskOffering(domainId, name, description, numGibibytes, tags, isCustomized, localStorageRequired);
}
@Override

View File

@ -2997,7 +2997,11 @@ public class StorageManagerImpl implements StorageManager, Manager, ClusterManag
StoragePool destPool = _storagePoolDao.findById(storagePoolId);
if (destPool == null) {
throw new InvalidParameterValueException("Faild to find the destination storage pool: " + storagePoolId);
throw new InvalidParameterValueException("Failed to find the destination storage pool: " + storagePoolId);
}
if (!volumeOnSharedStoragePool(vol)) {
throw new InvalidParameterValueException("Migration of volume from local storage pool is not supported");
}
List<Volume> vols = new ArrayList<Volume>();

View File

@ -28,8 +28,10 @@ import com.cloud.deploy.DeploymentPlan;
import com.cloud.deploy.DeploymentPlanner.ExcludeList;
import com.cloud.server.StatsCollector;
import com.cloud.storage.StoragePool;
import com.cloud.storage.StoragePoolHostVO;
import com.cloud.storage.StoragePoolVO;
import com.cloud.storage.VMTemplateVO;
import com.cloud.storage.Volume;
import com.cloud.user.Account;
import com.cloud.vm.DiskProfile;
import com.cloud.vm.VirtualMachine;
@ -71,11 +73,23 @@ public class FirstFitStoragePoolAllocator extends AbstractStoragePoolAllocator {
s_logger.debug("Looking for pools in dc: " + dcId + " pod:" + podId + " cluster:" + clusterId);
}
List<StoragePoolVO> pools = _storagePoolDao.findPoolsByTags(dcId, podId, clusterId, dskCh.getTags(), null);
List<StoragePoolVO> pools = null;
if (!dskCh.useLocalStorage() || vmProfile.getVirtualMachine().getHostId() == null) {
pools = _storagePoolDao.findPoolsByTags(dcId, podId, clusterId, dskCh.getTags(), !dskCh.useLocalStorage());
} else {
pools = new ArrayList<StoragePoolVO>();
List<StoragePoolHostVO> hostPools = _poolHostDao.listByHostId(vmProfile.getVirtualMachine().getHostId());
for (StoragePoolHostVO hostPool: hostPools) {
StoragePoolVO pool = _storagePoolDao.findById(hostPool.getPoolId());
if (pool != null && pool.isLocal()) {
pools.add(pool);
}
}
}
if (pools.size() == 0) {
if (s_logger.isDebugEnabled()) {
s_logger.debug("No storage pools available for allocation, returning");
}
if (s_logger.isDebugEnabled()) {
s_logger.debug("No storage pools available for " + (dskCh.useLocalStorage() ? "local" : "shared") + " volume allocation, returning");
}
return suitablePools;
}

View File

@ -575,11 +575,6 @@ public class UserVmManagerImpl implements UserVmManager, UserVmService, Manager
}
}
//If the volume is Ready, check that the volume is stored on shared storage
if (!(Volume.State.Allocated.equals(volume.getState()) || Volume.State.UploadOp.equals(volume.getState())) && !_storageMgr.volumeOnSharedStoragePool(volume)) {
throw new InvalidParameterValueException("Please specify a volume that has been created on a shared storage pool.", null);
}
if ( !(Volume.State.Allocated.equals(volume.getState()) || Volume.State.Ready.equals(volume.getState()) || Volume.State.UploadOp.equals(volume.getState())) ) {
throw new InvalidParameterValueException("Volume state must be in Allocated, Ready or in Uploaded state", null);
}
@ -672,11 +667,11 @@ public class UserVmManagerImpl implements UserVmManager, UserVmService, Manager
StoragePoolVO vmRootVolumePool = _storagePoolDao.findById(rootVolumeOfVm.getPoolId());
DiskOfferingVO volumeDiskOffering = _diskOfferingDao.findById(volume.getDiskOfferingId());
String[] volumeTags = volumeDiskOffering.getTagsArray();
boolean isVolumeOnSharedPool = _storageMgr.volumeOnSharedStoragePool(volume);
StoragePoolVO sourcePool = _storagePoolDao.findById(volume.getPoolId());
List<StoragePoolVO> sharedVMPools = _storagePoolDao.findPoolsByTags(vmRootVolumePool.getDataCenterId(), vmRootVolumePool.getPodId(), vmRootVolumePool.getClusterId(), volumeTags, true);
List<StoragePoolVO> matchingVMPools = _storagePoolDao.findPoolsByTags(vmRootVolumePool.getDataCenterId(), vmRootVolumePool.getPodId(), vmRootVolumePool.getClusterId(), volumeTags, isVolumeOnSharedPool);
boolean moveVolumeNeeded = true;
if (sharedVMPools.size() == 0) {
if (matchingVMPools.size() == 0) {
String poolType;
if (vmRootVolumePool.getClusterId() != null) {
poolType = "cluster";
@ -687,15 +682,18 @@ public class UserVmManagerImpl implements UserVmManager, UserVmService, Manager
}
throw new CloudRuntimeException("There are no storage pools in the VM's " + poolType + " with all of the volume's tags (" + volumeDiskOffering.getTags() + ").");
} else {
long sourcePoolId = sourcePool.getId();
Long sourcePoolDcId = sourcePool.getDataCenterId();
Long sourcePoolPodId = sourcePool.getPodId();
Long sourcePoolClusterId = sourcePool.getClusterId();
for (StoragePoolVO vmPool : sharedVMPools) {
for (StoragePoolVO vmPool : matchingVMPools) {
long vmPoolId = vmPool.getId();
Long vmPoolDcId = vmPool.getDataCenterId();
Long vmPoolPodId = vmPool.getPodId();
Long vmPoolClusterId = vmPool.getClusterId();
if (sourcePoolDcId == vmPoolDcId && sourcePoolPodId == vmPoolPodId && sourcePoolClusterId == vmPoolClusterId) {
if (sourcePoolDcId == vmPoolDcId && sourcePoolPodId == vmPoolPodId && sourcePoolClusterId == vmPoolClusterId
&& (isVolumeOnSharedPool || sourcePoolId == vmPoolId)) {
moveVolumeNeeded = false;
break;
}
@ -703,11 +701,15 @@ public class UserVmManagerImpl implements UserVmManager, UserVmService, Manager
}
if (moveVolumeNeeded) {
// Move the volume to a storage pool in the VM's zone, pod, or cluster
try {
volume = _storageMgr.moveVolume(volume, vmRootVolumePool.getDataCenterId(), vmRootVolumePool.getPodId(), vmRootVolumePool.getClusterId(), dataDiskHyperType);
} catch (ConcurrentOperationException e) {
throw new CloudRuntimeException(e.toString());
if (isVolumeOnSharedPool) {
// Move the volume to a storage pool in the VM's zone, pod, or cluster
try {
volume = _storageMgr.moveVolume(volume, vmRootVolumePool.getDataCenterId(), vmRootVolumePool.getPodId(), vmRootVolumePool.getClusterId(), dataDiskHyperType);
} catch (ConcurrentOperationException e) {
throw new CloudRuntimeException(e.toString());
}
} else {
throw new CloudRuntimeException("Moving a local data volume " + volume + " is not allowed");
}
}
}
@ -811,11 +813,6 @@ public class UserVmManagerImpl implements UserVmManager, UserVmService, Manager
throw new InvalidParameterValueException("The specified volume is not attached to a VM.", null);
}
// Check that the volume is stored on shared storage
if (volume.getState() != Volume.State.Allocated && !_storageMgr.volumeOnSharedStoragePool(volume)) {
throw new InvalidParameterValueException("Please specify a volume that has been created on a shared storage pool.", null);
}
// Check that the VM is in the correct state
UserVmVO vm = _vmDao.findById(vmId);
if (vm.getState() != State.Running && vm.getState() != State.Stopped && vm.getState() != State.Destroyed) {
@ -3244,6 +3241,25 @@ public class UserVmManagerImpl implements UserVmManager, UserVmService, Manager
}
private boolean isVMUsingLocalStorage(VMInstanceVO vm)
{
boolean usesLocalStorage = false;
ServiceOfferingVO svcOffering = _serviceOfferingDao.findById(vm.getServiceOfferingId());
if (svcOffering.getUseLocalStorage()) {
usesLocalStorage = true;
} else {
List<VolumeVO> volumes = _volsDao.findByInstanceAndType(vm.getId(), Volume.Type.DATADISK);
for (VolumeVO vol : volumes) {
DiskOfferingVO diskOffering = _diskOfferingDao.findById(vol.getDiskOfferingId());
if (diskOffering.getUseLocalStorage()) {
usesLocalStorage = true;
break;
}
}
}
return usesLocalStorage;
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_VM_MIGRATE, eventDescription = "migrating VM", async = true)
public VirtualMachine migrateVirtualMachine(Long vmId, Host destinationHost) throws ResourceUnavailableException, ConcurrentOperationException, ManagementServerException, VirtualMachineMigrationException {
@ -3276,8 +3292,7 @@ public class UserVmManagerImpl implements UserVmManager, UserVmService, Manager
throw new InvalidParameterValueException("Unsupported Hypervisor Type for VM migration, we support XenServer/VMware/KVM only", null);
}
ServiceOfferingVO svcOffering = _serviceOfferingDao.findById(vm.getServiceOfferingId());
if (svcOffering.getUseLocalStorage()) {
if (isVMUsingLocalStorage(vm)) {
if (s_logger.isDebugEnabled()) {
s_logger.debug(vm + " is using Local Storage, cannot migrate this VM.");
}

View File

@ -769,6 +769,15 @@
label: 'label.description',
validation: { required: true }
},
storageType: {
label: 'label.storage.type',
select: function(args) {
var items = [];
items.push({id: 'shared', description: 'shared'});
items.push({id: 'local', description: 'local'});
args.response.success({data: items});
}
},
isCustomized: {
label: 'label.custom.disk.size',
isBoolean: true,
@ -816,7 +825,7 @@
var array1 = [];
array1.push("&name=" + args.data.name);
array1.push("&displaytext=" + todb(args.data.description));
array1.push("&storageType=" + todb(args.data.storageType));
array1.push("&customized=" + (args.data.isCustomized=="on"));
if(args.$form.find('.form-item[rel=disksize]').css("display") != "none")
array1.push("&disksize=" + args.data.disksize);

View File

@ -1323,18 +1323,16 @@
}
else { //jsonObj.type == "DATADISK"
if (jsonObj.virtualmachineid != null) {
if (jsonObj.storagetype == "shared" && (jsonObj.vmstate == "Running" || jsonObj.vmstate == "Stopped" || jsonObj.vmstate == "Destroyed")) {
if (jsonObj.vmstate == "Running" || jsonObj.vmstate == "Stopped" || jsonObj.vmstate == "Destroyed") {
allowedActions.push("detachDisk");
}
}
else { // Disk not attached
allowedActions.push("remove");
if(jsonObj.state == "Ready" && isAdmin()) {
allowedActions.push("remove");
if(jsonObj.state == "Ready" && isAdmin() && jsonObj.storagetype == "shared") {
allowedActions.push("migrateToAnotherStorage");
}
if (jsonObj.storagetype == "shared") {
allowedActions.push("attachDisk");
}
allowedActions.push("attachDisk");
}
}
}