From 266b8e5ee83afef8971e77a98f01c7a37da082a6 Mon Sep 17 00:00:00 2001 From: Koushik Das Date: Wed, 25 Jul 2012 15:16:42 +0530 Subject: [PATCH] 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 --- .../api/commands/CreateDiskOfferingCmd.java | 8 +++ .../configuration/ConfigurationManager.java | 2 +- .../ConfigurationManagerImpl.java | 15 ++++- .../com/cloud/storage/StorageManagerImpl.java | 6 +- .../FirstFitStoragePoolAllocator.java | 22 +++++-- .../src/com/cloud/vm/UserVmManagerImpl.java | 59 ++++++++++++------- ui/scripts/configuration.js | 11 +++- ui/scripts/storage.js | 10 ++-- 8 files changed, 96 insertions(+), 37 deletions(-) diff --git a/api/src/com/cloud/api/commands/CreateDiskOfferingCmd.java b/api/src/com/cloud/api/commands/CreateDiskOfferingCmd.java index 4e356f43bb9..4d0aa469d23 100755 --- a/api/src/com/cloud/api/commands/CreateDiskOfferingCmd.java +++ b/api/src/com/cloud/api/commands/CreateDiskOfferingCmd.java @@ -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/////////////////// ///////////////////////////////////////////////////// diff --git a/server/src/com/cloud/configuration/ConfigurationManager.java b/server/src/com/cloud/configuration/ConfigurationManager.java index 29de7bf4ba2..6e43895ad2d 100644 --- a/server/src/com/cloud/configuration/ConfigurationManager.java +++ b/server/src/com/cloud/configuration/ConfigurationManager.java @@ -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 diff --git a/server/src/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/com/cloud/configuration/ConfigurationManagerImpl.java index 346313470b7..c9fc65cb477 100755 --- a/server/src/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/com/cloud/configuration/ConfigurationManagerImpl.java @@ -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 diff --git a/server/src/com/cloud/storage/StorageManagerImpl.java b/server/src/com/cloud/storage/StorageManagerImpl.java index 146b8d3bf47..9fb8a081f2c 100755 --- a/server/src/com/cloud/storage/StorageManagerImpl.java +++ b/server/src/com/cloud/storage/StorageManagerImpl.java @@ -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 vols = new ArrayList(); diff --git a/server/src/com/cloud/storage/allocator/FirstFitStoragePoolAllocator.java b/server/src/com/cloud/storage/allocator/FirstFitStoragePoolAllocator.java index 02ab3c1ef6b..365fb6a7007 100644 --- a/server/src/com/cloud/storage/allocator/FirstFitStoragePoolAllocator.java +++ b/server/src/com/cloud/storage/allocator/FirstFitStoragePoolAllocator.java @@ -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 pools = _storagePoolDao.findPoolsByTags(dcId, podId, clusterId, dskCh.getTags(), null); + List pools = null; + if (!dskCh.useLocalStorage() || vmProfile.getVirtualMachine().getHostId() == null) { + pools = _storagePoolDao.findPoolsByTags(dcId, podId, clusterId, dskCh.getTags(), !dskCh.useLocalStorage()); + } else { + pools = new ArrayList(); + List 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; } diff --git a/server/src/com/cloud/vm/UserVmManagerImpl.java b/server/src/com/cloud/vm/UserVmManagerImpl.java index 65558fe6d7b..a94933fd899 100755 --- a/server/src/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/com/cloud/vm/UserVmManagerImpl.java @@ -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 sharedVMPools = _storagePoolDao.findPoolsByTags(vmRootVolumePool.getDataCenterId(), vmRootVolumePool.getPodId(), vmRootVolumePool.getClusterId(), volumeTags, true); + List 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 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."); } diff --git a/ui/scripts/configuration.js b/ui/scripts/configuration.js index 9f9d354f8fe..2da4d5f8ecd 100644 --- a/ui/scripts/configuration.js +++ b/ui/scripts/configuration.js @@ -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); diff --git a/ui/scripts/storage.js b/ui/scripts/storage.js index ce85087bd7c..42244a0fdb0 100644 --- a/ui/scripts/storage.js +++ b/ui/scripts/storage.js @@ -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"); } } }