This is an automated email from the ASF dual-hosted git repository.

nvazquez pushed a commit to branch 4.22
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/4.22 by this push:
     new bce55945ece Mark VMs in error state when expunge fails during destroy 
operation (#12749)
bce55945ece is described below

commit bce55945ece8ee7453b2ee42426e823b31643d43
Author: Suresh Kumar Anaparti <[email protected]>
AuthorDate: Tue Mar 24 08:59:14 2026 +0530

    Mark VMs in error state when expunge fails during destroy operation (#12749)
    
    * Mark VMs in error state when expunge fails during destroy operation
    
    * fetch volume by external id (used by external plugins)
    
    * review comments
    
    * Update reorder hosts log to DEBUG, log line is too verbose to have on as 
INFO
---
 api/src/main/java/com/cloud/vm/VirtualMachine.java |  3 ++
 .../main/java/com/cloud/storage/dao/VolumeDao.java |  8 ++++
 .../java/com/cloud/storage/dao/VolumeDaoImpl.java  | 12 +++++
 .../deploy/DeploymentPlanningManagerImpl.java      |  4 +-
 .../main/java/com/cloud/vm/UserVmManagerImpl.java  | 31 ++++++++++++-
 .../java/com/cloud/vm/UserVmManagerImplTest.java   | 52 ++++++++++++++++++++++
 6 files changed, 106 insertions(+), 4 deletions(-)

diff --git a/api/src/main/java/com/cloud/vm/VirtualMachine.java 
b/api/src/main/java/com/cloud/vm/VirtualMachine.java
index d244de7115e..41c9a864c9d 100644
--- a/api/src/main/java/com/cloud/vm/VirtualMachine.java
+++ b/api/src/main/java/com/cloud/vm/VirtualMachine.java
@@ -124,6 +124,9 @@ public interface VirtualMachine extends RunningOn, 
ControlledEntity, Partition,
             s_fsm.addTransition(new Transition<State, Event>(State.Stopping, 
VirtualMachine.Event.StopRequested, State.Stopping, null));
             s_fsm.addTransition(new Transition<State, Event>(State.Stopping, 
VirtualMachine.Event.AgentReportShutdowned, State.Stopped, Arrays.asList(new 
Impact[]{Impact.USAGE})));
             s_fsm.addTransition(new Transition<State, Event>(State.Expunging, 
VirtualMachine.Event.OperationFailed, State.Expunging,null));
+            // Note: In addition to the Stopped -> Error transition for failed 
VM creation,
+            // a VM can also transition from Expunging to Error on 
OperationFailedToError.
+            s_fsm.addTransition(new Transition<State, Event>(State.Expunging, 
VirtualMachine.Event.OperationFailedToError, State.Error, null));
             s_fsm.addTransition(new Transition<State, Event>(State.Expunging, 
VirtualMachine.Event.ExpungeOperation, State.Expunging,null));
             s_fsm.addTransition(new Transition<State, Event>(State.Error, 
VirtualMachine.Event.DestroyRequested, State.Expunging, null));
             s_fsm.addTransition(new Transition<State, Event>(State.Error, 
VirtualMachine.Event.ExpungeOperation, State.Expunging, null));
diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDao.java 
b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDao.java
index a03b94faa79..717e3e782f2 100644
--- a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDao.java
+++ b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDao.java
@@ -166,4 +166,12 @@ public interface VolumeDao extends GenericDao<VolumeVO, 
Long>, StateDao<Volume.S
     int getVolumeCountByOfferingId(long diskOfferingId);
 
     VolumeVO findByLastIdAndState(long lastVolumeId, Volume.State...states);
+
+    /**
+     *  Retrieves volume by its externalId
+     *
+     * @param externalUuid
+     * @return Volume Object of matching search criteria
+     */
+    VolumeVO findByExternalUuid(String externalUuid);
 }
diff --git 
a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java 
b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java
index 727c4fe8ef2..fce4d1f7233 100644
--- a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java
+++ b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java
@@ -74,6 +74,7 @@ public class VolumeDaoImpl extends GenericDaoBase<VolumeVO, 
Long> implements Vol
     private final SearchBuilder<VolumeVO> storeAndInstallPathSearch;
     private final SearchBuilder<VolumeVO> volumeIdSearch;
     protected GenericSearchBuilder<VolumeVO, Long> CountByAccount;
+    protected final SearchBuilder<VolumeVO> ExternalUuidSearch;
     protected GenericSearchBuilder<VolumeVO, SumCount> primaryStorageSearch;
     protected GenericSearchBuilder<VolumeVO, SumCount> primaryStorageSearch2;
     protected GenericSearchBuilder<VolumeVO, SumCount> secondaryStorageSearch;
@@ -459,6 +460,10 @@ public class VolumeDaoImpl extends 
GenericDaoBase<VolumeVO, Long> implements Vol
         CountByAccount.and("idNIN", CountByAccount.entity().getId(), Op.NIN);
         CountByAccount.done();
 
+        ExternalUuidSearch = createSearchBuilder();
+        ExternalUuidSearch.and("externalUuid", 
ExternalUuidSearch.entity().getExternalUuid(), Op.EQ);
+        ExternalUuidSearch.done();
+
         primaryStorageSearch = createSearchBuilder(SumCount.class);
         primaryStorageSearch.select("sum", Func.SUM, 
primaryStorageSearch.entity().getSize());
         primaryStorageSearch.and("accountId", 
primaryStorageSearch.entity().getAccountId(), Op.EQ);
@@ -934,4 +939,11 @@ public class VolumeDaoImpl extends 
GenericDaoBase<VolumeVO, Long> implements Vol
         sc.and(sc.entity().getState(), SearchCriteria.Op.IN,  (Object[]) 
states);
         return sc.find();
     }
