mirror of https://github.com/apache/cloudstack.git
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:
parent
c4dff1e204
commit
266b8e5ee8
|
|
@ -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///////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue