Skip to content

Commit 68030df

Browse files
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
1 parent 6ca6aa1 commit 68030df

File tree

2 files changed

+49
-10
lines changed

2 files changed

+49
-10
lines changed

engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
package com.cloud.vm;
1919

20+
import static com.cloud.configuration.ConfigurationManagerImpl.EXPOSE_ERRORS_TO_USER;
2021
import static com.cloud.configuration.ConfigurationManagerImpl.MIGRATE_VM_ACROSS_CLUSTERS;
2122

2223
import java.lang.reflect.Field;
@@ -931,10 +932,22 @@ public void start(final String vmUuid, final Map<VirtualMachineProfile.Param, Ob
931932
public void start(final String vmUuid, final Map<VirtualMachineProfile.Param, Object> params, final DeploymentPlan planToDeploy, final DeploymentPlanner planner) {
932933
try {
933934
advanceStart(vmUuid, params, planToDeploy, planner);
934-
} catch (ConcurrentOperationException | InsufficientCapacityException e) {
935-
throw new CloudRuntimeException(String.format("Unable to start a VM [%s] due to [%s].", vmUuid, e.getMessage()), e).add(VirtualMachine.class, vmUuid);
935+
} catch (ConcurrentOperationException e) {
936+
final CallContext cctxt = CallContext.current();
937+
final Account account = cctxt.getCallingAccount();
938+
if (canExposeError(account)) {
939+
throw new CloudRuntimeException(String.format("Unable to start a VM [%s] due to [%s].", vmUuid, e.getMessage()), e).add(VirtualMachine.class, vmUuid);
940+
}
941+
throw new CloudRuntimeException(String.format("Unable to start a VM [%s] due to concurrent operation.", vmUuid), e).add(VirtualMachine.class, vmUuid);
942+
} catch (final InsufficientCapacityException e) {
943+
final CallContext cctxt = CallContext.current();
944+
final Account account = cctxt.getCallingAccount();
945+
if (canExposeError(account)) {
946+
throw new CloudRuntimeException(String.format("Unable to start a VM [%s] due to [%s].", vmUuid, e.getMessage()), e).add(VirtualMachine.class, vmUuid);
947+
}
948+
throw new CloudRuntimeException(String.format("Unable to start a VM [%s] due to insufficient capacity.", vmUuid), e).add(VirtualMachine.class, vmUuid);
936949
} catch (final ResourceUnavailableException e) {
937-
if (e.getScope() != null && e.getScope().equals(VirtualRouter.class)){
950+
if (e.getScope() != null && e.getScope().equals(VirtualRouter.class)) {
938951
throw new CloudRuntimeException("Network is unavailable. Please contact administrator", e).add(VirtualMachine.class, vmUuid);
939952
}
940953
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 void orchestrateStart(final String vmUuid, final Map<VirtualMachineProfil
13611374

13621375
final HypervisorGuru hvGuru = _hvGuruMgr.getGuru(vm.getHypervisorType());
13631376

1377+
Throwable lastKnownError = null;
13641378
boolean canRetry = true;
13651379
ExcludeList avoids = null;
13661380
try {
@@ -1384,7 +1398,8 @@ public void orchestrateStart(final String vmUuid, final Map<VirtualMachineProfil
13841398

13851399
int retry = StartRetry.value();
13861400
while (retry-- != 0) {
1387-
logger.debug("Instance start attempt #{}", (StartRetry.value() - retry));
1401+
int attemptNumber = StartRetry.value() - retry;
1402+
logger.debug("Instance start attempt #{}", attemptNumber);
13881403

13891404
if (reuseVolume) {
13901405
final List<VolumeVO> vols = _volsDao.findReadyRootVolumesByInstance(vm.getId());
@@ -1450,8 +1465,13 @@ public void orchestrateStart(final String vmUuid, final Map<VirtualMachineProfil
14501465
reuseVolume = false;
14511466
continue;
14521467
}
1453-
throw new InsufficientServerCapacityException("Unable to create a deployment for " + vmProfile, DataCenter.class, plan.getDataCenterId(),
1454-
areAffinityGroupsAssociated(vmProfile));
1468+
String message = String.format("Unable to create a deployment for %s after %s attempts", vmProfile, attemptNumber);
1469+
if (canExposeError(account) && lastKnownError != null) {
1470+
message += String.format(" Last known error: %s", lastKnownError.getMessage());
1471+
throw new CloudRuntimeException(message, lastKnownError);
1472+
} else {
1473+
throw new InsufficientServerCapacityException(message, DataCenter.class, plan.getDataCenterId(), areAffinityGroupsAssociated(vmProfile));
1474+
}
14551475
}
14561476

14571477
avoids.addHost(dest.getHost().getId());
@@ -1619,11 +1639,15 @@ public void orchestrateStart(final String vmUuid, final Map<VirtualMachineProfil
16191639
throw new ExecutionException("Unable to start VM:" + vm.getUuid() + " due to error in finalizeStart, not retrying");
16201640
}
16211641
}
1622-
logger.info("Unable to start VM on {} due to {}", dest.getHost(), (startAnswer == null ? " no start answer" : startAnswer.getDetails()));
1642+
String msg = String.format("Unable to start VM on %s due to %s", dest.getHost(), startAnswer == null ? "no start command answer" : startAnswer.getDetails());
1643+
lastKnownError = new ExecutionException(msg);
1644+
16231645
if (startAnswer != null && startAnswer.getContextParam("stopRetry") != null) {
1646+
logger.error(msg, lastKnownError);
16241647
break;
16251648
}
16261649

1650+
logger.debug(msg, lastKnownError);
16271651
} catch (OperationTimedoutException e) {
16281652
logger.debug("Unable to send the start command to host {} failed to start VM: {}", dest.getHost(), vm);
16291653
if (e.isActive()) {
@@ -1633,6 +1657,7 @@ public void orchestrateStart(final String vmUuid, final Map<VirtualMachineProfil
16331657
throw new AgentUnavailableException("Unable to start " + vm.getHostName(), destHostId, e);
16341658
} catch (final ResourceUnavailableException e) {
16351659
logger.warn("Unable to contact resource.", e);
1660+
lastKnownError = e;
16361661
if (!avoids.add(e)) {
16371662
if (e.getScope() == Volume.class || e.getScope() == Nic.class) {
16381663
throw e;
@@ -1689,10 +1714,22 @@ public void orchestrateStart(final String vmUuid, final Map<VirtualMachineProfil
16891714
}
16901715

16911716
if (startedVm == null) {
1692-
throw new CloudRuntimeException("Unable to start Instance '" + vm.getHostName() + "' (" + vm.getUuid() + "), see management server log for details");
1717+
String messageTmpl = "Unable to start Instance '%s' (%s)%s";
1718+
String details;
1719+
if (canExposeError(account) && lastKnownError != null) {
1720+
details = ": " + lastKnownError.getMessage();
1721+
} else {
1722+
details = ", see management server log for details";
1723+
}
1724+
String message = String.format(messageTmpl, vm.getHostName(), vm.getUuid(), details);
1725+
throw new CloudRuntimeException(message, lastKnownError);
16931726
}
16941727
}
16951728

1729+
private boolean canExposeError(Account account) {
1730+
return (account != null && account.getType() == Account.Type.ADMIN) || Boolean.TRUE.equals(EXPOSE_ERRORS_TO_USER.value());
1731+
}
1732+
16961733
protected void updateStartCommandWithExternalDetails(Host host, VirtualMachineTO vmTO, StartCommand command) {
16971734
if (!HypervisorType.External.equals(host.getHypervisorType())) {
16981735
return;

server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,9 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
536536
public static final ConfigKey<Boolean> ALLOW_DOMAIN_ADMINS_TO_CREATE_TAGGED_OFFERINGS = new ConfigKey<>(Boolean.class, "allow.domain.admins.to.create.tagged.offerings", "Advanced",
537537
"false", "Allow domain admins to create offerings with tags.", true, ConfigKey.Scope.Account, null);
538538

539+
public static final ConfigKey<Boolean> EXPOSE_ERRORS_TO_USER = new ConfigKey<>(Boolean.class, "expose.errors.to.user", ConfigKey.CATEGORY_ADVANCED,
540+
"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);
541+
539542
public static final ConfigKey<Long> DELETE_QUERY_BATCH_SIZE = new ConfigKey<>("Advanced", Long.class, "delete.query.batch.size", "0",
540543
"Indicates the limit applied while deleting entries in bulk. With this, the delete query will apply the limit as many times as necessary," +
541544
" 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 ConfigKey<?>[] getConfigKeys() {
84948497
BYTES_MAX_READ_LENGTH, BYTES_MAX_WRITE_LENGTH, ADD_HOST_ON_SERVICE_RESTART_KVM, SET_HOST_DOWN_TO_MAINTENANCE,
84958498
VM_SERVICE_OFFERING_MAX_CPU_CORES, VM_SERVICE_OFFERING_MAX_RAM_SIZE, MIGRATE_VM_ACROSS_CLUSTERS,
84968499
ENABLE_ACCOUNT_SETTINGS_FOR_DOMAIN, ENABLE_DOMAIN_SETTINGS_FOR_CHILD_DOMAIN,
8497-
ALLOW_DOMAIN_ADMINS_TO_CREATE_TAGGED_OFFERINGS, DELETE_QUERY_BATCH_SIZE, AllowNonRFC1918CompliantIPs, HostCapacityTypeCpuMemoryWeight
8500+
ALLOW_DOMAIN_ADMINS_TO_CREATE_TAGGED_OFFERINGS, EXPOSE_ERRORS_TO_USER, DELETE_QUERY_BATCH_SIZE, AllowNonRFC1918CompliantIPs, HostCapacityTypeCpuMemoryWeight
84988501
};
84998502
}
85008503

8501-
85028504
/**
85038505
* Returns a string representing the specified configuration's type.
85048506
* @param configName name of the configuration.

0 commit comments

Comments
 (0)