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 68030df10b1 VM start error handling improvements and config to expose 
error to users (#12894)
68030df10b1 is described below

commit 68030df10b1f3a1c203a1b8f65f56817ecb149fc
Author: Suresh Kumar Anaparti <[email protected]>
AuthorDate: Sat Mar 28 00:05:08 2026 +0530

    VM start error handling improvements and config to expose error to users 
(#12894)
    
    * VM start error handling improvements, and config to expose error to user
    
    * refactor
---
 .../com/cloud/vm/VirtualMachineManagerImpl.java    | 53 ++++++++++++++++++----
 .../configuration/ConfigurationManagerImpl.java    |  6 ++-
 2 files changed, 49 insertions(+), 10 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 86f45630611..a6b6802e978 100755
--- 
a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java
+++ 
b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java
@@ -17,6 +17,7 @@
 
 package com.cloud.vm;
 
+import static 
com.cloud.configuration.ConfigurationManagerImpl.EXPOSE_ERRORS_TO_USER;
 import static 
com.cloud.configuration.ConfigurationManagerImpl.MIGRATE_VM_ACROSS_CLUSTERS;
 
 import java.lang.reflect.Field;
@@ -931,10 +932,22 @@ public class VirtualMachineManagerImpl extends 
ManagerBase implements VirtualMac
     public void start(final String vmUuid, final 
Map<VirtualMachineProfile.Param, Object> params, final DeploymentPlan 
planToDeploy, final DeploymentPlanner planner) {
         try {
             advanceStart(vmUuid, params, planToDeploy, planner);
-        } catch (ConcurrentOperationException | InsufficientCapacityException 
e) {
-            throw new CloudRuntimeException(String.format("Unable to start a 
VM [%s] due to [%s].", vmUuid, e.getMessage()), e).add(VirtualMachine.class, 
vmUuid);
+        } catch (ConcurrentOperationException e) {
+            final CallContext cctxt = CallContext.current();
+            final Account account = cctxt.getCallingAccount();
+            if (canExposeError(account)) {
+                throw new CloudRuntimeException(String.format("Unable to start 
a VM [%s] due to [%s].", vmUuid, e.getMessage()), e).add(VirtualMachine.class, 
vmUuid);
+            }
+            throw new CloudRuntimeException(String.format("Unable to start a 
VM [%s] due to concurrent operation.", vmUuid), e).add(VirtualMachine.class, 
vmUuid);
+        } catch (final InsufficientCapacityException e) {
+            final CallContext cctxt = CallContext.current();
+            final Account account = cctxt.getCallingAccount();
+            if (canExposeError(account)) {
+                throw new CloudRuntimeException(String.format("Unable to start 
a VM [%s] due to [%s].", vmUuid, e.getMessage()), e).add(VirtualMachine.class, 
vmUuid);
+            }
+            throw new CloudRuntimeException(String.format("Unable to start a 
VM [%s] due to insufficient capacity.", vmUuid), e).add(VirtualMachine.class, 
vmUuid);
         } catch (final ResourceUnavailableException e) {
-            if (e.getScope() != null && 
e.getScope().equals(VirtualRouter.class)){
+            if (e.getScope() != null && 
e.getScope().equals(VirtualRouter.class)) {
                 throw new CloudRuntimeException("Network is unavailable. 
Please contact administrator", e).add(VirtualMachine.class, vmUuid);
             }
             throw new CloudRuntimeException(String.format("Unable to start a 
VM [%s] due to [%s].", vmUuid, e.getMessage()), e).add(VirtualMachine.class, 
vmUuid);
@@ -1361,6 +1374,7 @@ public class VirtualMachineManagerImpl extends 
ManagerBase implements VirtualMac
 
         final HypervisorGuru hvGuru = 
_hvGuruMgr.getGuru(vm.getHypervisorType());
 
+        Throwable lastKnownError = null;
         boolean canRetry = true;
         ExcludeList avoids = null;
         try {
@@ -1384,7 +1398,8 @@ public class VirtualMachineManagerImpl extends 
ManagerBase implements VirtualMac
 
             int retry = StartRetry.value();
             while (retry-- != 0) {
-                logger.debug("Instance start attempt #{}", (StartRetry.value() 
- retry));
+                int attemptNumber = StartRetry.value() - retry;
+                logger.debug("Instance start attempt #{}", attemptNumber);
 
                 if (reuseVolume) {
                     final List<VolumeVO> vols = 
_volsDao.findReadyRootVolumesByInstance(vm.getId());
@@ -1450,8 +1465,13 @@ public class VirtualMachineManagerImpl extends 
ManagerBase implements VirtualMac
                         reuseVolume = false;
                         continue;
                     }
-                    throw new InsufficientServerCapacityException("Unable to 
create a deployment for " + vmProfile, DataCenter.class, plan.getDataCenterId(),
-                            areAffinityGroupsAssociated(vmProfile));
+                    String message = String.format("Unable to create a 
deployment for %s after %s attempts", vmProfile, attemptNumber);
+                    if (canExposeError(account) && lastKnownError != null) {
+                        message += String.format(" Last known error: %s", 
lastKnownError.getMessage());
+                        throw new CloudRuntimeException(message, 
lastKnownError);
+                    } else {
+                        throw new InsufficientServerCapacityException(message, 
DataCenter.class, plan.getDataCenterId(), 
areAffinityGroupsAssociated(vmProfile));
+                    }
                 }
 
                 avoids.addHost(dest.getHost().getId());
@@ -1619,11 +1639,15 @@ public class VirtualMachineManagerImpl extends 
ManagerBase implements VirtualMac
                             throw new ExecutionException("Unable to start  
VM:" + vm.getUuid() + " due to error in finalizeStart, not retrying");
                         }
                     }
-                    logger.info("Unable to start VM on {} due to {}", 
dest.getHost(), (startAnswer == null ? " no start answer" : 
startAnswer.getDetails()));
+                    String msg = String.format("Unable to start VM on %s due 
to %s", dest.getHost(), startAnswer == null ? "no start command answer" : 
startAnswer.getDetails());
+                    lastKnownError = new ExecutionException(msg);
+
                     if (startAnswer != null && 
startAnswer.getContextParam("stopRetry") != null) {
+                        logger.error(msg, lastKnownError);
                         break;
                     }
 
+                    logger.debug(msg, lastKnownError);
                 } catch (OperationTimedoutException e) {
                     logger.debug("Unable to send the start command to host {} 
failed to start VM: {}", dest.getHost(), vm);
                     if (e.isActive()) {
@@ -1633,6 +1657,7 @@ public class VirtualMachineManagerImpl extends 
ManagerBase implements VirtualMac
                     throw new AgentUnavailableException("Unable to start " + 
vm.getHostName(), destHostId, e);
                 } catch (final ResourceUnavailableException e) {
                     logger.warn("Unable to contact resource.", e);
+                    lastKnownError = e;
                     if (!avoids.add(e)) {
                         if (e.getScope() == Volume.class || e.getScope() == 
Nic.class) {
                             throw e;
@@ -1689,10 +1714,22 @@ public class VirtualMachineManagerImpl extends 
ManagerBase implements VirtualMac
         }
 
         if (startedVm == null) {
-            throw new CloudRuntimeException("Unable to start Instance '" + 
vm.getHostName() + "' (" + vm.getUuid() + "), see management server log for 
details");
+            String messageTmpl = "Unable to start Instance '%s' (%s)%s";
+            String details;
+            if (canExposeError(account) && lastKnownError != null) {
+                details = ": " + lastKnownError.getMessage();
+            } else {
+                details = ", see management server log for details";
+            }
+            String message = String.format(messageTmpl, vm.getHostName(), 
vm.getUuid(), details);
+            throw new CloudRuntimeException(message, lastKnownError);
         }
     }
 
+    private boolean canExposeError(Account account) {
+        return (account != null && account.getType() == Account.Type.ADMIN) || 
Boolean.TRUE.equals(EXPOSE_ERRORS_TO_USER.value());
+    }
+
     protected void updateStartCommandWithExternalDetails(Host host, 
VirtualMachineTO vmTO, StartCommand command) {
         if (!HypervisorType.External.equals(host.getHypervisorType())) {
             return;
diff --git 
a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java 
b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
index 7dbf3e1d2a2..e7306b3a8c5 100644
--- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
+++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
@@ -536,6 +536,9 @@ public class ConfigurationManagerImpl extends ManagerBase 
implements Configurati
     public static final ConfigKey<Boolean> 
ALLOW_DOMAIN_ADMINS_TO_CREATE_TAGGED_OFFERINGS = new ConfigKey<>(Boolean.class, 
"allow.domain.admins.to.create.tagged.offerings", "Advanced",
             "false", "Allow domain admins to create offerings with tags.", 
true, ConfigKey.Scope.Account, null);
 
+    public static final ConfigKey<Boolean> EXPOSE_ERRORS_TO_USER = new 
ConfigKey<>(Boolean.class, "expose.errors.to.user", ConfigKey.CATEGORY_ADVANCED,
+            "false", "If set to true, detailed error messages will be returned 
to all user roles. If false, detailed errors are only shown to admin users", 
true, ConfigKey.Scope.Global, null);
+
     public static final ConfigKey<Long> DELETE_QUERY_BATCH_SIZE = new 
ConfigKey<>("Advanced", Long.class, "delete.query.batch.size", "0",
             "Indicates the limit applied while deleting entries in bulk. With 
this, the delete query will apply the limit as many times as necessary," +
                     " to delete all the entries. This is advised when 
retaining several days of records, which can lead to slowness. <= 0 means that 
no limit will " +
@@ -8494,11 +8497,10 @@ public class ConfigurationManagerImpl extends 
ManagerBase implements Configurati
                 BYTES_MAX_READ_LENGTH, BYTES_MAX_WRITE_LENGTH, 
ADD_HOST_ON_SERVICE_RESTART_KVM, SET_HOST_DOWN_TO_MAINTENANCE,
                 VM_SERVICE_OFFERING_MAX_CPU_CORES, 
VM_SERVICE_OFFERING_MAX_RAM_SIZE, MIGRATE_VM_ACROSS_CLUSTERS,
                 ENABLE_ACCOUNT_SETTINGS_FOR_DOMAIN, 
ENABLE_DOMAIN_SETTINGS_FOR_CHILD_DOMAIN,
-                ALLOW_DOMAIN_ADMINS_TO_CREATE_TAGGED_OFFERINGS, 
DELETE_QUERY_BATCH_SIZE, AllowNonRFC1918CompliantIPs, 
HostCapacityTypeCpuMemoryWeight
+                ALLOW_DOMAIN_ADMINS_TO_CREATE_TAGGED_OFFERINGS, 
EXPOSE_ERRORS_TO_USER, DELETE_QUERY_BATCH_SIZE, AllowNonRFC1918CompliantIPs, 
HostCapacityTypeCpuMemoryWeight
         };
     }
 
-
     /**
      * Returns a string representing the specified configuration's type.
      * @param configName name of the configuration.

Reply via email to