Skip to content

Commit f550d70

Browse files
Add managed storage pool constraints to MigrateWithVolume API method (#2761)
* Add managed storage pool constraints to MigrateWithVolume API method * Apply mike's suggestions * Apply Mike's suggestion in a second review * Mike's suggestions * Confused bit * just executeManagedStorageChecks * Created methods `executeManagedStorageChecksWhenTargetStoragePoolNotProvided` and `executeManagedStorageChecksWhenTargetStoragePoolProvided` * improve "executeManagedStorageChecksWhenTargetStoragePoolNotProvided" * Fix "findVolumesThatWereNotMappedByTheUser" method * Applu Mike's suggestion to improve "createMappingVolumeAndStoragePool" method * Unit tests to cover modified code
1 parent 82fc9f3 commit f550d70

File tree

2 files changed

+539
-56
lines changed

2 files changed

+539
-56
lines changed

engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java

Lines changed: 107 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2286,31 +2286,52 @@ protected void migrate(final VMInstanceVO vm, final long srcHostId, final Deploy
22862286
}
22872287

22882288
/**
2289-
* Create the mapping of volumes and storage pools. If the user did not enter a mapping on her/his own, we create one using {@link #getDefaultMappingOfVolumesAndStoragePoolForMigration(VirtualMachineProfile, Host)}.
2290-
* If the user provided a mapping, we use whatever the user has provided (check the method {@link #createMappingVolumeAndStoragePoolEnteredByUser(VirtualMachineProfile, Host, Map)}).
2289+
* We create the mapping of volumes and storage pool to migrate the VMs according to the information sent by the user.
2290+
* If the user did not enter a complete mapping, the volumes that were left behind will be auto mapped using {@link #createStoragePoolMappingsForVolumes(VirtualMachineProfile, Host, Map, List)}
22912291
*/
2292-
private Map<Volume, StoragePool> getPoolListForVolumesForMigration(VirtualMachineProfile profile, Host targetHost, Map<Long, Long> volumeToPool) {
2293-
if (MapUtils.isEmpty(volumeToPool)) {
2294-
return getDefaultMappingOfVolumesAndStoragePoolForMigration(profile, targetHost);
2295-
}
2292+
protected Map<Volume, StoragePool> createMappingVolumeAndStoragePool(VirtualMachineProfile profile, Host targetHost, Map<Long, Long> userDefinedMapOfVolumesAndStoragePools) {
2293+
Map<Volume, StoragePool> volumeToPoolObjectMap = buildMapUsingUserInformation(profile, targetHost, userDefinedMapOfVolumesAndStoragePools);
22962294

2297-
return createMappingVolumeAndStoragePoolEnteredByUser(profile, targetHost, volumeToPool);
2295+
List<Volume> volumesNotMapped = findVolumesThatWereNotMappedByTheUser(profile, volumeToPoolObjectMap);
2296+
createStoragePoolMappingsForVolumes(profile, targetHost, volumeToPoolObjectMap, volumesNotMapped);
2297+
return volumeToPoolObjectMap;
22982298
}
22992299

23002300
/**
2301-
* We create the mapping of volumes and storage pool to migrate the VMs according to the information sent by the user.
2301+
* Given the map of volume to target storage pool entered by the user, we check for other volumes that the VM might have and were not configured.
2302+
* This map can be then used by CloudStack to find new target storage pools according to the target host.
23022303
*/
2303-
private Map<Volume, StoragePool> createMappingVolumeAndStoragePoolEnteredByUser(VirtualMachineProfile profile, Host host, Map<Long, Long> volumeToPool) {
2304-
Map<Volume, StoragePool> volumeToPoolObjectMap = new HashMap<Volume, StoragePool>();
2305-
for(Long volumeId: volumeToPool.keySet()) {
2304+
protected List<Volume> findVolumesThatWereNotMappedByTheUser(VirtualMachineProfile profile, Map<Volume, StoragePool> volumeToStoragePoolObjectMap) {
2305+
List<VolumeVO> allVolumes = _volsDao.findUsableVolumesForInstance(profile.getId());
2306+
List<Volume> volumesNotMapped = new ArrayList<>();
2307+
for (Volume volume : allVolumes) {
2308+
if (!volumeToStoragePoolObjectMap.containsKey(volume)) {
2309+
volumesNotMapped.add(volume);
2310+
}
2311+
}
2312+
return volumesNotMapped;
2313+
}
2314+
2315+
/**
2316+
* Builds the map of storage pools and volumes with the information entered by the user. Before creating the an entry we validate if the migration is feasible checking if the migration is allowed and if the target host can access the defined target storage pool.
2317+
*/
2318+
protected Map<Volume, StoragePool> buildMapUsingUserInformation(VirtualMachineProfile profile, Host targetHost, Map<Long, Long> userDefinedVolumeToStoragePoolMap) {
2319+
Map<Volume, StoragePool> volumeToPoolObjectMap = new HashMap<>();
2320+
if (MapUtils.isEmpty(userDefinedVolumeToStoragePoolMap)) {
2321+
return volumeToPoolObjectMap;
2322+
}
2323+
for(Long volumeId: userDefinedVolumeToStoragePoolMap.keySet()) {
23062324
VolumeVO volume = _volsDao.findById(volumeId);
23072325

2308-
Long poolId = volumeToPool.get(volumeId);
2326+
Long poolId = userDefinedVolumeToStoragePoolMap.get(volumeId);
23092327
StoragePoolVO targetPool = _storagePoolDao.findById(poolId);
23102328
StoragePoolVO currentPool = _storagePoolDao.findById(volume.getPoolId());
23112329

2312-
if (_poolHostDao.findByPoolHost(targetPool.getId(), host.getId()) == null) {
2313-
throw new CloudRuntimeException(String.format("Cannot migrate the volume [%s] to the storage pool [%s] while migrating VM [%s] to target host [%s]. The host does not have access to the storage pool entered.", volume.getUuid(), targetPool.getUuid(), profile.getUuid(), host.getUuid()));
2330+
executeManagedStorageChecksWhenTargetStoragePoolProvided(currentPool, volume, targetPool);
2331+
if (_poolHostDao.findByPoolHost(targetPool.getId(), targetHost.getId()) == null) {
2332+
throw new CloudRuntimeException(
2333+
String.format("Cannot migrate the volume [%s] to the storage pool [%s] while migrating VM [%s] to target host [%s]. The host does not have access to the storage pool entered.",
2334+
volume.getUuid(), targetPool.getUuid(), profile.getUuid(), targetHost.getUuid()));
23142335
}
23152336
if (currentPool.getId() == targetPool.getId()) {
23162337
s_logger.info(String.format("The volume [%s] is already allocated in storage pool [%s].", volume.getUuid(), targetPool.getUuid()));
@@ -2321,60 +2342,99 @@ private Map<Volume, StoragePool> createMappingVolumeAndStoragePoolEnteredByUser(
23212342
}
23222343

23232344
/**
2324-
* We create the default mapping of volumes and storage pools for the migration of the VM to the target host.
2325-
* If the current storage pool of one of the volumes is using local storage in the host, it then needs to be migrated to a local storage in the target host.
2326-
* Otherwise, we do not need to migrate, and the volume can be kept in its current storage pool.
2345+
* Executes the managed storage checks for the mapping<volume, storage pool> entered by the user. The checks execute by this method are the following.
2346+
* <ul>
2347+
* <li> If the current storage pool of the volume is not a managed storage, we do not need to validate anything here.
2348+
* <li> If the current storage pool is a managed storage and the target storage pool ID is different from the current one, we throw an exception.
2349+
* </ul>
23272350
*/
2328-
private Map<Volume, StoragePool> getDefaultMappingOfVolumesAndStoragePoolForMigration(VirtualMachineProfile profile, Host targetHost) {
2329-
Map<Volume, StoragePool> volumeToPoolObjectMap = new HashMap<Volume, StoragePool>();
2330-
List<VolumeVO> allVolumes = _volsDao.findUsableVolumesForInstance(profile.getId());
2331-
for (VolumeVO volume : allVolumes) {
2351+
protected void executeManagedStorageChecksWhenTargetStoragePoolProvided(StoragePoolVO currentPool, VolumeVO volume, StoragePoolVO targetPool) {
2352+
if (!currentPool.isManaged()) {
2353+
return;
2354+
}
2355+
if (currentPool.getId() == targetPool.getId()) {
2356+
return;
2357+
}
2358+
throw new CloudRuntimeException(String.format("Currently, a volume on managed storage can only be 'migrated' to itself " + "[volumeId=%s, currentStoragePoolId=%s, targetStoragePoolId=%s].",
2359+
volume.getUuid(), currentPool.getUuid(), targetPool.getUuid()));
2360+
}
2361+
2362+
/**
2363+
* For each one of the volumes we will map it to a storage pool that is available via the target host.
2364+
* An exception is thrown if we cannot find a storage pool that is accessible in the target host to migrate the volume to.
2365+
*/
2366+
protected void createStoragePoolMappingsForVolumes(VirtualMachineProfile profile, Host targetHost, Map<Volume, StoragePool> volumeToPoolObjectMap, List<Volume> allVolumes) {
2367+
for (Volume volume : allVolumes) {
23322368
StoragePoolVO currentPool = _storagePoolDao.findById(volume.getPoolId());
2333-
if (ScopeType.HOST.equals(currentPool.getScope())) {
2334-
createVolumeToStoragePoolMappingIfNeeded(profile, targetHost, volumeToPoolObjectMap, volume, currentPool);
2369+
2370+
executeManagedStorageChecksWhenTargetStoragePoolNotProvided(targetHost, currentPool, volume);
2371+
if (ScopeType.HOST.equals(currentPool.getScope()) || isStorageCrossClusterMigration(targetHost, currentPool)) {
2372+
createVolumeToStoragePoolMappingIfPossible(profile, targetHost, volumeToPoolObjectMap, volume, currentPool);
23352373
} else {
23362374
volumeToPoolObjectMap.put(volume, currentPool);
23372375
}
23382376
}
2339-
return volumeToPoolObjectMap;
2377+
}
2378+
2379+
/**
2380+
* Executes the managed storage checks for the volumes that the user has not entered a mapping of <volume, storage pool>. The following checks are performed.
2381+
* <ul>
2382+
* <li> If the current storage pool is not a managed storage, we do not need to proceed with this method;
2383+
* <li> We check if the target host has access to the current managed storage pool. If it does not have an exception will be thrown.
2384+
* </ul>
2385+
*/
2386+
protected void executeManagedStorageChecksWhenTargetStoragePoolNotProvided(Host targetHost, StoragePoolVO currentPool, Volume volume) {
2387+
if (!currentPool.isManaged()) {
2388+
return;
2389+
}
2390+
if (_poolHostDao.findByPoolHost(currentPool.getId(), targetHost.getId()) == null) {
2391+
throw new CloudRuntimeException(String.format("The target host does not have access to the volume's managed storage pool. [volumeId=%s, storageId=%s, targetHostId=%s].", volume.getUuid(),
2392+
currentPool.getUuid(), targetHost.getUuid()));
2393+
}
2394+
}
2395+
2396+
/**
2397+
* Return true if the VM migration is a cross cluster migration. To execute that, we check if the volume current storage pool cluster is different from the target host cluster.
2398+
*/
2399+
protected boolean isStorageCrossClusterMigration(Host targetHost, StoragePoolVO currentPool) {
2400+
return ScopeType.CLUSTER.equals(currentPool.getScope()) && currentPool.getClusterId() != targetHost.getClusterId();
23402401
}
23412402

23422403
/**
23432404
* We will add a mapping of volume to storage pool if needed. The conditions to add a mapping are the following:
23442405
* <ul>
2345-
* <li> The current storage pool where the volume is allocated can be accessed by the target host
2346-
* <li> If not storage pool is found to allocate the volume we throw an exception.
2406+
* <li> The candidate storage pool where the volume is to be allocated can be accessed by the target host
2407+
* <li> If no storage pool is found to allocate the volume we throw an exception.
23472408
* </ul>
23482409
*
2410+
* Side note: this method should only be called if the volume is on local storage or if we are executing a cross cluster migration.
23492411
*/
2350-
private void createVolumeToStoragePoolMappingIfNeeded(VirtualMachineProfile profile, Host targetHost, Map<Volume, StoragePool> volumeToPoolObjectMap, VolumeVO volume, StoragePoolVO currentPool) {
2351-
List<StoragePool> poolList = getCandidateStoragePoolsToMigrateLocalVolume(profile, targetHost, volume);
2412+
protected void createVolumeToStoragePoolMappingIfPossible(VirtualMachineProfile profile, Host targetHost, Map<Volume, StoragePool> volumeToPoolObjectMap, Volume volume,
2413+
StoragePoolVO currentPool) {
2414+
List<StoragePool> storagePoolList = getCandidateStoragePoolsToMigrateLocalVolume(profile, targetHost, volume);
23522415

2353-
Collections.shuffle(poolList);
2354-
boolean canTargetHostAccessVolumeStoragePool = false;
2355-
for (StoragePool storagePool : poolList) {
2416+
if (CollectionUtils.isEmpty(storagePoolList)) {
2417+
throw new CloudRuntimeException(String.format("There is not storage pools available at the target host [%s] to migrate volume [%s]", targetHost.getUuid(), volume.getUuid()));
2418+
}
2419+
2420+
Collections.shuffle(storagePoolList);
2421+
boolean canTargetHostAccessVolumeCurrentStoragePool = false;
2422+
for (StoragePool storagePool : storagePoolList) {
23562423
if (storagePool.getId() == currentPool.getId()) {
2357-
canTargetHostAccessVolumeStoragePool = true;
2424+
canTargetHostAccessVolumeCurrentStoragePool = true;
23582425
break;
23592426
}
23602427

23612428
}
2362-
if(!canTargetHostAccessVolumeStoragePool && CollectionUtils.isEmpty(poolList)) {
2363-
throw new CloudRuntimeException(String.format("There is not storage pools avaliable at the target host [%s] to migrate volume [%s]", targetHost.getUuid(), volume.getUuid()));
2364-
}
2365-
if (!canTargetHostAccessVolumeStoragePool) {
2366-
volumeToPoolObjectMap.put(volume, _storagePoolDao.findByUuid(poolList.get(0).getUuid()));
2367-
}
2368-
if (!canTargetHostAccessVolumeStoragePool && !volumeToPoolObjectMap.containsKey(volume)) {
2369-
throw new CloudRuntimeException(String.format("Cannot find a storage pool which is available for volume [%s] while migrating virtual machine [%s] to host [%s]", volume.getUuid(),
2370-
profile.getUuid(), targetHost.getUuid()));
2429+
if (!canTargetHostAccessVolumeCurrentStoragePool) {
2430+
volumeToPoolObjectMap.put(volume, _storagePoolDao.findByUuid(storagePoolList.get(0).getUuid()));
23712431
}
23722432
}
23732433

23742434
/**
2375-
* We use {@link StoragePoolAllocator} objects to find local storage pools connected to the targetHost where we would be able to allocate the given volume.
2435+
* We use {@link StoragePoolAllocator} objects to find storage pools connected to the targetHost where we would be able to allocate the given volume.
23762436
*/
2377-
private List<StoragePool> getCandidateStoragePoolsToMigrateLocalVolume(VirtualMachineProfile profile, Host targetHost, VolumeVO volume) {
2437+
protected List<StoragePool> getCandidateStoragePoolsToMigrateLocalVolume(VirtualMachineProfile profile, Host targetHost, Volume volume) {
23782438
List<StoragePool> poolList = new ArrayList<>();
23792439

23802440
DiskOfferingVO diskOffering = _diskOfferingDao.findById(volume.getDiskOfferingId());
@@ -2392,7 +2452,7 @@ private List<StoragePool> getCandidateStoragePoolsToMigrateLocalVolume(VirtualMa
23922452
continue;
23932453
}
23942454
for (StoragePool pool : poolListFromAllocator) {
2395-
if (pool.isLocal()) {
2455+
if (pool.isLocal() || isStorageCrossClusterMigration(targetHost, volumeStoragePool)) {
23962456
poolList.add(pool);
23972457
}
23982458
}
@@ -2487,7 +2547,7 @@ private void orchestrateMigrateWithStorage(final String vmUuid, final long srcHo
24872547

24882548
// Create a map of which volume should go in which storage pool.
24892549
final VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm);
2490-
final Map<Volume, StoragePool> volumeToPoolMap = getPoolListForVolumesForMigration(profile, destHost, volumeToPool);
2550+
final Map<Volume, StoragePool> volumeToPoolMap = createMappingVolumeAndStoragePool(profile, destHost, volumeToPool);
24912551

24922552
// If none of the volumes have to be migrated, fail the call. Administrator needs to make a call for migrating
24932553
// a vm and not migrating a vm with storage.
@@ -3971,8 +4031,8 @@ public String getConfigComponentName() {
39714031
@Override
39724032
public ConfigKey<?>[] getConfigKeys() {
39734033
return new ConfigKey<?>[] {ClusterDeltaSyncInterval, StartRetry, VmDestroyForcestop, VmOpCancelInterval, VmOpCleanupInterval, VmOpCleanupWait,
3974-
VmOpLockStateRetry,
3975-
VmOpWaitInterval, ExecuteInSequence, VmJobCheckInterval, VmJobTimeout, VmJobStateReportInterval, VmConfigDriveLabel, VmConfigDriveOnPrimaryPool, HaVmRestartHostUp};
4034+
VmOpLockStateRetry,
4035+
VmOpWaitInterval, ExecuteInSequence, VmJobCheckInterval, VmJobTimeout, VmJobStateReportInterval, VmConfigDriveLabel, VmConfigDriveOnPrimaryPool, HaVmRestartHostUp};
39764036
}
39774037

39784038
public List<StoragePoolAllocator> getStoragePoolAllocators() {

0 commit comments

Comments
 (0)