in server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java [3291:3514]
public Volume migrateVolume(MigrateVolumeCmd cmd) {
Account caller = CallContext.current().getCallingAccount();
Long volumeId = cmd.getVolumeId();
Long storagePoolId = cmd.getStoragePoolId();
VolumeVO vol = _volsDao.findById(volumeId);
if (vol == null) {
throw new InvalidParameterValueException("Failed to find the volume id: " + volumeId);
}
_accountMgr.checkAccess(caller, null, true, vol);
if (vol.getState() != Volume.State.Ready) {
throw new InvalidParameterValueException("Volume must be in ready state");
}
if (vol.getPoolId() == storagePoolId.longValue()) {
throw new InvalidParameterValueException("Volume " + vol + " is already on the destination storage pool");
}
boolean liveMigrateVolume = false;
boolean srcAndDestOnStorPool = false;
Long instanceId = vol.getInstanceId();
Long srcClusterId = null;
VMInstanceVO vm = null;
if (instanceId != null) {
vm = _vmInstanceDao.findById(instanceId);
checkVmStateForMigration(vm, vol);
}
// Check that Vm to which this volume is attached does not have VM Snapshots
// OfflineVmwareMigration: consider if this is needed and desirable
if (vm != null && _vmSnapshotDao.findByVm(vm.getId()).size() > 0) {
throw new InvalidParameterValueException("Volume cannot be migrated, please remove all VM snapshots for VM to which this volume is attached");
}
StoragePoolVO srcStoragePoolVO = _storagePoolDao.findById(vol.getPoolId());
// OfflineVmwareMigration: extract this block as method and check if it is subject to regression
if (vm != null && State.Running.equals(vm.getState())) {
// Check if the VM is GPU enabled.
if (_serviceOfferingDetailsDao.findDetail(vm.getServiceOfferingId(), GPU.Keys.pciDevice.toString()) != null) {
throw new InvalidParameterValueException("Live Migration of GPU enabled VM is not supported");
}
// Check if the underlying hypervisor supports storage motion.
Long hostId = vm.getHostId();
if (hostId != null) {
HostVO host = _hostDao.findById(hostId);
HypervisorCapabilitiesVO capabilities = null;
if (host != null) {
capabilities = _hypervisorCapabilitiesDao.findByHypervisorTypeAndVersion(host.getHypervisorType(), host.getHypervisorVersion());
srcClusterId = host.getClusterId();
}
if (capabilities != null) {
liveMigrateVolume = capabilities.isStorageMotionSupported();
}
if (liveMigrateVolume && HypervisorType.KVM.equals(host.getHypervisorType()) && !srcStoragePoolVO.getPoolType().equals(Storage.StoragePoolType.PowerFlex)) {
StoragePoolVO destinationStoragePoolVo = _storagePoolDao.findById(storagePoolId);
if (isSourceOrDestNotOnStorPool(srcStoragePoolVO, destinationStoragePoolVo)) {
throw new InvalidParameterValueException("KVM does not support volume live migration due to the limited possibility to refresh VM XML domain. " +
"Therefore, to live migrate a volume between storage pools, one must migrate the VM to a different host as well to force the VM XML domain update. " +
"Use 'migrateVirtualMachineWithVolumes' instead.");
}
srcAndDestOnStorPool = isSourceAndDestOnStorPool(srcStoragePoolVO, destinationStoragePoolVo);
}
}
// If vm is running, and hypervisor doesn't support live migration, then return error
if (!liveMigrateVolume) {
throw new InvalidParameterValueException("Volume needs to be detached from VM");
}
if (!cmd.isLiveMigrate()) {
throw new InvalidParameterValueException("The volume " + vol + "is attached to a vm and for migrating it " + "the parameter livemigrate should be specified");
}
}
if (vol.getPassphraseId() != null && !srcAndDestOnStorPool && !srcStoragePoolVO.getPoolType().equals(Storage.StoragePoolType.PowerFlex)) {
throw new InvalidParameterValueException("Migration of encrypted volumes is unsupported");
}
if (vm != null &&
HypervisorType.VMware.equals(vm.getHypervisorType()) &&
State.Stopped.equals(vm.getState())) {
// For VMware, use liveMigrateVolume=true so that it follows VmwareStorageMotionStrategy
liveMigrateVolume = true;
}
StoragePool destPool = (StoragePool)dataStoreMgr.getDataStore(storagePoolId, DataStoreRole.Primary);
if (destPool == null) {
throw new InvalidParameterValueException("Failed to find the destination storage pool: " + storagePoolId);
} else if (destPool.isInMaintenance()) {
throw new InvalidParameterValueException("Cannot migrate volume " + vol + "to the destination storage pool " + destPool.getName() + " as the storage pool is in maintenance mode.");
}
try {
snapshotHelper.checkKvmVolumeSnapshotsOnlyInPrimaryStorage(vol, _volsDao.getHypervisorType(vol.getId()));
} catch (CloudRuntimeException ex) {
throw new CloudRuntimeException(String.format("Unable to migrate %s to the destination storage pool [%s] due to [%s]", vol,
ReflectionToStringBuilderUtils.reflectOnlySelectedFields(destPool, "uuid", "name"), ex.getMessage()), ex);
}
DiskOfferingVO diskOffering = _diskOfferingDao.findById(vol.getDiskOfferingId());
if (diskOffering == null) {
throw new CloudRuntimeException("volume '" + vol.getUuid() + "', has no diskoffering. Migration target cannot be checked.");
}
String poolUuid = destPool.getUuid();
if (destPool.getPoolType() == Storage.StoragePoolType.DatastoreCluster) {
DataCenter dc = _entityMgr.findById(DataCenter.class, vol.getDataCenterId());
Pod destPoolPod = _entityMgr.findById(Pod.class, destPool.getPodId());
destPool = _volumeMgr.findChildDataStoreInDataStoreCluster(dc, destPoolPod, destPool.getClusterId(), null, null, destPool.getId());
}
if (!storageMgr.storagePoolCompatibleWithVolumePool(destPool, (Volume) vol)) {
throw new CloudRuntimeException("Storage pool " + destPool.getName() + " is not suitable to migrate volume " + vol.getName());
}
HypervisorType hypervisorType = _volsDao.getHypervisorType(volumeId);
DiskProfile diskProfile = new DiskProfile(vol, diskOffering, hypervisorType);
Pair<Volume, DiskProfile> volumeDiskProfilePair = new Pair<>(vol, diskProfile);
if (!storageMgr.storagePoolHasEnoughSpace(Collections.singletonList(volumeDiskProfilePair), destPool)) {
throw new CloudRuntimeException("Storage pool " + destPool.getName() + " does not have enough space to migrate volume " + vol.getName());
}
// OfflineVmwareMigration: check storage tags on disk(offering)s in comparison to destination storage pool
// OfflineVmwareMigration: if no match return a proper error now
if (liveMigrateVolume && State.Running.equals(vm.getState()) &&
destPool.getClusterId() != null && srcClusterId != null) {
if (!srcClusterId.equals(destPool.getClusterId())) {
throw new InvalidParameterValueException("Cannot migrate a volume of a virtual machine to a storage pool in a different cluster");
}
}
// In case of VMware, if ROOT volume is being cold-migrated, then ensure destination storage pool is in the same Datacenter as the VM.
if (vm != null && vm.getHypervisorType().equals(HypervisorType.VMware)) {
if (!liveMigrateVolume && vol.getVolumeType().equals(Volume.Type.ROOT)) {
Long hostId = vm.getHostId() != null ? vm.getHostId() : vm.getLastHostId();
HostVO host = _hostDao.findById(hostId);
if (host != null) {
srcClusterId = host.getClusterId();
}
if (srcClusterId != null && destPool.getClusterId() != null && !srcClusterId.equals(destPool.getClusterId())) {
String srcDcName = _clusterDetailsDao.getVmwareDcName(srcClusterId);
String destDcName = _clusterDetailsDao.getVmwareDcName(destPool.getClusterId());
if (srcDcName != null && destDcName != null && !srcDcName.equals(destDcName)) {
throw new InvalidParameterValueException("Cannot migrate ROOT volume of a stopped VM to a storage pool in a different VMware datacenter");
}
}
updateMissingRootDiskController(vm, vol.getChainInfo());
}
}
if (hypervisorType.equals(HypervisorType.VMware)) {
try {
boolean isStoragePoolStoragepolicyComplaince = storageMgr.isStoragePoolCompliantWithStoragePolicy(Arrays.asList(volumeDiskProfilePair), destPool);
if (!isStoragePoolStoragepolicyComplaince) {
throw new CloudRuntimeException(String.format("Storage pool %s is not storage policy compliance with the volume %s", poolUuid, vol.getUuid()));
}
} catch (StorageUnavailableException e) {
throw new CloudRuntimeException(String.format("Could not verify storage policy compliance against storage pool %s due to exception %s", destPool.getUuid(), e.getMessage()));
}
}
DiskOfferingVO newDiskOffering = retrieveAndValidateNewDiskOffering(cmd);
// if no new disk offering was provided, and match is required, default to the offering of the
// original volume. otherwise it falls through with no check and the target volume may
// not work correctly in some scenarios with the target provider. Adminstrator
// can disable this flag dynamically for certain bulk migration scenarios if required.
if (newDiskOffering == null && Boolean.TRUE.equals(MatchStoragePoolTagsWithDiskOffering.value())) {
newDiskOffering = diskOffering;
}
validateConditionsToReplaceDiskOfferingOfVolume(vol, newDiskOffering, destPool);
if (vm != null) {
// serialize VM operation
AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext();
if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) {
// avoid re-entrance
VmWorkJobVO placeHolder = null;
placeHolder = createPlaceHolderWork(vm.getId());
try {
return orchestrateMigrateVolume(vol, destPool, liveMigrateVolume, newDiskOffering);
} finally {
_workJobDao.expunge(placeHolder.getId());
}
} else {
Outcome<Volume> outcome = migrateVolumeThroughJobQueue(vm, vol, destPool, liveMigrateVolume, newDiskOffering);
try {
outcome.get();
} catch (InterruptedException e) {
throw new RuntimeException("Operation is interrupted", e);
} catch (ExecutionException e) {
throw new RuntimeException("Execution excetion", e);
}
Object jobResult = _jobMgr.unmarshallResultObject(outcome.getJob());
if (jobResult != null) {
if (jobResult instanceof ConcurrentOperationException) {
throw (ConcurrentOperationException)jobResult;
} else if (jobResult instanceof RuntimeException) {
throw (RuntimeException)jobResult;
} else if (jobResult instanceof Throwable) {
throw new RuntimeException("Unexpected exception", (Throwable)jobResult);
}
}
// retrieve the migrated new volume from job result
if (jobResult != null && jobResult instanceof Long) {
return _entityMgr.findById(VolumeVO.class, ((Long)jobResult));
}
return null;
}
}
return orchestrateMigrateVolume(vol, destPool, liveMigrateVolume, newDiskOffering);
}