Skip to content

Commit 7f25ad0

Browse files
author
kunal.behbudzade
committed
kvm: fix direct-download live storage migration
Direct-download backed volumes should not use the linked-clone live storage migration path because the backing chain is not guaranteed on the destination host. Force the KVM migration request to full clone when a migrated volume is direct-download backed, while ignoring volumes that are skipped from the migration request. Keep direct-download volume metadata consistent across VolumeDataFactory paths and make target-connection/template-reference failures explicit. Add unit coverage for forced full-clone, skipped-volume boundaries, copied template references, ModifyTargets answers, and VolumeDataFactory propagation.
1 parent ffebe8e commit 7f25ad0

5 files changed

Lines changed: 549 additions & 33 deletions

File tree

engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java

Lines changed: 81 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1964,7 +1964,7 @@ protected MigrationOptions createLinkedCloneMigrationOptions(VolumeInfo srcVolum
19641964
Storage.StoragePoolType srcPoolType = srcPool.getPoolType();
19651965
Long srcPoolClusterId = srcPool.getClusterId();
19661966
VMTemplateStoragePoolVO ref = templatePoolDao.findByPoolTemplate(destVolumeInfo.getPoolId(), srcVolumeInfo.getTemplateId(), null);
1967-
boolean updateBackingFileReference = ref == null;
1967+
boolean updateBackingFileReference = ref == null || !StringUtils.equals(ref.getInstallPath(), srcVolumeBackingFile);
19681968
String backingFile = !updateBackingFileReference ? ref.getInstallPath() : srcVolumeBackingFile;
19691969
ScopeType scopeType = srcVolumeInfo.getDataStore().getScope().getScopeType();
19701970
return new MigrationOptions(srcPoolUuid, srcPoolType, backingFile, updateBackingFileReference, scopeType, srcPoolClusterId);
@@ -2009,6 +2009,34 @@ protected void setVolumeMigrationOptions(VolumeInfo srcVolumeInfo, VolumeInfo de
20092009
destVolumeInfo.setMigrationOptions(migrationOptions);
20102010
}
20112011

2012+
/**
2013+
* KVM/libvirt selects linked-clone or full-clone storage migration for the whole VM migration request.
2014+
* If any disk is backed by a direct-download template, force the request to full clone so libvirt does
2015+
* not use incremental shared-backing semantics for a disk whose backing chain is not guaranteed on the destination.
2016+
*/
2017+
protected boolean shouldForceFullCloneMigration(Map<VolumeInfo, DataStore> volumeDataStoreMap, Host destHost) {
2018+
for (Map.Entry<VolumeInfo, DataStore> entry : volumeDataStoreMap.entrySet()) {
2019+
VolumeInfo srcVolumeInfo = entry.getKey();
2020+
DataStore destDataStore = entry.getValue();
2021+
StoragePoolVO sourceStoragePool = _storagePoolDao.findById(srcVolumeInfo.getPoolId());
2022+
StoragePoolVO destStoragePool = _storagePoolDao.findById(destDataStore.getId());
2023+
2024+
if (shouldSkipVolumeMigration(sourceStoragePool, destHost, destStoragePool)) {
2025+
continue;
2026+
}
2027+
2028+
if (srcVolumeInfo.isDirectDownload()) {
2029+
return true;
2030+
}
2031+
}
2032+
return false;
2033+
}
2034+
2035+
protected boolean shouldSkipVolumeMigration(StoragePoolVO sourceStoragePool, Host destHost, StoragePoolVO destStoragePool) {
2036+
return (sourceStoragePool.getId() == destStoragePool.getId() && sourceStoragePool.getPoolType() == Storage.StoragePoolType.PowerFlex) ||
2037+
!shouldMigrateVolume(sourceStoragePool, destHost, destStoragePool);
2038+
}
2039+
20122040
/**
20132041
* For each disk to migrate:
20142042
* <ul>
@@ -2036,6 +2064,10 @@ public void copyAsync(Map<VolumeInfo, DataStore> volumeDataStoreMap, VirtualMach
20362064
List<MigrateDiskInfo> migrateDiskInfoList = new ArrayList<MigrateDiskInfo>();
20372065

20382066
Map<String, MigrateCommand.MigrateDiskInfo> migrateStorage = new HashMap<>();
2067+
boolean forceFullCloneMigration = shouldForceFullCloneMigration(volumeDataStoreMap, destHost);
2068+
if (forceFullCloneMigration) {
2069+
logger.info("Using full clone live storage migration for VM [{}] because one or more migrated volumes are backed by direct-download templates.", vmTO);
2070+
}
20392071

20402072
boolean managedStorageDestination = false;
20412073
boolean migrateNonSharedInc = false;
@@ -2047,16 +2079,12 @@ public void copyAsync(Map<VolumeInfo, DataStore> volumeDataStoreMap, VirtualMach
20472079
StoragePoolVO destStoragePool = _storagePoolDao.findById(destDataStore.getId());
20482080
StoragePoolVO sourceStoragePool = _storagePoolDao.findById(srcVolumeInfo.getPoolId());
20492081

2050-
// do not initiate migration for the same PowerFlex/ScaleIO pool
2051-
if (sourceStoragePool.getId() == destStoragePool.getId() && sourceStoragePool.getPoolType() == Storage.StoragePoolType.PowerFlex) {
2052-
continue;
2053-
}
2054-
2055-
if (!shouldMigrateVolume(sourceStoragePool, destHost, destStoragePool)) {
2082+
if (shouldSkipVolumeMigration(sourceStoragePool, destHost, destStoragePool)) {
20562083
continue;
20572084
}
20582085

2059-
MigrationOptions.Type migrationType = decideMigrationTypeAndCopyTemplateIfNeeded(destHost, vmInstance, srcVolumeInfo, sourceStoragePool, destStoragePool, destDataStore);
2086+
MigrationOptions.Type migrationType = decideMigrationTypeAndCopyTemplateIfNeeded(destHost, vmInstance, srcVolumeInfo, sourceStoragePool, destStoragePool,
2087+
destDataStore, forceFullCloneMigration);
20602088
migrateNonSharedInc = migrateNonSharedInc || MigrationOptions.Type.LinkedClone.equals(migrationType);
20612089

20622090
VolumeVO destVolume = duplicateVolumeOnAnotherStorage(srcVolume, destStoragePool);
@@ -2068,6 +2096,7 @@ public void copyAsync(Map<VolumeInfo, DataStore> volumeDataStoreMap, VirtualMach
20682096
destVolumeInfo.processEvent(Event.MigrationCopySucceeded);
20692097
// move the volume from Ready to Migrating
20702098
destVolumeInfo.processEvent(Event.MigrationRequested);
2099+
srcVolumeInfoToDestVolumeInfo.put(srcVolumeInfo, destVolumeInfo);
20712100

20722101
setVolumeMigrationOptions(srcVolumeInfo, destVolumeInfo, vmTO, srcHost, destStoragePool, migrationType);
20732102

@@ -2112,8 +2141,6 @@ public void copyAsync(Map<VolumeInfo, DataStore> volumeDataStoreMap, VirtualMach
21122141
prepareDiskWithSecretConsumerDetail(vmTO, srcVolumeInfo, destVolumeInfo.getPath());
21132142

21142143
migrateStorage.put(srcVolumeInfo.getPath(), migrateDiskInfo);
2115-
2116-
srcVolumeInfoToDestVolumeInfo.put(srcVolumeInfo, destVolumeInfo);
21172144
}
21182145

21192146
PrepareForMigrationCommand pfmc = new PrepareForMigrationCommand(vmTO);
@@ -2211,7 +2238,13 @@ public void copyAsync(Map<VolumeInfo, DataStore> volumeDataStoreMap, VirtualMach
22112238
}
22122239
}
22132240

2214-
private MigrationOptions.Type decideMigrationTypeAndCopyTemplateIfNeeded(Host destHost, VMInstanceVO vmInstance, VolumeInfo srcVolumeInfo, StoragePoolVO sourceStoragePool, StoragePoolVO destStoragePool, DataStore destDataStore) {
2241+
protected MigrationOptions.Type decideMigrationTypeAndCopyTemplateIfNeeded(Host destHost, VMInstanceVO vmInstance, VolumeInfo srcVolumeInfo, StoragePoolVO sourceStoragePool,
2242+
StoragePoolVO destStoragePool, DataStore destDataStore, boolean forceFullCloneMigration) {
2243+
if (forceFullCloneMigration) {
2244+
logger.debug("Skipping linked clone migration for volume [{}] because the migration request includes a direct-download backed volume.", srcVolumeInfo.getId());
2245+
return MigrationOptions.Type.FullClone;
2246+
}
2247+
22152248
VMTemplateVO vmTemplate = _vmTemplateDao.findById(vmInstance.getTemplateId());
22162249
String srcVolumeBackingFile = getVolumeBackingFile(srcVolumeInfo);
22172250
if (StringUtils.isNotBlank(srcVolumeBackingFile) && supportStoragePoolType(destStoragePool.getPoolType(), StoragePoolType.Filesystem) &&
@@ -2449,7 +2482,11 @@ private VolumeVO duplicateVolumeOnAnotherStorage(Volume volume, StoragePoolVO st
24492482
protected String connectHostToVolume(Host host, long storagePoolId, String iqn) {
24502483
ModifyTargetsCommand modifyTargetsCommand = getModifyTargetsCommand(storagePoolId, iqn, true);
24512484

2452-
return sendModifyTargetsCommand(modifyTargetsCommand, host.getId()).get(0);
2485+
List<String> connectedPaths = sendModifyTargetsCommand(modifyTargetsCommand, host.getId());
2486+
if (CollectionUtils.isEmpty(connectedPaths)) {
2487+
throw new CloudRuntimeException(String.format("Unable to modify targets on the following host: %s because no connected path was returned for target [%s].", host.getId(), iqn));
2488+
}
2489+
return connectedPaths.get(0);
24532490
}
24542491

24552492
private void disconnectHostFromVolume(Host host, long storagePoolId, String iqn) {
@@ -2484,14 +2521,25 @@ private ModifyTargetsCommand getModifyTargetsCommand(long storagePoolId, String
24842521
}
24852522

24862523
private List<String> sendModifyTargetsCommand(ModifyTargetsCommand cmd, long hostId) {
2487-
ModifyTargetsAnswer modifyTargetsAnswer = (ModifyTargetsAnswer)agentManager.easySend(hostId, cmd);
2524+
Answer answer = agentManager.easySend(hostId, cmd);
24882525

2489-
if (modifyTargetsAnswer == null) {
2526+
if (answer == null) {
24902527
throw new CloudRuntimeException("Unable to get an answer to the modify targets command");
24912528
}
24922529

2530+
if (!(answer instanceof ModifyTargetsAnswer)) {
2531+
String details = StringUtils.defaultIfBlank(answer.getDetails(),
2532+
String.format("Unexpected answer type returned: %s", answer.getClass().getName()));
2533+
throw new CloudRuntimeException(String.format("Unable to modify targets on the following host: %s due to [%s]", hostId, details));
2534+
}
2535+
2536+
ModifyTargetsAnswer modifyTargetsAnswer = (ModifyTargetsAnswer)answer;
2537+
24932538
if (!modifyTargetsAnswer.getResult()) {
24942539
String msg = "Unable to modify targets on the following host: " + hostId;
2540+
if (StringUtils.isNotBlank(modifyTargetsAnswer.getDetails())) {
2541+
msg = String.format("%s due to [%s]", msg, modifyTargetsAnswer.getDetails());
2542+
}
24952543

24962544
throw new CloudRuntimeException(msg);
24972545
}
@@ -2504,14 +2552,25 @@ private List<String> sendModifyTargetsCommand(ModifyTargetsCommand cmd, long hos
25042552
*/
25052553
protected void updateCopiedTemplateReference(VolumeInfo srcVolumeInfo, VolumeInfo destVolumeInfo) {
25062554
VMTemplateStoragePoolVO ref = templatePoolDao.findByPoolTemplate(srcVolumeInfo.getPoolId(), srcVolumeInfo.getTemplateId(), null);
2507-
VMTemplateStoragePoolVO newRef = new VMTemplateStoragePoolVO(destVolumeInfo.getPoolId(), ref.getTemplateId(), null);
2508-
newRef.setDownloadPercent(100);
2509-
newRef.setDownloadState(VMTemplateStorageResourceAssoc.Status.DOWNLOADED);
2510-
newRef.setState(ObjectInDataStoreStateMachine.State.Ready);
2511-
newRef.setTemplateSize(ref.getTemplateSize());
2512-
newRef.setLocalDownloadPath(ref.getLocalDownloadPath());
2513-
newRef.setInstallPath(ref.getInstallPath());
2514-
templatePoolDao.persist(newRef);
2555+
if (ref == null) {
2556+
throw new CloudRuntimeException(String.format("Unable to update copied template reference because source template reference was not found for pool [%s] and template [%s].",
2557+
srcVolumeInfo.getPoolId(), srcVolumeInfo.getTemplateId()));
2558+
}
2559+
VMTemplateStoragePoolVO destRef = templatePoolDao.findByPoolTemplate(destVolumeInfo.getPoolId(), ref.getTemplateId(), null);
2560+
if (destRef == null) {
2561+
destRef = new VMTemplateStoragePoolVO(destVolumeInfo.getPoolId(), ref.getTemplateId(), null);
2562+
}
2563+
destRef.setDownloadPercent(100);
2564+
destRef.setDownloadState(VMTemplateStorageResourceAssoc.Status.DOWNLOADED);
2565+
destRef.setState(ObjectInDataStoreStateMachine.State.Ready);
2566+
destRef.setTemplateSize(ref.getTemplateSize());
2567+
destRef.setLocalDownloadPath(ref.getLocalDownloadPath());
2568+
destRef.setInstallPath(ref.getInstallPath());
2569+
if (destRef.getId() == 0) {
2570+
templatePoolDao.persist(destRef);
2571+
} else {
2572+
templatePoolDao.update(destRef.getId(), destRef);
2573+
}
25152574
}
25162575

25172576
/**

engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/KvmNonManagedStorageSystemDataMotionTest.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,29 @@ public void migrateTemplateToTargetStorageIfNeededTestTemplateNotOnTargetHost()
337337
configureAndTestcopyTemplateToTargetStorageIfNeeded(null, StoragePoolType.Filesystem, 1);
338338
}
339339

340+
@Test
341+
public void copyTemplateToTargetStorageIfNeededTestDirectDownloadTemplateAlreadyStaged() {
342+
DataStore destDataStore = Mockito.mock(DataStore.class);
343+
Host destHost = Mockito.mock(Host.class);
344+
VolumeInfo srcVolumeInfo = Mockito.mock(VolumeInfo.class);
345+
StoragePool srcStoragePool = Mockito.mock(StoragePool.class);
346+
StoragePool destStoragePool = Mockito.mock(StoragePool.class);
347+
TemplateInfo directDownloadTemplateInfo = Mockito.mock(TemplateInfo.class);
348+
349+
Mockito.when(destDataStore.getId()).thenReturn(11L);
350+
Mockito.when(destHost.getId()).thenReturn(12L);
351+
Mockito.when(srcVolumeInfo.getTemplateId()).thenReturn(13L);
352+
Mockito.when(srcVolumeInfo.getVolumeType()).thenReturn(Volume.Type.ROOT);
353+
Mockito.when(templateDataFactory.getReadyBypassedTemplateOnPrimaryStore(13L, 11L, 12L)).thenReturn(directDownloadTemplateInfo);
354+
355+
kvmNonManagedStorageDataMotionStrategy.copyTemplateToTargetFilesystemStorageIfNeeded(srcVolumeInfo, srcStoragePool, destDataStore, destStoragePool, destHost);
356+
357+
Mockito.verify(templateDataFactory).getReadyBypassedTemplateOnPrimaryStore(13L, 11L, 12L);
358+
Mockito.verify(vmTemplatePoolDao, Mockito.never()).findByPoolTemplate(Mockito.anyLong(), Mockito.anyLong(), nullable(String.class));
359+
Mockito.verify(dataStoreManagerImpl, Mockito.never()).getRandomImageStore(Mockito.anyLong());
360+
Mockito.verify(kvmNonManagedStorageDataMotionStrategy, Mockito.never()).sendCopyCommand(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());
361+
}
362+
340363
@Test
341364
public void migrateTemplateToTargetStorageIfNeededTestNonDesiredStoragePoolType() throws AgentUnavailableException, OperationTimedoutException {
342365
StoragePoolType[] storagePoolTypeArray = StoragePoolType.values();

0 commit comments

Comments
 (0)