Skip to content

Commit d0baf33

Browse files
[CLOUDSTACK-10240] ACS cannot migrate a volume from local to shared storage.
CloudStack is logically restricting the migration of local storages to shared storage and vice versa. This restriction is a logical one and can be removed for XenServer deployments. Therefore, we will enable migration of volumes between local-shared storages in XenServers independently of their service offering. This will work as an override mechanism to the disk offering used by volumes. If administrators want to migrate local volumes to a shared storage, they should be able to do so (the hypervisor already allows that). The same the other way around.
1 parent 6233a77 commit d0baf33

File tree

9 files changed

+355
-606
lines changed

9 files changed

+355
-606
lines changed

api/src/main/java/org/apache/cloudstack/api/command/admin/storage/FindStoragePoolsForMigrationCmd.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package org.apache.cloudstack.api.command.admin.storage;
1818

1919
import java.util.ArrayList;
20+
import java.util.Collections;
21+
import java.util.Comparator;
2022
import java.util.List;
2123

2224
import org.apache.log4j.Logger;
@@ -90,9 +92,27 @@ public void execute() {
9092
poolResponse.setObjectName("storagepool");
9193
poolResponses.add(poolResponse);
9294
}
93-
95+
sortPoolsBySuitabilityAndName(poolResponses);
9496
response.setResponses(poolResponses);
9597
response.setResponseName(getCommandName());
9698
this.setResponseObject(response);
9799
}
100+
101+
protected void sortPoolsBySuitabilityAndName(List<StoragePoolResponse> poolResponses) {
102+
Collections.sort(poolResponses, new Comparator<StoragePoolResponse>() {
103+
@Override
104+
public int compare(StoragePoolResponse o1, StoragePoolResponse o2) {
105+
if (o1.getSuitableForMigration() && o2.getSuitableForMigration()) {
106+
return o1.getName().compareTo(o2.getName());
107+
}
108+
if (o1.getSuitableForMigration()) {
109+
return -1;
110+
}
111+
if (o2.getSuitableForMigration()) {
112+
return 1;
113+
}
114+
return 0;
115+
}
116+
});
117+
}
98118
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
package org.apache.cloudstack.api.command.admin.storage;
18+
19+
import java.util.ArrayList;
20+
import java.util.List;
21+
22+
import org.apache.cloudstack.api.response.StoragePoolResponse;
23+
import org.junit.Assert;
24+
import org.junit.Test;
25+
import org.junit.runner.RunWith;
26+
import org.mockito.runners.MockitoJUnitRunner;
27+
28+
@RunWith(MockitoJUnitRunner.class)
29+
public class FindStoragePoolsForMigrationCmdTest {
30+
31+
private FindStoragePoolsForMigrationCmd findStoragePoolsForMigrationCmd = new FindStoragePoolsForMigrationCmd();
32+
33+
@Test
34+
public void sortPoolsBySuitability() {
35+
List<StoragePoolResponse> storagePoolsResponse = new ArrayList<>();
36+
StoragePoolResponse storagePoolResponse1 = new StoragePoolResponse();
37+
storagePoolResponse1.setSuitableForMigration(true);
38+
storagePoolResponse1.setId("1");
39+
storagePoolResponse1.setName("1");
40+
41+
StoragePoolResponse storagePoolResponse2 = new StoragePoolResponse();
42+
storagePoolResponse2.setSuitableForMigration(false);
43+
storagePoolResponse2.setId("2");
44+
storagePoolResponse2.setName("2");
45+
46+
StoragePoolResponse storagePoolResponse3 = new StoragePoolResponse();
47+
storagePoolResponse3.setSuitableForMigration(true);
48+
storagePoolResponse3.setId("3");
49+
storagePoolResponse3.setName("3");
50+
51+
storagePoolsResponse.add(storagePoolResponse3);
52+
storagePoolsResponse.add(storagePoolResponse2);
53+
storagePoolsResponse.add(storagePoolResponse1);
54+
55+
findStoragePoolsForMigrationCmd.sortPoolsBySuitabilityAndName(storagePoolsResponse);
56+
57+
Assert.assertEquals("1", storagePoolsResponse.get(0).getId());
58+
Assert.assertEquals("3", storagePoolsResponse.get(1).getId());
59+
Assert.assertEquals("2", storagePoolsResponse.get(2).getId());
60+
61+
}
62+
63+
}

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

Lines changed: 107 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.sql.SQLException;
2424
import java.util.ArrayList;
2525
import java.util.Arrays;
26+
import java.util.Collections;
2627
import java.util.Date;
2728
import java.util.HashMap;
2829
import java.util.Iterator;
@@ -40,6 +41,7 @@
4041
import javax.naming.ConfigurationException;
4142

4243
import org.apache.commons.collections.CollectionUtils;
44+
import org.apache.commons.collections.MapUtils;
4345
import org.apache.log4j.Logger;
4446

4547
import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao;
@@ -2292,99 +2294,121 @@ protected void migrate(final VMInstanceVO vm, final long srcHostId, final Deploy
22922294
}
22932295
}
22942296

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+
}
23552305

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);
23602316

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());
23622320

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+
}
23662369

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<>();
23702388

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();
23752393

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);
23812406
}
23822407
}
23832408
}
2384-
2385-
return volumeToPoolObjectMap;
2409+
return poolList;
23862410
}
2387-
2411+
23882412
private <T extends VMInstanceVO> void moveVmToMigratingState(final T vm, final Long hostId, final ItWorkVO work) throws ConcurrentOperationException {
23892413
// Put the vm in migrating state.
23902414
try {

0 commit comments

Comments
 (0)