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

dahn 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 160876c6d7d Fix: API Thread held forever during force deleting across 
MS (#12968)
160876c6d7d is described below

commit 160876c6d7d31ed2ba2ad9233fa3b40371e070e9
Author: Nicolas Vazquez <[email protected]>
AuthorDate: Wed Apr 15 03:41:26 2026 -0300

    Fix: API Thread held forever during force deleting across MS (#12968)
---
 .../agent/api/PropagateResourceEventCommand.java   | 17 ++++++++++++++
 .../java/com/cloud/resource/ResourceManager.java   |  2 ++
 .../agent/manager/ClusteredAgentManagerImpl.java   | 11 ++++++++-
 .../com/cloud/resource/ResourceManagerImpl.java    | 24 +++++++++++++++----
 .../cloud/resource/MockResourceManagerImpl.java    |  5 ++++
 .../cloud/resource/ResourceManagerImplTest.java    | 27 ++++++++++++++++++++++
 6 files changed, 81 insertions(+), 5 deletions(-)

diff --git 
a/core/src/main/java/com/cloud/agent/api/PropagateResourceEventCommand.java 
b/core/src/main/java/com/cloud/agent/api/PropagateResourceEventCommand.java
index ed337885bee..21c4e7b97d0 100644
--- a/core/src/main/java/com/cloud/agent/api/PropagateResourceEventCommand.java
+++ b/core/src/main/java/com/cloud/agent/api/PropagateResourceEventCommand.java
@@ -24,6 +24,8 @@ import com.cloud.resource.ResourceState;
 public class PropagateResourceEventCommand extends Command {
     long hostId;
     ResourceState.Event event;
+    boolean forced;
+    boolean forceDeleteStorage;
 
     protected PropagateResourceEventCommand() {
 
@@ -34,6 +36,13 @@ public class PropagateResourceEventCommand extends Command {
         this.event = event;
     }
 
+    public PropagateResourceEventCommand(long hostId, ResourceState.Event 
event, boolean forced, boolean forceDeleteStorage) {
+        this.hostId = hostId;
+        this.event = event;
+        this.forced = forced;
+        this.forceDeleteStorage = forceDeleteStorage;
+    }
+
     public long getHostId() {
         return hostId;
     }
@@ -42,6 +51,14 @@ public class PropagateResourceEventCommand extends Command {
         return event;
     }
 
+    public boolean isForced() {
+        return forced;
+    }
+
+    public boolean isForceDeleteStorage() {
+        return forceDeleteStorage;
+    }
+
     @Override
     public boolean executeInSequence() {
         // TODO Auto-generated method stub
diff --git 
a/engine/components-api/src/main/java/com/cloud/resource/ResourceManager.java 
b/engine/components-api/src/main/java/com/cloud/resource/ResourceManager.java
index e724f5d081b..4767e86e8ab 100755
--- 
a/engine/components-api/src/main/java/com/cloud/resource/ResourceManager.java
+++ 
b/engine/components-api/src/main/java/com/cloud/resource/ResourceManager.java
@@ -122,6 +122,8 @@ public interface ResourceManager extends ResourceService, 
Configurable {
 
     public boolean executeUserRequest(long hostId, ResourceState.Event event) 
throws AgentUnavailableException;
 
+    boolean executeUserRequest(long hostId, ResourceState.Event event, boolean 
isForced, boolean isForceDeleteStorage) throws AgentUnavailableException;
+
     boolean resourceStateTransitTo(Host host, Event event, long msId) throws 
NoTransitionException;
 
     boolean umanageHost(long hostId);
diff --git 
a/engine/orchestration/src/main/java/com/cloud/agent/manager/ClusteredAgentManagerImpl.java
 
b/engine/orchestration/src/main/java/com/cloud/agent/manager/ClusteredAgentManagerImpl.java
index ffc993645ad..e62e5ad065d 100644
--- 
a/engine/orchestration/src/main/java/com/cloud/agent/manager/ClusteredAgentManagerImpl.java
+++ 
b/engine/orchestration/src/main/java/com/cloud/agent/manager/ClusteredAgentManagerImpl.java
@@ -1307,11 +1307,20 @@ public class ClusteredAgentManagerImpl extends 
AgentManagerImpl implements Clust
 
                 boolean result;
                 try {
-                    result = _resourceMgr.executeUserRequest(cmd.getHostId(), 
cmd.getEvent());
+                    result = _resourceMgr.executeUserRequest(cmd.getHostId(), 
cmd.getEvent(), cmd.isForced(), cmd.isForceDeleteStorage());
                     logger.debug("Result is {}", result);
                 } catch (final AgentUnavailableException ex) {
                     logger.warn("Agent is unavailable", ex);
                     return null;
+                } catch (final RuntimeException ex) {
+                    logger.error(String.format("Failed to execute propagated 
event %s for host %d", cmd.getEvent().name(), cmd.getHostId()), ex);
+                    final Answer[] answers = new Answer[1];
+                    String details = ex.getMessage();
+                    if (details == null || details.isEmpty()) {
+                        details = ex.toString();
+                    }
+                    answers[0] = new Answer(cmd, false, details);
+                    return _gson.toJson(answers);
                 }
 
                 final Answer[] answers = new Answer[1];
diff --git a/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java 
b/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java
index 621b110486b..87059badbec 100755
--- a/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java
+++ b/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java
@@ -1169,7 +1169,7 @@ public class ResourceManagerImpl extends ManagerBase 
implements ResourceManager,
     @Override
     public boolean deleteHost(final long hostId, final boolean isForced, final 
boolean isForceDeleteStorage) {
         try {
-            final Boolean result = propagateResourceEvent(hostId, 
ResourceState.Event.DeleteHost);
+            final Boolean result = propagateResourceEvent(hostId, 
ResourceState.Event.DeleteHost, isForced, isForceDeleteStorage);
             if (result != null) {
                 return result;
             }
@@ -3902,13 +3902,18 @@ public class ResourceManagerImpl extends ManagerBase 
implements ResourceManager,
     }
 
     @Override
-    public boolean executeUserRequest(final long hostId, final 
ResourceState.Event event) {
+    public boolean executeUserRequest(final long hostId, final 
ResourceState.Event event) throws AgentUnavailableException {
+        return executeUserRequest(hostId, event, false, false);
+    }
+
+    @Override
+    public boolean executeUserRequest(final long hostId, final 
ResourceState.Event event, final boolean isForced, final boolean 
isForceDeleteStorage) throws AgentUnavailableException {
         if (event == ResourceState.Event.AdminAskMaintenance) {
             return doMaintain(hostId);
         } else if (event == ResourceState.Event.AdminCancelMaintenance) {
             return doCancelMaintenance(hostId);
         } else if (event == ResourceState.Event.DeleteHost) {
-            return doDeleteHost(hostId, false, false);
+            return doDeleteHost(hostId, isForced, isForceDeleteStorage);
         } else if (event == ResourceState.Event.Unmanaged) {
             return doUmanageHost(hostId);
         } else if (event == ResourceState.Event.UpdatePassword) {
@@ -4028,6 +4033,10 @@ public class ResourceManagerImpl extends ManagerBase 
implements ResourceManager,
     }
 
     public Boolean propagateResourceEvent(final long agentId, final 
ResourceState.Event event) throws AgentUnavailableException {
+        return propagateResourceEvent(agentId, event, false, false);
+    }
+
+    public Boolean propagateResourceEvent(final long agentId, final 
ResourceState.Event event, final boolean isForced, final boolean 
isForceDeleteStorage) throws AgentUnavailableException {
         final String msPeer = getPeerName(agentId);
         if (msPeer == null) {
             return null;
@@ -4035,7 +4044,7 @@ public class ResourceManagerImpl extends ManagerBase 
implements ResourceManager,
 
         logger.debug("Propagating resource request event:" + event.toString() 
+ " to agent:" + agentId);
         final Command[] cmds = new Command[1];
-        cmds[0] = new PropagateResourceEventCommand(agentId, event);
+        cmds[0] = new PropagateResourceEventCommand(agentId, event, isForced, 
isForceDeleteStorage);
 
         final String AnsStr = _clusterMgr.execute(msPeer, agentId, 
_gson.toJson(cmds), true);
         if (AnsStr == null) {
@@ -4048,6 +4057,13 @@ public class ResourceManagerImpl extends ManagerBase 
implements ResourceManager,
             logger.debug("Result for agent change is " + 
answers[0].getResult());
         }
 
+        if (!answers[0].getResult()) {
+            final String details = answers[0].getDetails();
+            if (details != null && !details.isEmpty()) {
+                throw new CloudRuntimeException(String.format("Failed to 
propagate resource event %s for host %d on peer %s: %s", event, agentId, 
msPeer, details));
+            }
+        }
+
         return answers[0].getResult();
     }
 
diff --git 
a/server/src/test/java/com/cloud/resource/MockResourceManagerImpl.java 
b/server/src/test/java/com/cloud/resource/MockResourceManagerImpl.java
index 6373c3277d0..8b62861165f 100755
--- a/server/src/test/java/com/cloud/resource/MockResourceManagerImpl.java
+++ b/server/src/test/java/com/cloud/resource/MockResourceManagerImpl.java
@@ -318,6 +318,11 @@ public class MockResourceManagerImpl extends ManagerBase 
implements ResourceMana
         return false;
     }
 
+    @Override
+    public boolean executeUserRequest(long hostId, Event event, boolean 
isForced, boolean isForceDeleteStorage) throws AgentUnavailableException {
+        return false;
+    }
+
     /* (non-Javadoc)
      * @see 
com.cloud.resource.ResourceManager#resourceStateTransitTo(com.cloud.host.Host, 
com.cloud.resource.ResourceState.Event, long)
      */
diff --git 
a/server/src/test/java/com/cloud/resource/ResourceManagerImplTest.java 
b/server/src/test/java/com/cloud/resource/ResourceManagerImplTest.java
index 3937a595069..30a021591a5 100644
--- a/server/src/test/java/com/cloud/resource/ResourceManagerImplTest.java
+++ b/server/src/test/java/com/cloud/resource/ResourceManagerImplTest.java
@@ -1184,4 +1184,31 @@ public class ResourceManagerImplTest {
         Mockito.verify(host).setStorageAccessGroups("group1,group2");
         Mockito.verify(hostDao).update(hostId, host);
     }
+
+    @Test
+    public void executeUserRequestDeleteHostPassesForcedFlags() throws 
Exception {
+        Mockito.doReturn(true).when(resourceManager).doDeleteHost(anyLong(), 
anyBoolean(), anyBoolean());
+
+        resourceManager.executeUserRequest(hostId, 
ResourceState.Event.DeleteHost, true, true);
+
+        Mockito.verify(resourceManager).doDeleteHost(hostId, true, true);
+    }
+
+    @Test
+    public void executeUserRequestDeleteHostPassesNonForcedFlags() throws 
Exception {
+        Mockito.doReturn(true).when(resourceManager).doDeleteHost(anyLong(), 
anyBoolean(), anyBoolean());
+
+        resourceManager.executeUserRequest(hostId, 
ResourceState.Event.DeleteHost, false, false);
+
+        Mockito.verify(resourceManager).doDeleteHost(hostId, false, false);
+    }
+
+    @Test
+    public void executeUserRequestDefaultOverloadPassesFalseForDeleteHost() 
throws Exception {
+        Mockito.doReturn(true).when(resourceManager).doDeleteHost(anyLong(), 
anyBoolean(), anyBoolean());
+
+        resourceManager.executeUserRequest(hostId, 
ResourceState.Event.DeleteHost);
+
+        Mockito.verify(resourceManager).doDeleteHost(hostId, false, false);
+    }
 }

Reply via email to