Copilot commented on code in PR #13084:
URL: https://github.com/apache/cloudstack/pull/13084#discussion_r3161303674


##########
engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java:
##########
@@ -2009,6 +2009,34 @@ protected void setVolumeMigrationOptions(VolumeInfo 
srcVolumeInfo, VolumeInfo de
         destVolumeInfo.setMigrationOptions(migrationOptions);
     }
 
+    /**
+     * KVM/libvirt selects linked-clone or full-clone storage migration for 
the whole VM migration request.
+     * If any disk is backed by a direct-download template, force the request 
to full clone so libvirt does
+     * not use incremental shared-backing semantics for a disk whose backing 
chain is not guaranteed on the destination.
+     */
+    protected boolean shouldForceFullCloneMigration(Map<VolumeInfo, DataStore> 
volumeDataStoreMap, Host destHost) {
+        for (Map.Entry<VolumeInfo, DataStore> entry : 
volumeDataStoreMap.entrySet()) {
+            VolumeInfo srcVolumeInfo = entry.getKey();
+            DataStore destDataStore = entry.getValue();
+            StoragePoolVO sourceStoragePool = 
_storagePoolDao.findById(srcVolumeInfo.getPoolId());
+            StoragePoolVO destStoragePool = 
_storagePoolDao.findById(destDataStore.getId());
+

Review Comment:
   `shouldForceFullCloneMigration` performs two `_storagePoolDao.findById(...)` 
lookups per volume, and `copyAsync` immediately repeats those same lookups in 
the main loop. This doubles DAO calls for every live migration request. 
Consider computing the “force full clone” decision inside the existing 
`copyAsync` loop (before `decideMigrationType...`) or refactoring to reuse the 
already-fetched `sourceStoragePool`/`destStoragePool` objects to avoid 
redundant database access.



##########
engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategyTest.java:
##########
@@ -210,6 +280,223 @@ public void configureMigrateDiskInfoWithBackingTest() {
         Assert.assertEquals("backingPath", 
migrateDiskInfo.getBackingStoreText());
     }
 
+    @Test
+    public void 
createLinkedCloneMigrationOptionsUsesSourceBackingFileWhenDestinationReferencePathDiffers()
 {
+        VolumeInfo srcVolumeInfo = Mockito.mock(VolumeInfo.class);
+        VolumeInfo destVolumeInfo = Mockito.mock(VolumeInfo.class);
+        StoragePoolVO srcPool = Mockito.mock(StoragePoolVO.class);
+        DataStore srcDataStore = Mockito.mock(DataStore.class);
+        Scope scope = Mockito.mock(Scope.class);
+        VMTemplateStoragePoolVO ref = 
Mockito.mock(VMTemplateStoragePoolVO.class);
+
+        Mockito.when(srcPool.getUuid()).thenReturn("src-pool");
+        
Mockito.when(srcPool.getPoolType()).thenReturn(StoragePoolType.NetworkFilesystem);
+        Mockito.when(srcPool.getClusterId()).thenReturn(1L);
+        Mockito.when(srcVolumeInfo.getTemplateId()).thenReturn(13L);
+        Mockito.when(srcVolumeInfo.getDataStore()).thenReturn(srcDataStore);
+        Mockito.when(srcDataStore.getScope()).thenReturn(scope);
+        Mockito.when(scope.getScopeType()).thenReturn(ScopeType.CLUSTER);
+        Mockito.when(destVolumeInfo.getPoolId()).thenReturn(4L);
+        Mockito.when(templatePoolDao.findByPoolTemplate(4L, 13L, 
null)).thenReturn(ref);
+        Mockito.when(ref.getInstallPath()).thenReturn("target-backing.qcow2");
+
+        MigrationOptions options = 
strategy.createLinkedCloneMigrationOptions(srcVolumeInfo, destVolumeInfo, 
"source-backing.qcow2", srcPool);
+
+        Assert.assertTrue(options.isCopySrcTemplate());
+        Assert.assertEquals("source-backing.qcow2", 
options.getSrcBackingFilePath());
+    }
+
+    @Test
+    public void 
updateCopiedTemplateReferenceUpdatesExistingDestinationReference() {
+        VolumeInfo srcVolumeInfo = Mockito.mock(VolumeInfo.class);
+        VolumeInfo destVolumeInfo = Mockito.mock(VolumeInfo.class);
+        VMTemplateStoragePoolVO srcRef = 
Mockito.mock(VMTemplateStoragePoolVO.class);
+        VMTemplateStoragePoolVO destRef = 
Mockito.mock(VMTemplateStoragePoolVO.class);
+
+        Mockito.when(srcVolumeInfo.getPoolId()).thenReturn(5L);
+        Mockito.when(srcVolumeInfo.getTemplateId()).thenReturn(13L);
+        Mockito.when(destVolumeInfo.getPoolId()).thenReturn(4L);
+        Mockito.when(srcRef.getTemplateId()).thenReturn(13L);
+        Mockito.when(srcRef.getTemplateSize()).thenReturn(1851129856L);
+        
Mockito.when(srcRef.getLocalDownloadPath()).thenReturn("d06b4640-d7d3-45d7-92c1-8cd3c8eb1eb7");
+        
Mockito.when(srcRef.getInstallPath()).thenReturn("d06b4640-d7d3-45d7-92c1-8cd3c8eb1eb7");
+        Mockito.when(destRef.getId()).thenReturn(19L);
+        Mockito.when(templatePoolDao.findByPoolTemplate(5L, 13L, 
null)).thenReturn(srcRef);
+        Mockito.when(templatePoolDao.findByPoolTemplate(4L, 13L, 
null)).thenReturn(destRef);
+
+        strategy.updateCopiedTemplateReference(srcVolumeInfo, destVolumeInfo);
+
+        Mockito.verify(destRef).setDownloadPercent(100);
+        
Mockito.verify(destRef).setDownloadState(VMTemplateStorageResourceAssoc.Status.DOWNLOADED);
+        
Mockito.verify(destRef).setState(ObjectInDataStoreStateMachine.State.Ready);
+        Mockito.verify(destRef).setTemplateSize(1851129856L);
+        
Mockito.verify(destRef).setLocalDownloadPath("d06b4640-d7d3-45d7-92c1-8cd3c8eb1eb7");
+        
Mockito.verify(destRef).setInstallPath("d06b4640-d7d3-45d7-92c1-8cd3c8eb1eb7");
+        Mockito.verify(templatePoolDao).update(19L, destRef);
+        Mockito.verify(templatePoolDao, 
Mockito.never()).persist(Mockito.any(VMTemplateStoragePoolVO.class));
+    }
+
+    @Test
+    public void 
updateCopiedTemplateReferencePersistsDestinationReferenceWhenMissing() {
+        VolumeInfo srcVolumeInfo = Mockito.mock(VolumeInfo.class);
+        VolumeInfo destVolumeInfo = Mockito.mock(VolumeInfo.class);
+        VMTemplateStoragePoolVO srcRef = 
Mockito.mock(VMTemplateStoragePoolVO.class);
+        ArgumentCaptor<VMTemplateStoragePoolVO> captor = 
ArgumentCaptor.forClass(VMTemplateStoragePoolVO.class);
+
+        Mockito.when(srcVolumeInfo.getPoolId()).thenReturn(5L);
+        Mockito.when(srcVolumeInfo.getTemplateId()).thenReturn(13L);
+        Mockito.when(destVolumeInfo.getPoolId()).thenReturn(4L);
+        Mockito.when(srcRef.getTemplateId()).thenReturn(13L);
+        Mockito.when(srcRef.getTemplateSize()).thenReturn(1851129856L);
+        
Mockito.when(srcRef.getLocalDownloadPath()).thenReturn("d06b4640-d7d3-45d7-92c1-8cd3c8eb1eb7");
+        
Mockito.when(srcRef.getInstallPath()).thenReturn("d06b4640-d7d3-45d7-92c1-8cd3c8eb1eb7");
+        Mockito.when(templatePoolDao.findByPoolTemplate(5L, 13L, 
null)).thenReturn(srcRef);
+        Mockito.when(templatePoolDao.findByPoolTemplate(4L, 13L, 
null)).thenReturn(null);
+
+        strategy.updateCopiedTemplateReference(srcVolumeInfo, destVolumeInfo);
+
+        Mockito.verify(templatePoolDao).persist(captor.capture());
+        VMTemplateStoragePoolVO persistedRef = captor.getValue();
+        Assert.assertEquals(4L, persistedRef.getPoolId());
+        Assert.assertEquals(13L, persistedRef.getTemplateId());
+        Assert.assertEquals(100, persistedRef.getDownloadPercent());
+        Assert.assertEquals(VMTemplateStorageResourceAssoc.Status.DOWNLOADED, 
persistedRef.getDownloadState());
+        Assert.assertEquals(ObjectInDataStoreStateMachine.State.Ready, 
persistedRef.getState());
+        Assert.assertEquals(1851129856L, persistedRef.getTemplateSize());
+        Assert.assertEquals("d06b4640-d7d3-45d7-92c1-8cd3c8eb1eb7", 
persistedRef.getLocalDownloadPath());
+        Assert.assertEquals("d06b4640-d7d3-45d7-92c1-8cd3c8eb1eb7", 
persistedRef.getInstallPath());
+        Mockito.verify(templatePoolDao, 
Mockito.never()).update(Mockito.anyLong(), 
Mockito.any(VMTemplateStoragePoolVO.class));
+    }
+
+    @Test
+    public void 
updateCopiedTemplateReferenceThrowsWhenSourceReferenceMissing() {
+        VolumeInfo srcVolumeInfo = Mockito.mock(VolumeInfo.class);
+        VolumeInfo destVolumeInfo = Mockito.mock(VolumeInfo.class);
+
+        Mockito.when(srcVolumeInfo.getPoolId()).thenReturn(5L);
+        Mockito.when(srcVolumeInfo.getTemplateId()).thenReturn(13L);
+        Mockito.when(templatePoolDao.findByPoolTemplate(5L, 13L, 
null)).thenReturn(null);
+
+        try {
+            strategy.updateCopiedTemplateReference(srcVolumeInfo, 
destVolumeInfo);
+            Assert.fail("Expected CloudRuntimeException to be thrown");
+        } catch (CloudRuntimeException e) {
+            Assert.assertTrue(e.getMessage().contains("source template 
reference was not found"));
+            Assert.assertTrue(e.getMessage().contains("pool [5]"));
+            Assert.assertTrue(e.getMessage().contains("template [13]"));
+        }
+    }
+
+    @Test
+    public void 
shouldForceFullCloneMigrationReturnsTrueForMixedDirectDownloadVolumes() {
+        VolumeInfo directDownloadVolume = Mockito.mock(VolumeInfo.class);
+        VolumeInfo regularVolume = Mockito.mock(VolumeInfo.class);
+        DataStore destPrimary = Mockito.mock(DataStore.class);
+        DataStore otherDestPrimary = Mockito.mock(DataStore.class);
+        Host destHost = Mockito.mock(Host.class);
+        Map<VolumeInfo, DataStore> volumeMap = new HashMap<>();
+
+        configurePoolLookup(directDownloadVolume, destPrimary, 1L, 2L, 
StoragePoolType.NetworkFilesystem, StoragePoolType.NetworkFilesystem);
+        configurePoolLookup(regularVolume, otherDestPrimary, 3L, 4L, 
StoragePoolType.NetworkFilesystem, StoragePoolType.NetworkFilesystem);
+        Mockito.when(directDownloadVolume.isDirectDownload()).thenReturn(true);
+        Mockito.when(regularVolume.isDirectDownload()).thenReturn(false);
+
+        volumeMap.put(directDownloadVolume, destPrimary);
+        volumeMap.put(regularVolume, otherDestPrimary);
+
+        Assert.assertTrue(strategy.shouldForceFullCloneMigration(volumeMap, 
destHost));
+    }
+
+    @Test
+    public void 
shouldForceFullCloneMigrationIgnoresSkippedDirectDownloadVolumes() {
+        VolumeInfo skippedDirectDownloadVolume = 
Mockito.mock(VolumeInfo.class);
+        VolumeInfo regularVolume = Mockito.mock(VolumeInfo.class);
+        DataStore samePowerFlexStore = Mockito.mock(DataStore.class);
+        DataStore destPrimary = Mockito.mock(DataStore.class);
+        Host destHost = Mockito.mock(Host.class);
+        Map<VolumeInfo, DataStore> volumeMap = new HashMap<>();
+
+        configurePoolLookup(skippedDirectDownloadVolume, samePowerFlexStore, 
1L, 1L, StoragePoolType.PowerFlex, StoragePoolType.PowerFlex);
+        configurePoolLookup(regularVolume, destPrimary, 3L, 4L, 
StoragePoolType.NetworkFilesystem, StoragePoolType.NetworkFilesystem);

Review Comment:
   In `shouldForceFullCloneMigrationIgnoresSkippedDirectDownloadVolumes`, 
`skippedDirectDownloadVolume` is never stubbed to return `true` from 
`isDirectDownload()`, so the assertion would still pass even if the skip logic 
were removed. To actually verify the “ignored direct-download volume” behavior, 
set 
`Mockito.when(skippedDirectDownloadVolume.isDirectDownload()).thenReturn(true)` 
and keep the expectation `false` due to the PowerFlex same-pool skip.
   ```suggestion
           configurePoolLookup(regularVolume, destPrimary, 3L, 4L, 
StoragePoolType.NetworkFilesystem, StoragePoolType.NetworkFilesystem);
           
Mockito.when(skippedDirectDownloadVolume.isDirectDownload()).thenReturn(true);
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to