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


##########
engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/vmsnapshot/KvmFileBasedStorageVmSnapshotStrategy.java:
##########
@@ -322,16 +327,43 @@ private void 
mergeOldSiblingWithOldParentIfOldParentIsDead(VMSnapshotVO oldParen
         transitStateWithoutThrow(oldParent, 
VMSnapshot.Event.OperationSucceeded);
     }
 
+    protected boolean deleteVMSnapshotWithoutVolumeSnapshots(VMSnapshotVO 
vmSnapshot) {
+        logger.info("VM snapshot [{}] has no volume snapshots on primary 
storage; removing only its metadata.", vmSnapshot.getUuid());
+
+        transitStateWithoutThrow(vmSnapshot, 
VMSnapshot.Event.ExpungeRequested);
+        transitStateWithoutThrow(vmSnapshot, 
VMSnapshot.Event.OperationSucceeded);
+
+        vmSnapshotDetailsDao.removeDetails(vmSnapshot.getId());
+
+        vmSnapshot.setRemoved(DateUtil.now());
+        vmSnapshotDao.update(vmSnapshot.getId(), vmSnapshot);
+
+        return true;
+    }
+
+    protected boolean hasVolumeSnapshotDetails(VMSnapshot vmSnapshot) {
+        return 
CollectionUtils.isNotEmpty(vmSnapshotDetailsDao.findDetails(vmSnapshot.getId(), 
KVM_FILE_BASED_STORAGE_SNAPSHOT));
+    }

Review Comment:
   The new metadata-only deletion path is selected solely based on the absence 
of `kvmFileBasedStorageSnapshot` volume details. If a snapshot has an NVRAM 
sidecar detail (e.g. `kvmFileBasedStorageSnapshotNvram`) but no volume details, 
this will bypass host-side cleanup and only remove DB metadata, potentially 
leaking the NVRAM artifact on storage. Consider gating the metadata-only delete 
on the absence of *any* primary-storage artifacts (volume snapshot details 
*and* NVRAM detail), or explicitly checking/remediating the NVRAM detail before 
taking the metadata-only path.



##########
engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/vmsnapshot/KvmFileBasedStorageVmSnapshotStrategy.java:
##########
@@ -322,16 +327,43 @@ private void 
mergeOldSiblingWithOldParentIfOldParentIsDead(VMSnapshotVO oldParen
         transitStateWithoutThrow(oldParent, 
VMSnapshot.Event.OperationSucceeded);
     }
 
+    protected boolean deleteVMSnapshotWithoutVolumeSnapshots(VMSnapshotVO 
vmSnapshot) {
+        logger.info("VM snapshot [{}] has no volume snapshots on primary 
storage; removing only its metadata.", vmSnapshot.getUuid());
+
+        transitStateWithoutThrow(vmSnapshot, 
VMSnapshot.Event.ExpungeRequested);
+        transitStateWithoutThrow(vmSnapshot, 
VMSnapshot.Event.OperationSucceeded);
+
+        vmSnapshotDetailsDao.removeDetails(vmSnapshot.getId());
+
+        vmSnapshot.setRemoved(DateUtil.now());
+        vmSnapshotDao.update(vmSnapshot.getId(), vmSnapshot);
+
+        return true;
+    }
+
+    protected boolean hasVolumeSnapshotDetails(VMSnapshot vmSnapshot) {
+        return 
CollectionUtils.isNotEmpty(vmSnapshotDetailsDao.findDetails(vmSnapshot.getId(), 
KVM_FILE_BASED_STORAGE_SNAPSHOT));
+    }
+
+    protected boolean isCreatedByThisStrategy(VMSnapshot vmSnapshot) {
+        return vmSnapshotDetailsDao.findDetail(vmSnapshot.getId(), 
KVM_FILE_BASED_STORAGE_SNAPSHOT_STRATEGY) != null;
+    }
+
     @Override
     public StrategyPriority canHandle(VMSnapshot vmSnapshot) {
-        if (!VMSnapshot.State.Allocated.equals(vmSnapshot.getState())) {
-            List<VMSnapshotDetailsVO> vmSnapshotDetails = 
vmSnapshotDetailsDao.findDetails(vmSnapshot.getId(), 
KVM_FILE_BASED_STORAGE_SNAPSHOT);
-            if (CollectionUtils.isEmpty(vmSnapshotDetails)) {
+        VMSnapshot.State state = vmSnapshot.getState();
+        if (!VMSnapshot.State.Allocated.equals(state)) {
+            if (hasVolumeSnapshotDetails(vmSnapshot) || 
isCreatedByThisStrategy(vmSnapshot)) {
+                return StrategyPriority.HIGHEST;
+            }
+            if (!VMSnapshot.State.Error.equals(state)) {
                 logger.debug("KVM file based storage VM snapshot strategy 
cannot handle [{}] as it is not a KVM file based storage VM snapshot.",
                         vmSnapshot.getUuid());
                 return StrategyPriority.CANT_HANDLE;
             }
-            return StrategyPriority.HIGHEST;
+            // Error rows created before the strategy marker existed carry no 
detail at all; fall back to the creation eligibility check.
+            logger.debug("VM snapshot [{}] is in Error state without any KVM 
file based storage details; checking whether this strategy would have created 
it.",
+                    vmSnapshot.getUuid());
         }

Review Comment:
   Given the new branching around ‘volume snapshot details’ vs ‘strategy 
marker’, it would be valuable to add a unit test for the legacy/pre-marker edge 
case where an `Error` snapshot has *NVRAM* detail(s) (e.g. 
`kvmFileBasedStorageSnapshotNvram`) but no `kvmFileBasedStorageSnapshot` volume 
details and no strategy marker. This helps lock in the intended 
ownership/handling behavior and prevents accidental selection of the 
metadata-only deletion path when an on-storage artifact may still exist.



##########
engine/storage/snapshot/src/test/java/org/apache/cloudstack/storage/vmsnapshot/KvmFileBasedStorageVmSnapshotStrategyTest.java:
##########
@@ -462,6 +470,38 @@ public void 
testTakeVmSnapshotInternalFailsWhenHostLacksUefiCapabilityForUefiVm(
         strategy.takeVmSnapshotInternal(vmSnapshot, Collections.emptyMap());
     }
 
+    @Test
+    public void 
testTakeVmSnapshotInternalPersistsStrategyMarkerBeforeHostValidation() throws 
Exception {
+        long vmId = 10L;
+        long vmSnapshotId = 20L;
+        long hostId = 40L;
+
+        UserVmVO userVm = mock(UserVmVO.class);
+        VMSnapshotVO vmSnapshot = mock(VMSnapshotVO.class);
+
+        when(vmSnapshot.getId()).thenReturn(vmSnapshotId);
+        when(vmSnapshot.getVmId()).thenReturn(vmId);
+        when(userVm.getId()).thenReturn(vmId);
+        when(userVm.getUuid()).thenReturn("vm-uuid");
+        when(userVm.getState()).thenReturn(VirtualMachine.State.Running);
+        when(strategy.userVmDao.findById(vmId)).thenReturn(userVm);
+        when(vmSnapshotHelper.pickRunningHost(vmId)).thenReturn(hostId);
+        when(strategy.vmInstanceDetailsDao.findDetail(vmId, 
ApiConstants.BootType.UEFI.toString()))
+                .thenReturn(new VMInstanceDetailVO(vmId, 
ApiConstants.BootType.UEFI.toString(), "SECURE", true));
+        when(hostDetailsDao.findDetail(hostId, 
Host.HOST_UEFI_ENABLE)).thenReturn(null);
+
+        try {
+            strategy.takeVmSnapshotInternal(vmSnapshot, 
Collections.emptyMap());
+        } catch (CloudRuntimeException expected) {
+            // creation is expected to fail; the marker must already be 
persisted
+        }
+
+        InOrder inOrder = inOrder(vmSnapshotHelper, vmSnapshotDetailsDao);
+        inOrder.verify(vmSnapshotHelper).vmSnapshotStateTransitTo(vmSnapshot, 
VMSnapshot.Event.CreateRequested);
+        inOrder.verify(vmSnapshotDetailsDao).addDetail(vmSnapshotId, 
"kvmFileBasedStorageSnapshotStrategy", Boolean.TRUE.toString(), false);

Review Comment:
   The tests introduced several string literals for snapshot detail keys 
(`\"kvmFileBasedStorageSnapshot\"`, `\"kvmFileBasedStorageSnapshotStrategy\"`). 
Since the production code now defines 
`KVM_FILE_BASED_STORAGE_SNAPSHOT_STRATEGY` (and already has constants for other 
keys), using those constants in tests will reduce duplication and prevent 
silent drift/typos if the keys change later.



##########
engine/storage/snapshot/src/test/java/org/apache/cloudstack/storage/vmsnapshot/KvmFileBasedStorageVmSnapshotStrategyTest.java:
##########
@@ -527,6 +567,96 @@ public void 
testNotifyGuestRecoveryIssueIfNeededSendsAlert() {
         
verify(strategy.alertManager).sendAlert(eq(AlertManager.AlertType.ALERT_TYPE_VM_SNAPSHOT),
 eq(1L), eq(2L), anyString(), anyString());
     }
 
+    @Test
+    public void 
testCanHandleReturnsHighestForSnapshotWithVolumeSnapshotDetails() {
+        VMSnapshotVO vmSnapshot = 
mockVmSnapshotForCanHandle(VMSnapshot.State.Error);
+        when(vmSnapshotDetailsDao.findDetails(20L, 
"kvmFileBasedStorageSnapshot"))
+                .thenReturn(List.of(new VMSnapshotDetailsVO(20L, 
"kvmFileBasedStorageSnapshot", "30", true)));
+
+        assertEquals(StrategyPriority.HIGHEST, strategy.canHandle(vmSnapshot));
+
+        verify(strategy, never()).canHandle(anyLong(), any(), anyBoolean());
+    }
+
+    @Test
+    public void 
testCanHandleReturnsHighestForErrorSnapshotWithStrategyMarker() {
+        VMSnapshotVO vmSnapshot = 
mockVmSnapshotForCanHandle(VMSnapshot.State.Error);
+        when(vmSnapshotDetailsDao.findDetails(20L, 
"kvmFileBasedStorageSnapshot")).thenReturn(List.of());
+        when(vmSnapshotDetailsDao.findDetail(20L, 
"kvmFileBasedStorageSnapshotStrategy"))
+                .thenReturn(new VMSnapshotDetailsVO(20L, 
"kvmFileBasedStorageSnapshotStrategy", Boolean.TRUE.toString(), false));

Review Comment:
   The tests introduced several string literals for snapshot detail keys 
(`\"kvmFileBasedStorageSnapshot\"`, `\"kvmFileBasedStorageSnapshotStrategy\"`). 
Since the production code now defines 
`KVM_FILE_BASED_STORAGE_SNAPSHOT_STRATEGY` (and already has constants for other 
keys), using those constants in tests will reduce duplication and prevent 
silent drift/typos if the keys change later.



##########
engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/vmsnapshot/KvmFileBasedStorageVmSnapshotStrategy.java:
##########
@@ -133,8 +134,12 @@ public VMSnapshot takeVMSnapshot(VMSnapshot vmSnapshot) {
     @Override
     public boolean deleteVMSnapshot(VMSnapshot vmSnapshot) {
         logger.info("Starting VM snapshot delete process for snapshot [{}].", 
vmSnapshot.getUuid());
-        UserVmVO userVm = userVmDao.findById(vmSnapshot.getVmId());
         VMSnapshotVO vmSnapshotBeingDeleted = (VMSnapshotVO) vmSnapshot;
+        if (!hasVolumeSnapshotDetails(vmSnapshotBeingDeleted)) {
+            return 
deleteVMSnapshotWithoutVolumeSnapshots(vmSnapshotBeingDeleted);
+        }
+
+        UserVmVO userVm = userVmDao.findById(vmSnapshot.getVmId());
         Long hostId = pickHostForNvramSidecarCleanup(vmSnapshotBeingDeleted, 
userVm, "delete");
         validateHostSupportsNvramSidecarCleanup(vmSnapshotBeingDeleted, 
hostId, "delete");

Review Comment:
   The new metadata-only deletion path is selected solely based on the absence 
of `kvmFileBasedStorageSnapshot` volume details. If a snapshot has an NVRAM 
sidecar detail (e.g. `kvmFileBasedStorageSnapshotNvram`) but no volume details, 
this will bypass host-side cleanup and only remove DB metadata, potentially 
leaking the NVRAM artifact on storage. Consider gating the metadata-only delete 
on the absence of *any* primary-storage artifacts (volume snapshot details 
*and* NVRAM detail), or explicitly checking/remediating the NVRAM detail before 
taking the metadata-only path.



##########
engine/storage/snapshot/src/test/java/org/apache/cloudstack/storage/vmsnapshot/KvmFileBasedStorageVmSnapshotStrategyTest.java:
##########
@@ -527,6 +567,96 @@ public void 
testNotifyGuestRecoveryIssueIfNeededSendsAlert() {
         
verify(strategy.alertManager).sendAlert(eq(AlertManager.AlertType.ALERT_TYPE_VM_SNAPSHOT),
 eq(1L), eq(2L), anyString(), anyString());
     }
 
+    @Test
+    public void 
testCanHandleReturnsHighestForSnapshotWithVolumeSnapshotDetails() {
+        VMSnapshotVO vmSnapshot = 
mockVmSnapshotForCanHandle(VMSnapshot.State.Error);
+        when(vmSnapshotDetailsDao.findDetails(20L, 
"kvmFileBasedStorageSnapshot"))
+                .thenReturn(List.of(new VMSnapshotDetailsVO(20L, 
"kvmFileBasedStorageSnapshot", "30", true)));

Review Comment:
   The tests introduced several string literals for snapshot detail keys 
(`\"kvmFileBasedStorageSnapshot\"`, `\"kvmFileBasedStorageSnapshotStrategy\"`). 
Since the production code now defines 
`KVM_FILE_BASED_STORAGE_SNAPSHOT_STRATEGY` (and already has constants for other 
keys), using those constants in tests will reduce duplication and prevent 
silent drift/typos if the keys change later.



-- 
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