|
23 | 23 | import java.sql.SQLException; |
24 | 24 | import java.util.ArrayList; |
25 | 25 | import java.util.Arrays; |
| 26 | +import java.util.Collections; |
26 | 27 | import java.util.Date; |
27 | 28 | import java.util.HashMap; |
28 | 29 | import java.util.Iterator; |
|
40 | 41 | import javax.naming.ConfigurationException; |
41 | 42 |
|
42 | 43 | import org.apache.commons.collections.CollectionUtils; |
| 44 | +import org.apache.commons.collections.MapUtils; |
43 | 45 | import org.apache.log4j.Logger; |
44 | 46 |
|
45 | 47 | import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao; |
@@ -2292,99 +2294,121 @@ protected void migrate(final VMInstanceVO vm, final long srcHostId, final Deploy |
2292 | 2294 | } |
2293 | 2295 | } |
2294 | 2296 |
|
2295 | | - private Map<Volume, StoragePool> getPoolListForVolumesForMigration(final VirtualMachineProfile profile, final Host host, final Map<Long, Long> volumeToPool) { |
2296 | | - final List<VolumeVO> allVolumes = _volsDao.findUsableVolumesForInstance(profile.getId()); |
2297 | | - final Map<Volume, StoragePool> volumeToPoolObjectMap = new HashMap<>(); |
2298 | | - |
2299 | | - for (final VolumeVO volume : allVolumes) { |
2300 | | - final Long poolId = volumeToPool.get(volume.getId()); |
2301 | | - final StoragePoolVO destPool = _storagePoolDao.findById(poolId); |
2302 | | - final StoragePoolVO currentPool = _storagePoolDao.findById(volume.getPoolId()); |
2303 | | - final DiskOfferingVO diskOffering = _diskOfferingDao.findById(volume.getDiskOfferingId()); |
2304 | | - |
2305 | | - if (destPool != null) { |
2306 | | - if (currentPool.isManaged()) { |
2307 | | - if (destPool.getId() == currentPool.getId()) { |
2308 | | - volumeToPoolObjectMap.put(volume, currentPool); |
2309 | | - } |
2310 | | - else { |
2311 | | - throw new CloudRuntimeException("Currently, a volume on managed storage can only be 'migrated' to itself."); |
2312 | | - } |
2313 | | - } |
2314 | | - else { |
2315 | | - // Check if pool is accessible from the destination host and disk offering with which the volume was |
2316 | | - // created is compliant with the pool type. |
2317 | | - if (_poolHostDao.findByPoolHost(destPool.getId(), host.getId()) == null || destPool.isLocal() != diskOffering.getUseLocalStorage()) { |
2318 | | - // Cannot find a pool for the volume. Throw an exception. |
2319 | | - throw new CloudRuntimeException("Cannot migrate volume " + volume + " to storage pool " + destPool + " while migrating vm to host " + host + |
2320 | | - ". Either the pool is not accessible from the host or because of the offering with which the volume is created it cannot be placed on " + |
2321 | | - "the given pool."); |
2322 | | - } else if (destPool.getId() == currentPool.getId()) { |
2323 | | - // If the pool to migrate to is the same as current pool, the volume doesn't need to be migrated. |
2324 | | - } else { |
2325 | | - volumeToPoolObjectMap.put(volume, destPool); |
2326 | | - } |
2327 | | - } |
2328 | | - } else { |
2329 | | - if (currentPool.isManaged()) { |
2330 | | - if (currentPool.getScope() == ScopeType.ZONE) { |
2331 | | - volumeToPoolObjectMap.put(volume, currentPool); |
2332 | | - } |
2333 | | - else { |
2334 | | - throw new CloudRuntimeException("Currently, you can only 'migrate' a volume on managed storage if its storage pool is zone wide."); |
2335 | | - } |
2336 | | - } else { |
2337 | | - // Find a suitable pool for the volume. Call the storage pool allocator to find the list of pools. |
2338 | | - |
2339 | | - final DiskProfile diskProfile = new DiskProfile(volume, diskOffering, profile.getHypervisorType()); |
2340 | | - final DataCenterDeployment plan = new DataCenterDeployment(host.getDataCenterId(), host.getPodId(), host.getClusterId(), |
2341 | | - host.getId(), null, null); |
2342 | | - |
2343 | | - final List<StoragePool> poolList = new ArrayList<>(); |
2344 | | - final ExcludeList avoid = new ExcludeList(); |
2345 | | - |
2346 | | - for (final StoragePoolAllocator allocator : _storagePoolAllocators) { |
2347 | | - final List<StoragePool> poolListFromAllocator = allocator.allocateToPool(diskProfile, profile, plan, avoid, StoragePoolAllocator.RETURN_UPTO_ALL); |
2348 | | - |
2349 | | - if (poolListFromAllocator != null && !poolListFromAllocator.isEmpty()) { |
2350 | | - poolList.addAll(poolListFromAllocator); |
2351 | | - } |
2352 | | - } |
2353 | | - |
2354 | | - boolean currentPoolAvailable = false; |
| 2297 | + /** |
| 2298 | + * 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)}. |
| 2299 | + * If the user provided a mapping, we use whatever the user has provided (check the method {@link #createMappingVolumeAndStoragePoolEnteredByUser(VirtualMachineProfile, Host, Map)}). |
| 2300 | + */ |
| 2301 | + private Map<Volume, StoragePool> getPoolListForVolumesForMigration(VirtualMachineProfile profile, Host targetHost, Map<Long, Long> volumeToPool) { |
| 2302 | + if (MapUtils.isEmpty(volumeToPool)) { |
| 2303 | + return getDefaultMappingOfVolumesAndStoragePoolForMigration(profile, targetHost); |
| 2304 | + } |
2355 | 2305 |
|
2356 | | - if (poolList != null && !poolList.isEmpty()) { |
2357 | | - // Volume needs to be migrated. Pick the first pool from the list. Add a mapping to migrate the |
2358 | | - // volume to a pool only if it is required; that is the current pool on which the volume resides |
2359 | | - // is not available on the destination host. |
| 2306 | + return createMappingVolumeAndStoragePoolEnteredByUser(profile, targetHost, volumeToPool); |
| 2307 | + } |
| 2308 | + |
| 2309 | + /** |
| 2310 | + * We create the mapping of volumes and storage pool to migrate the VMs according to the information sent by the user. |
| 2311 | + */ |
| 2312 | + private Map<Volume, StoragePool> createMappingVolumeAndStoragePoolEnteredByUser(VirtualMachineProfile profile, Host host, Map<Long, Long> volumeToPool) { |
| 2313 | + Map<Volume, StoragePool> volumeToPoolObjectMap = new HashMap<Volume, StoragePool>(); |
| 2314 | + for(Long volumeId: volumeToPool.keySet()) { |
| 2315 | + VolumeVO volume = _volsDao.findById(volumeId); |
2360 | 2316 |
|
2361 | | - final Iterator<StoragePool> iter = poolList.iterator(); |
| 2317 | + Long poolId = volumeToPool.get(volumeId); |
| 2318 | + StoragePoolVO targetPool = _storagePoolDao.findById(poolId); |
| 2319 | + StoragePoolVO currentPool = _storagePoolDao.findById(volume.getPoolId()); |
2362 | 2320 |
|
2363 | | - while (iter.hasNext()) { |
2364 | | - if (currentPool.getId() == iter.next().getId()) { |
2365 | | - currentPoolAvailable = true; |
| 2321 | + if (_poolHostDao.findByPoolHost(targetPool.getId(), host.getId()) == null) { |
| 2322 | + 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())); |
| 2323 | + } |
| 2324 | + if (currentPool.getId() == targetPool.getId()) { |
| 2325 | + s_logger.info(String.format("The volume [%s] is already allocated in storage pool [%s].", volume.getUuid(), targetPool.getUuid())); |
| 2326 | + } |
| 2327 | + volumeToPoolObjectMap.put(volume, targetPool); |
| 2328 | + } |
| 2329 | + return volumeToPoolObjectMap; |
| 2330 | +} |
| 2331 | + |
| 2332 | + /** |
| 2333 | + * We create the default mapping of volumes and storage pools for the migration of the VM to the target host. |
| 2334 | + * 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. |
| 2335 | + * Otherwise, we do not need to migrate, and the volume can be kept in its current storage pool. |
| 2336 | + */ |
| 2337 | + private Map<Volume, StoragePool> getDefaultMappingOfVolumesAndStoragePoolForMigration(VirtualMachineProfile profile, Host targetHost) { |
| 2338 | + Map<Volume, StoragePool> volumeToPoolObjectMap = new HashMap<Volume, StoragePool>(); |
| 2339 | + List<VolumeVO> allVolumes = _volsDao.findUsableVolumesForInstance(profile.getId()); |
| 2340 | + for (VolumeVO volume : allVolumes) { |
| 2341 | + StoragePoolVO currentPool = _storagePoolDao.findById(volume.getPoolId()); |
| 2342 | + if (ScopeType.HOST.equals(currentPool.getScope())) { |
| 2343 | + createVolumeToStoragePoolMappingIfNeeded(profile, targetHost, volumeToPoolObjectMap, volume, currentPool); |
| 2344 | + } else { |
| 2345 | + volumeToPoolObjectMap.put(volume, currentPool); |
| 2346 | + } |
| 2347 | + } |
| 2348 | + return volumeToPoolObjectMap; |
| 2349 | + } |
| 2350 | + |
| 2351 | + /** |
| 2352 | + * We will add a mapping of volume to storage pool if needed. The conditions to add a mapping are the following: |
| 2353 | + * <ul> |
| 2354 | + * <li> The current storage pool where the volume is allocated can be accessed by the target host |
| 2355 | + * <li> If not storage pool is found to allocate the volume we throw an exception. |
| 2356 | + * </ul> |
| 2357 | + * |
| 2358 | + */ |
| 2359 | + private void createVolumeToStoragePoolMappingIfNeeded(VirtualMachineProfile profile, Host targetHost, Map<Volume, StoragePool> volumeToPoolObjectMap, VolumeVO volume, StoragePoolVO currentPool) { |
| 2360 | + List<StoragePool> poolList = getCandidateStoragePoolsToMigrateLocalVolume(profile, targetHost, volume); |
| 2361 | + |
| 2362 | + Collections.shuffle(poolList); |
| 2363 | + boolean canTargetHostAccessVolumeStoragePool = false; |
| 2364 | + for (StoragePool storagePool : poolList) { |
| 2365 | + if (storagePool.getId() == currentPool.getId()) { |
| 2366 | + canTargetHostAccessVolumeStoragePool = true; |
| 2367 | + break; |
| 2368 | + } |
2366 | 2369 |
|
2367 | | - break; |
2368 | | - } |
2369 | | - } |
| 2370 | + } |
| 2371 | + if(!canTargetHostAccessVolumeStoragePool && CollectionUtils.isEmpty(poolList)) { |
| 2372 | + throw new CloudRuntimeException(String.format("There is not storage pools avaliable at the target host [%s] to migrate volume [%s]", targetHost.getUuid(), volume.getUuid())); |
| 2373 | + } |
| 2374 | + if (!canTargetHostAccessVolumeStoragePool) { |
| 2375 | + volumeToPoolObjectMap.put(volume, _storagePoolDao.findByUuid(poolList.get(0).getUuid())); |
| 2376 | + } |
| 2377 | + if (!canTargetHostAccessVolumeStoragePool && !volumeToPoolObjectMap.containsKey(volume)) { |
| 2378 | + 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(), |
| 2379 | + profile.getUuid(), targetHost.getUuid())); |
| 2380 | + } |
| 2381 | + } |
| 2382 | + |
| 2383 | + /** |
| 2384 | + * We use {@link StoragePoolAllocator} objects to find local storage pools connected to the targetHost where we would be able to allocate the given volume. |
| 2385 | + */ |
| 2386 | + private List<StoragePool> getCandidateStoragePoolsToMigrateLocalVolume(VirtualMachineProfile profile, Host targetHost, VolumeVO volume) { |
| 2387 | + List<StoragePool> poolList = new ArrayList<>(); |
2370 | 2388 |
|
2371 | | - if (!currentPoolAvailable) { |
2372 | | - volumeToPoolObjectMap.put(volume, _storagePoolDao.findByUuid(poolList.get(0).getUuid())); |
2373 | | - } |
2374 | | - } |
| 2389 | + DiskOfferingVO diskOffering = _diskOfferingDao.findById(volume.getDiskOfferingId()); |
| 2390 | + DiskProfile diskProfile = new DiskProfile(volume, diskOffering, profile.getHypervisorType()); |
| 2391 | + DataCenterDeployment plan = new DataCenterDeployment(targetHost.getDataCenterId(), targetHost.getPodId(), targetHost.getClusterId(), targetHost.getId(), null, null); |
| 2392 | + ExcludeList avoid = new ExcludeList(); |
2375 | 2393 |
|
2376 | | - if (!currentPoolAvailable && !volumeToPoolObjectMap.containsKey(volume)) { |
2377 | | - // Cannot find a pool for the volume. Throw an exception. |
2378 | | - throw new CloudRuntimeException("Cannot find a storage pool which is available for volume " + volume + " while migrating virtual machine " + |
2379 | | - profile.getVirtualMachine() + " to host " + host); |
2380 | | - } |
| 2394 | + StoragePoolVO volumeStoragePool = _storagePoolDao.findById(volume.getPoolId()); |
| 2395 | + if (volumeStoragePool.isLocal()) { |
| 2396 | + diskProfile.setUseLocalStorage(true); |
| 2397 | + } |
| 2398 | + for (StoragePoolAllocator allocator : _storagePoolAllocators) { |
| 2399 | + List<StoragePool> poolListFromAllocator = allocator.allocateToPool(diskProfile, profile, plan, avoid, StoragePoolAllocator.RETURN_UPTO_ALL); |
| 2400 | + if (CollectionUtils.isEmpty(poolListFromAllocator)) { |
| 2401 | + continue; |
| 2402 | + } |
| 2403 | + for (StoragePool pool : poolListFromAllocator) { |
| 2404 | + if (pool.isLocal()) { |
| 2405 | + poolList.add(pool); |
2381 | 2406 | } |
2382 | 2407 | } |
2383 | 2408 | } |
2384 | | - |
2385 | | - return volumeToPoolObjectMap; |
| 2409 | + return poolList; |
2386 | 2410 | } |
2387 | | - |
| 2411 | + |
2388 | 2412 | private <T extends VMInstanceVO> void moveVmToMigratingState(final T vm, final Long hostId, final ItWorkVO work) throws ConcurrentOperationException { |
2389 | 2413 | // Put the vm in migrating state. |
2390 | 2414 | try { |
|
0 commit comments