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

DaanHoogland 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 167320eb9fe Propagate StopAnswer error details up the call chain when 
stopping a VM (#13558)
167320eb9fe is described below

commit 167320eb9fe40c8b2abdba0e7952c965eea4beb7
Author: Daman Arora <[email protected]>
AuthorDate: Wed Sep 2 04:11:27 2026 -0400

    Propagate StopAnswer error details up the call chain when stopping a VM 
(#13558)
    
    Co-authored-by: Sachin R <[email protected]>
    Co-authored-by: dahn <[email protected]>
---
 .../com/cloud/vm/VirtualMachineManagerImpl.java    | 87 +++++++++++++---------
 .../cloud/vm/VirtualMachineManagerImplTest.java    | 15 ++--
 2 files changed, 62 insertions(+), 40 deletions(-)

diff --git 
a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java
 
b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java
index b03a33d9eb1..d322507fb4c 100755
--- 
a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java
+++ 
b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java
@@ -1630,9 +1630,10 @@ public class VirtualMachineManagerImpl extends 
ManagerBase implements VirtualMac
                             }
 
                             if (answer == null || !answer.getResult()) {
-                                logger.warn("Unable to stop {} dut to {}", vm, 
(answer != null ? answer.getDetails() : "no answers"));
+                                String errorDetails = (answer != null ? 
answer.getDetails() : "no answers");
+                                logger.warn("Unable to stop {} dut to {}", vm, 
errorDetails);
                                 _haMgr.scheduleStop(vm, destHostId, 
WorkType.ForceStop);
-                                throw new ExecutionException("Unable to stop 
this VM, " + vm.getUuid() + " so we are unable to retry the start operation");
+                                throw new ExecutionException("Unable to stop 
this VM, " + vm.getUuid() + " so we are unable to retry the start operation due 
to " + errorDetails);
                             }
                             throw new ExecutionException("Unable to start  
VM:" + vm.getUuid() + " due to error in finalizeStart, not retrying");
                         }
@@ -2208,7 +2209,7 @@ public class VirtualMachineManagerImpl extends 
ManagerBase implements VirtualMac
         return volumesToDisconnect;
     }
 
-    protected boolean sendStop(final VirtualMachineGuru guru, final 
VirtualMachineProfile profile, final boolean force, final boolean 
checkBeforeCleanup) {
+    protected Pair<Boolean, String> sendStop(final VirtualMachineGuru guru, 
final VirtualMachineProfile profile, final boolean force, final boolean 
checkBeforeCleanup) {
         final VirtualMachine vm = profile.getVirtualMachine();
         Map<String, Boolean> vlanToPersistenceMap = 
getVlanToPersistenceMapForVM(vm.getId());
         StopCommand stpCmd = new StopCommand(vm, 
getExecuteInSequence(vm.getHypervisorType()), checkBeforeCleanup);
@@ -2241,7 +2242,7 @@ public class VirtualMachineManagerImpl extends 
ManagerBase implements VirtualMac
                 if (!answer.getResult()) {
                     final String details = answer.getDetails();
                     logger.debug("Unable to stop VM due to {}", details);
-                    return false;
+                    return new Pair<>(false, details);
                 }
 
                 guru.finalizeStop(profile, answer);
@@ -2254,21 +2255,23 @@ public class VirtualMachineManagerImpl extends 
ManagerBase implements VirtualMac
                     }
                 }
             } else {
-                logger.error("Invalid answer received in response to a 
StopCommand for {}", vm.getInstanceName());
-                return false;
+                String errorMsg = String.format("Invalid answer received in 
response to a StopCommand for %s", vm.getInstanceName());
+                logger.error(errorMsg);
+                return new Pair<>(false, errorMsg);
             }
 
         } catch (final AgentUnavailableException | OperationTimedoutException 
e) {
-            logger.warn("Unable to stop {} due to [{}].", vm.toString(), 
e.getMessage(), e);
+            String errorMsg = String.format("Unable to stop %s due to [%s].", 
vm.toString(), e.getMessage());
+            logger.warn(errorMsg, e);
             if (!force) {
-                return false;
+                return new Pair<>(false, errorMsg);
             }
         }
 
-        return true;
+        return new Pair<>(true, null);
     }
 
-    protected boolean cleanup(final VirtualMachineGuru guru, final 
VirtualMachineProfile profile, final ItWorkVO work, final Event event, final 
boolean cleanUpEvenIfUnableToStop) {
+    protected Pair<Boolean, String> cleanup(final VirtualMachineGuru guru, 
final VirtualMachineProfile profile, final ItWorkVO work, final Event event, 
final boolean cleanUpEvenIfUnableToStop) {
         final VirtualMachine vm = profile.getVirtualMachine();
         final State state = vm.getState();
         logger.debug("Cleaning up resources for the vm {} in {} state", vm, 
state);
@@ -2277,57 +2280,63 @@ public class VirtualMachineManagerImpl extends 
ManagerBase implements VirtualMac
                 if (work != null) {
                     final Step step = work.getStep();
                     if (step == Step.Starting && !cleanUpEvenIfUnableToStop) {
-                        logger.warn("Unable to cleanup vm {}; work state is 
incorrect: {}", vm, step);
-                        return false;
+                        String errorMsg = String.format("Unable to cleanup vm 
%s; work state is incorrect: %s", vm, step);
+                        logger.warn(errorMsg);
+                        return new Pair<>(false, errorMsg);
                     }
 
                     if (step == Step.Started || step == Step.Starting || step 
== Step.Release) {
                         if (vm.getHostId() != null) {
-                            if (!sendStop(guru, profile, 
cleanUpEvenIfUnableToStop, false)) {
+                            Pair<Boolean, String> result = sendStop(guru, 
profile, cleanUpEvenIfUnableToStop, false);
+                            if (!result.first()) {
                                 logger.warn("Failed to stop vm {} in {} state 
as a part of cleanup process", vm, State.Starting);
-                                return false;
+                                return result;
                             }
                         }
                     }
 
                     if (step != Step.Release && step != Step.Prepare && step 
!= Step.Started && step != Step.Starting) {
                         logger.debug("Cleanup is not needed for vm {}; work 
state is incorrect: {}", vm, step);
-                        return true;
+                        return new Pair<>(true, null);
                     }
                 } else {
                     if (vm.getHostId() != null) {
-                        if (!sendStop(guru, profile, 
cleanUpEvenIfUnableToStop, false)) {
+                        Pair<Boolean, String> result = sendStop(guru, profile, 
cleanUpEvenIfUnableToStop, false);
+                        if (!result.first()) {
                             logger.warn("Failed to stop vm {} in {} state as a 
part of cleanup process", vm, State.Starting);
-                            return false;
+                            return result;
                         }
                     }
                 }
 
             } else if (state == State.Stopping) {
                 if (vm.getHostId() != null) {
-                    if (!sendStop(guru, profile, cleanUpEvenIfUnableToStop, 
false)) {
+                    Pair<Boolean, String> result = sendStop(guru, profile, 
cleanUpEvenIfUnableToStop, false);
+                    if (!result.first()) {
                         logger.warn("Failed to stop vm {} in {} state as a 
part of cleanup process", vm, State.Stopping);
-                        return false;
+                        return result;
                     }
                 }
             } else if (state == State.Migrating) {
                 if (vm.getHostId() != null || vm.getLastHostId() != null) {
-                    if (!sendStop(guru, profile, cleanUpEvenIfUnableToStop, 
false)) {
+                    Pair<Boolean, String> result = sendStop(guru, profile, 
cleanUpEvenIfUnableToStop, false);
+                    if (!result.first()) {
                         logger.warn("Failed to stop vm {} in {} state as a 
part of cleanup process", vm, State.Migrating);
-                        return false;
+                        return result;
                     }
                 }
             } else if (state == State.Running) {
-                if (!sendStop(guru, profile, cleanUpEvenIfUnableToStop, 
false)) {
+                Pair<Boolean, String> result = sendStop(guru, profile, 
cleanUpEvenIfUnableToStop, false);
+                if (!result.first()) {
                     logger.warn("Failed to stop vm {} in {} state as a part of 
cleanup process", vm, State.Running);
-                    return false;
+                    return result;
                 }
             }
         } finally {
             releaseVmResources(profile, cleanUpEvenIfUnableToStop);
         }
 
-        return true;
+        return new Pair<>(true, null);
     }
 
     protected void releaseVmResources(final VirtualMachineProfile profile, 
final boolean forced) {
@@ -2509,7 +2518,8 @@ public class VirtualMachineManagerImpl extends 
ManagerBase implements VirtualMac
             logger.warn("Unable to transition the state but we're moving on 
because it's forced stop", e1);
 
             if (doCleanup) {
-                if (cleanup(vmGuru, new VirtualMachineProfileImpl(vm), work, 
Event.StopRequested, cleanUpEvenIfUnableToStop)) {
+                Pair<Boolean, String> cleanupResult = cleanup(vmGuru, new 
VirtualMachineProfileImpl(vm), work, Event.StopRequested, 
cleanUpEvenIfUnableToStop);
+                if (cleanupResult.first()) {
                     try {
                         if (work != null) {
                             logger.debug("Updating work item to Done, id: {}", 
work.getId());
@@ -2524,7 +2534,8 @@ public class VirtualMachineManagerImpl extends 
ManagerBase implements VirtualMac
                     }
                 } else {
                     logger.debug("Failed to cleanup VM: {}", vm);
-                    throw new CloudRuntimeException("Failed to cleanup " + vm 
+ " , current state " + vm.getState());
+                    String errorDetails = cleanupResult.second() != null ? " 
due to " + cleanupResult.second() : "";
+                    throw new CloudRuntimeException("Failed to cleanup " + vm 
+ " , current state " + vm.getState() + errorDetails);
                 }
             }
         }
@@ -2545,6 +2556,7 @@ public class VirtualMachineManagerImpl extends 
ManagerBase implements VirtualMac
 
         boolean stopped = false;
         Answer answer = null;
+        String agentExceptionDetail = null;
         try {
             answer = _agentMgr.send(vm.getHostId(), stop);
             if (answer != null) {
@@ -2572,6 +2584,7 @@ public class VirtualMachineManagerImpl extends 
ManagerBase implements VirtualMac
             }
 
         } catch (AgentUnavailableException | OperationTimedoutException e) {
+            agentExceptionDetail = e.getMessage();
             logger.warn("Unable to stop {} due to [{}].", profile.toString(), 
e.toString(), e);
         } finally {
             if (!stopped) {
@@ -2582,7 +2595,9 @@ public class VirtualMachineManagerImpl extends 
ManagerBase implements VirtualMac
                     } catch (final NoTransitionException e) {
                         logger.warn("Unable to transition the state " + vm, e);
                     }
-                    throw new CloudRuntimeException("Unable to stop " + vm);
+                    String errorDetail = (answer != null && 
answer.getDetails() != null) ? answer.getDetails() : agentExceptionDetail;
+                    String errorDetails = errorDetail != null ? " due to " + 
errorDetail : "";
+                    throw new CloudRuntimeException("Unable to stop " + vm + 
errorDetails);
                 } else {
                     logger.warn("Unable to actually stop {} but continue with 
release because it's a force stop", vm);
                     vmGuru.finalizeStop(profile, answer);
@@ -3261,8 +3276,9 @@ public class VirtualMachineManagerImpl extends 
ManagerBase implements VirtualMac
                     } catch (final AgentUnavailableException e) {
                         logger.error("AgentUnavailableException while cleanup 
on source host: {}", fromHost, e);
                     }
-                    cleanup(vmGuru, new VirtualMachineProfileImpl(vm), work, 
Event.AgentReportStopped, true);
-                    throw new CloudRuntimeException("Unable to complete 
migration for " + vm);
+                    Pair<Boolean, String> cleanupResult = cleanup(vmGuru, new 
VirtualMachineProfileImpl(vm), work, Event.AgentReportStopped, true);
+                    String errorDetails = (cleanupResult.second() != null) ? " 
due to " + cleanupResult.second() : "";
+                    throw new CloudRuntimeException("Unable to complete 
migration for " + vm + errorDetails);
                 }
             } catch (final OperationTimedoutException e) {
                 logger.warn("Error while checking the vm {} on host {}", vm, 
dest.getHost(), e);
@@ -3718,8 +3734,9 @@ public class VirtualMachineManagerImpl extends 
ManagerBase implements VirtualMac
                     } catch (final AgentUnavailableException e) {
                         logger.error("AgentUnavailableException while cleanup 
on source host: {}", srcHost, e);
                     }
-                    cleanup(vmGuru, new VirtualMachineProfileImpl(vm), work, 
Event.AgentReportStopped, true);
-                    throw new CloudRuntimeException("VM not found on 
destination host. Unable to complete migration for " + vm);
+                    Pair<Boolean, String> cleanupResult = cleanup(vmGuru, new 
VirtualMachineProfileImpl(vm), work, Event.AgentReportStopped, true);
+                    String errorDetails = (cleanupResult.second() != null) ? " 
due to " + cleanupResult.second() : "";
+                    throw new CloudRuntimeException("VM not found on 
destination host. Unable to complete migration for " + vm + errorDetails);
                 }
             } catch (final OperationTimedoutException e) {
                 logger.error("Error while checking the vm {} is on host {}", 
vm, destHost, e);
@@ -5003,8 +5020,9 @@ public class VirtualMachineManagerImpl extends 
ManagerBase implements VirtualMac
                     } catch (final AgentUnavailableException e) {
                         logger.error("Unable to cleanup source host [{}] due 
to [{}].", fromHost, e.getMessage(), e);
                     }
-                    cleanup(vmGuru, new VirtualMachineProfileImpl(vm), work, 
Event.AgentReportStopped, true);
-                    throw new CloudRuntimeException("Unable to complete 
migration for " + vm);
+                    Pair<Boolean, String> cleanupResult = cleanup(vmGuru, new 
VirtualMachineProfileImpl(vm), work, Event.AgentReportStopped, true);
+                    String errorDetails = (cleanupResult.second() != null) ? " 
due to " + cleanupResult.second() : "";
+                    throw new CloudRuntimeException("Unable to complete 
migration for " + vm + errorDetails);
                 }
             } catch (final OperationTimedoutException e) {
                 logger.debug("Error while checking the {} on {}", vm, dstHost, 
e);
@@ -5465,7 +5483,8 @@ public class VirtualMachineManagerImpl extends 
ManagerBase implements VirtualMac
             if (PowerState.PowerOff.equals(vm.getPowerState())) {
                 final VirtualMachineGuru vmGuru = getVmGuru(vm);
                 final VirtualMachineProfile profile = new 
VirtualMachineProfileImpl(vm);
-                if (!sendStop(vmGuru, profile, true, true)) {
+                Pair<Boolean, String> result = sendStop(vmGuru, profile, true, 
true);
+                if (!result.first()) {
                     return;
                 } else {
                     // Release resources on StopCommand success
diff --git 
a/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java
 
b/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java
index a07870d09af..7329b67b4be 100644
--- 
a/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java
+++ 
b/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java
@@ -394,9 +394,9 @@ public class VirtualMachineManagerImplTest {
         when(vm.getHostId()).thenReturn(1L);
         when(agentManagerMock.send(anyLong(), 
(Command)any())).thenReturn(answer);
 
-        boolean actual = virtualMachineManagerImpl.sendStop(guru, profile, 
false, false);
+        Pair<Boolean, String> actual = 
virtualMachineManagerImpl.sendStop(guru, profile, false, false);
 
-        Assert.assertTrue(actual);
+        Assert.assertTrue(actual.first());
     }
 
     @Test
@@ -409,9 +409,10 @@ public class VirtualMachineManagerImplTest {
         when(vm.getHostId()).thenReturn(1L);
         when(agentManagerMock.send(anyLong(), 
(Command)any())).thenReturn(answer);
 
-        boolean actual = virtualMachineManagerImpl.sendStop(guru, profile, 
false, false);
+        Pair<Boolean, String> actual = 
virtualMachineManagerImpl.sendStop(guru, profile, false, false);
 
-        assertFalse(actual);
+        assertFalse(actual.first());
+        Assert.assertEquals("fail", actual.second());
     }
 
     @Test
@@ -421,11 +422,13 @@ public class VirtualMachineManagerImplTest {
         VirtualMachineProfile profile = mock(VirtualMachineProfile.class);
         when(profile.getVirtualMachine()).thenReturn(vm);
         when(vm.getHostId()).thenReturn(1L);
+        when(vm.getInstanceName()).thenReturn("test-vm");
         when(agentManagerMock.send(anyLong(), 
(Command)any())).thenReturn(null);
 
-        boolean actual = virtualMachineManagerImpl.sendStop(guru, profile, 
false, false);
+        Pair<Boolean, String> actual = 
virtualMachineManagerImpl.sendStop(guru, profile, false, false);
 
-        assertFalse(actual);
+        assertFalse(actual.first());
+        Assert.assertNotNull(actual.second());
     }
 
     @Test

Reply via email to