+
+    @Override
+    public VolumeVO findByExternalUuid(String externalUuid) {
+        SearchCriteria<VolumeVO> sc = ExternalUuidSearch.create();
+        sc.setParameters("externalUuid", externalUuid);
+        return findOneBy(sc);
+    }
 }
diff --git 
a/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java 
b/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java
index 230f97f6fd3..f163a3d52a5 100644
--- a/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java
+++ b/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java
@@ -1714,7 +1714,7 @@ StateListener<State, VirtualMachine.Event, 
VirtualMachine>, Configurable {
 
     @Override
     public void reorderHostsByPriority(Map<Long, Integer> priorities, 
List<Host> hosts) {
-        logger.info("Re-ordering hosts {} by priorities {}", hosts, 
priorities);
+        logger.debug("Re-ordering hosts {} by priorities {}", hosts, 
priorities);
 
         hosts.removeIf(host -> 
DataCenterDeployment.PROHIBITED_HOST_PRIORITY.equals(getHostPriority(priorities,
 host.getId())));
 
@@ -1727,7 +1727,7 @@ StateListener<State, VirtualMachine.Event, 
VirtualMachine>, Configurable {
                 }
         );
 
-        logger.info("Hosts after re-ordering are: {}", hosts);
+        logger.debug("Hosts after re-ordering are: {}", hosts);
     }
 
     private Integer getHostPriority(Map<Long, Integer> priorities, Long 
hostId) {
diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java 
b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
index 49761905f00..0c924dcced6 100644
--- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
+++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
@@ -2578,6 +2578,22 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
         }
     }
 
+    private void transitionExpungingToError(long vmId) {
+        UserVmVO vm = _vmDao.findById(vmId);
+        if (vm != null && vm.getState() == State.Expunging) {
+            try {
+                boolean transitioned = _itMgr.stateTransitTo(vm, 
VirtualMachine.Event.OperationFailedToError, null);
+                if (transitioned) {
+                    logger.info("Transitioned VM [{}] from Expunging to Error 
after failed expunge", vm.getUuid());
+                } else {
+                    logger.warn("Failed to persist transition of VM [{}] from 
Expunging to Error after failed expunge, possibly due to concurrent update", 
vm.getUuid());
+                }
+            } catch (NoTransitionException e) {
+                logger.warn("Failed to transition VM {} to Error state: {}", 
vm, e.getMessage());
+            }
+        }
+    }
+
     /**
      * Release network resources, it was done on vm stop previously.
      * @param id vm id
@@ -3561,8 +3577,19 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
         detachVolumesFromVm(vm, dataVols);
 
         UserVm destroyedVm = destroyVm(vmId, expunge);
-        if (expunge && !expunge(vm)) {
-            throw new CloudRuntimeException("Failed to expunge vm " + 
destroyedVm);
+        if (expunge) {
+            boolean expunged = false;
+            String errorMsg = "";
+            try {
+                expunged = expunge(vm);
+            } catch (RuntimeException e) {
+                logger.error("Failed to expunge VM [{}] due to: {}", vm, 
e.getMessage(), e);
+                errorMsg = e.getMessage();
+            }
+            if (!expunged) {
+                transitionExpungingToError(vm.getId());
+                throw new CloudRuntimeException("Failed to expunge VM " + 
vm.getUuid() + (StringUtils.isNotBlank(errorMsg) ? " due to: " + errorMsg : 
""));
+            }
         }
 
         autoScaleManager.removeVmFromVmGroup(vmId);
diff --git a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java 
b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java
index 1ea78bff3db..21bcec59a5d 100644
--- a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java
+++ b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java
@@ -60,6 +60,7 @@ import java.util.TimeZone;
 import java.util.UUID;
 
 import com.cloud.storage.dao.SnapshotPolicyDao;
+import com.cloud.utils.fsm.NoTransitionException;
 import org.apache.cloudstack.acl.ControlledEntity;
 import org.apache.cloudstack.acl.SecurityChecker;
 import org.apache.cloudstack.api.ApiCommandResourceType;
@@ -4177,4 +4178,55 @@ public class UserVmManagerImplTest {
         verify(userVmDao, times(1)).releaseFromLockTable(vmId);
     }
 
+    @Test
+    public void testTransitionExpungingToErrorVmInExpungingState() throws 
Exception {
+        UserVmVO vm = mock(UserVmVO.class);
+        when(vm.getState()).thenReturn(VirtualMachine.State.Expunging);
+        when(vm.getUuid()).thenReturn("test-uuid");
+        when(userVmDao.findById(vmId)).thenReturn(vm);
+        when(virtualMachineManager.stateTransitTo(eq(vm), 
eq(VirtualMachine.Event.OperationFailedToError), eq(null))).thenReturn(true);
+
+        java.lang.reflect.Method method = 
UserVmManagerImpl.class.getDeclaredMethod("transitionExpungingToError", 
long.class);
+        method.setAccessible(true);
+        method.invoke(userVmManagerImpl, vmId);
+
+        Mockito.verify(virtualMachineManager).stateTransitTo(vm, 
VirtualMachine.Event.OperationFailedToError, null);
+    }
+
+    @Test
+    public void testTransitionExpungingToErrorVmNotInExpungingState() throws 
Exception {
+        UserVmVO vm = mock(UserVmVO.class);
+        when(vm.getState()).thenReturn(VirtualMachine.State.Stopped);
+        when(userVmDao.findById(vmId)).thenReturn(vm);
+
+        java.lang.reflect.Method method = 
UserVmManagerImpl.class.getDeclaredMethod("transitionExpungingToError", 
long.class);
+        method.setAccessible(true);
+        method.invoke(userVmManagerImpl, vmId);
+
+        Mockito.verify(virtualMachineManager, 
Mockito.never()).stateTransitTo(any(VirtualMachine.class), 
any(VirtualMachine.Event.class), any());
+    }
+
+    @Test
+    public void testTransitionExpungingToErrorVmNotFound() throws Exception {
+        when(userVmDao.findById(vmId)).thenReturn(null);
+
+        java.lang.reflect.Method method = 
UserVmManagerImpl.class.getDeclaredMethod("transitionExpungingToError", 
long.class);
+        method.setAccessible(true);
+        method.invoke(userVmManagerImpl, vmId);
+
+        Mockito.verify(virtualMachineManager, 
Mockito.never()).stateTransitTo(any(VirtualMachine.class), 
any(VirtualMachine.Event.class), any());
+    }
+
+    @Test
+    public void testTransitionExpungingToErrorHandlesNoTransitionException() 
throws Exception {
+        UserVmVO vm = mock(UserVmVO.class);
+        when(vm.getState()).thenReturn(VirtualMachine.State.Expunging);
+        when(userVmDao.findById(vmId)).thenReturn(vm);
+        when(virtualMachineManager.stateTransitTo(eq(vm), 
eq(VirtualMachine.Event.OperationFailedToError), eq(null)))
+                .thenThrow(new NoTransitionException("no transition"));
+
+        java.lang.reflect.Method method = 
UserVmManagerImpl.class.getDeclaredMethod("transitionExpungingToError", 
long.class);
+        method.setAccessible(true);
+        method.invoke(userVmManagerImpl, vmId);
+    }
 }

Reply via email to