diff --git a/api/src/main/java/com/cloud/configuration/Resource.java b/api/src/main/java/com/cloud/configuration/Resource.java index 97be7f9d64c..460ff5d527b 100644 --- a/api/src/main/java/com/cloud/configuration/Resource.java +++ b/api/src/main/java/com/cloud/configuration/Resource.java @@ -22,32 +22,34 @@ public interface Resource { String UNLIMITED = "Unlimited"; enum ResourceType { // All storage type resources are allocated_storage and not the physical storage. - user_vm("user_vm", 0), - public_ip("public_ip", 1), - volume("volume", 2), - snapshot("snapshot", 3), - template("template", 4), - project("project", 5), - network("network", 6), - vpc("vpc", 7), - cpu("cpu", 8), - memory("memory", 9), - primary_storage("primary_storage", 10), - secondary_storage("secondary_storage", 11), - backup("backup", 12), - backup_storage("backup_storage", 13), - bucket("bucket", 14), - object_storage("object_storage", 15), - gpu("gpu", 16); + user_vm("user_vm", "Instance", 0), + public_ip("public_ip", "Public IP", 1), + volume("volume", "Volume", 2), + snapshot("snapshot", "Snapshot", 3), + template("template", "Template", 4), + project("project", "Project", 5), + network("network", "Network", 6), + vpc("vpc", "VPC", 7), + cpu("cpu", "CPU", 8), + memory("memory", "Memory", 9), + primary_storage("primary_storage", "Primary Storage", 10), + secondary_storage("secondary_storage", "Secondary Storage", 11), + backup("backup", "Backup", 12), + backup_storage("backup_storage", "Backup Storage", 13), + bucket("bucket", "Bucket", 14), + object_storage("object_storage", "Object Storage", 15), + gpu("gpu", "GPU", 16); private String name; + private String displayName; private int ordinal; public static final long bytesToKiB = 1024; public static final long bytesToMiB = bytesToKiB * 1024; public static final long bytesToGiB = bytesToMiB * 1024; - ResourceType(String name, int ordinal) { + ResourceType(String name, String displayName, int ordinal) { this.name = name; + this.displayName = displayName; this.ordinal = ordinal; } @@ -55,6 +57,10 @@ public interface Resource { return name; } + public String getDisplayName() { + return displayName; + } + public int getOrdinal() { return ordinal; } diff --git a/api/src/main/java/org/apache/cloudstack/api/ServerApiException.java b/api/src/main/java/org/apache/cloudstack/api/ServerApiException.java index a8bb2ed71c9..3de14a74024 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ServerApiException.java +++ b/api/src/main/java/org/apache/cloudstack/api/ServerApiException.java @@ -17,6 +17,9 @@ package org.apache.cloudstack.api; import java.util.ArrayList; +import java.util.Map; + +import org.apache.cloudstack.context.ErrorMessageResolver; import com.cloud.exception.CloudException; import com.cloud.utils.exception.CSExceptionErrorCode; @@ -34,6 +37,14 @@ public class ServerApiException extends CloudRuntimeException { setCSErrorCode(CSExceptionErrorCode.getCSErrCode(ServerApiException.class.getName())); } + public ServerApiException(ApiErrorCode errorCode, String messageKey, Map errorMetadata) { + _errorCode = errorCode; + this.messageKey = messageKey; + this.metadata = errorMetadata; + _description = ErrorMessageResolver.getMessage(messageKey, errorMetadata); + setCSErrorCode(CSExceptionErrorCode.getCSErrCode(ServerApiException.class.getName())); + } + public ServerApiException(ApiErrorCode errorCode, String description) { _errorCode = errorCode; _description = description; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java index 81ee00c98a2..beb00c8c03c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java @@ -16,6 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.user.vm; +import java.util.Collections; import java.util.Objects; import java.util.stream.Stream; @@ -157,7 +158,18 @@ public class DeployVMCmd extends BaseDeployVMCmd { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage()); } catch (ResourceAllocationException ex) { logger.warn("Exception: ", ex); - throw new ServerApiException(ApiErrorCode.RESOURCE_ALLOCATION_ERROR, ex.getMessage()); + handleCreateResourceAllocationException(ex); } } + + protected void handleCreateResourceAllocationException(ResourceAllocationException ex) { + if (ex.getMessage() != null && ex.getMessage().startsWith("Maximum amount")) { + throw new ServerApiException(ApiErrorCode.RESOURCE_ALLOCATION_ERROR, + "vm.deploy.resourcelimit.exceeded.account", Collections.emptyMap()); + } else if (ex.getMessage() != null && ex.getMessage().startsWith("Maximum domain resource limits")) { + throw new ServerApiException(ApiErrorCode.RESOURCE_ALLOCATION_ERROR, + "vm.deploy.resourcelimit.exceeded.domain", Collections.emptyMap()); + } + throw new ServerApiException(ApiErrorCode.RESOURCE_ALLOCATION_ERROR, ex.getMessage()); + } } diff --git a/api/src/main/java/org/apache/cloudstack/context/ErrorMessageResolver.java b/api/src/main/java/org/apache/cloudstack/context/ErrorMessageResolver.java index e9f30e2295d..291cefc3c10 100644 --- a/api/src/main/java/org/apache/cloudstack/context/ErrorMessageResolver.java +++ b/api/src/main/java/org/apache/cloudstack/context/ErrorMessageResolver.java @@ -39,7 +39,6 @@ import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import com.cloud.exception.InvalidParameterValueException; import com.cloud.utils.PropertiesUtil; import com.cloud.utils.exception.CloudRuntimeException; import com.fasterxml.jackson.core.type.TypeReference; @@ -287,12 +286,15 @@ public class ErrorMessageResolver { if (key == null) { Throwable cause = cre.getCause(); - if (!(cause instanceof InvalidParameterValueException)) { + if (!(cause instanceof CloudRuntimeException)) { return; } - InvalidParameterValueException ipve = (InvalidParameterValueException) cause; - key = ipve.getMessageKey(); - map = ipve.getMetadata(); + CloudRuntimeException causeEx = (CloudRuntimeException) cause; + key = causeEx.getMessageKey(); + if (key == null) { + return; + } + map = causeEx.getMetadata(); } response.setErrorTextKey(key); String template = getTemplateForKey(key); diff --git a/client/conf/error-messages.json.in b/client/conf/error-messages.json.in index 0dbfbc9008a..aea5cc7ad4f 100644 --- a/client/conf/error-messages.json.in +++ b/client/conf/error-messages.json.in @@ -5,6 +5,10 @@ "vm.deploy.diskoffering.with.diskoffering.details": "Unable to deploy Instance as both a disk offering ID and data disk offering details were provided. Specify only one.", "vm.deploy.hypervisor.volume.snapshot.not.supported": "Unable to deploy Instance because deployment from an existing volume or snapshot is supported only on the KVM hypervisor.", "vm.deploy.network.not.found.ip.map": "The network selected {{networkId}} in IP to network map could not be found. It may have been removed or is no longer accessible.", + "vm.deploy.resourcelimit.exceeded.account": "Unable to deploy Instance because allocating {{resourceRequested}} more {{resourceTypeDisplay}} would exceed the {{resourceOwnerType}} limits. Current: {{resourceAmount}}, Reserved: {{resourceReserved}}, Limit: {{resourceLimit}}. Release unused resources, then retry.", + "vm.deploy.resourcelimit.exceeded.account.admin": "Unable to deploy Instance because allocating {{resourceRequested}} more {{resourceTypeDisplay}} would exceed the {{resourceOwnerType}} limits for {{resourceOwner}}. Current: {{resourceAmount}}, Reserved: {{resourceReserved}}, Limit: {{resourceLimit}}. Release unused resources or increase the limit, then retry.", + "vm.deploy.resourcelimit.exceeded.domain": "Unable to deploy Instance because allocating {{resourceRequested}} more {{resourceTypeDisplay}} would exceed the domain limits. Current: {{resourceAmount}}, Reserved: {{resourceReserved}}, Limit: {{resourceLimit}}. Release unused resources, then retry.", + "vm.deploy.resourcelimit.exceeded.domain.admin": "Unable to deploy Instance because allocating {{resourceRequested}} more {{resourceTypeDisplay}} would exceed the limits for domain {{resourceOwnerDomain}}. Current: {{resourceAmount}}, Reserved: {{resourceReserved}}, Limit: {{resourceLimit}}. Release unused resources or increase the limit, then retry.", "vm.deploy.serviceoffering.fixed.parameters.not.allowed": "Unable to deploy the instance because the selected service offering {{serviceOffering}} does not allow specifying {{cpuNumberKey}}, {{cpuSpeedKey}}, or {{memory}}.", "vm.deploy.serviceoffering.fixed.parameters.not.allowed.admin": "Unable to deploy the instance because the selected service offering {{serviceOffering}} is not a dynamic offering and it does not allow specifying {{cpuNumberKey}}, {{cpuSpeedKey}}, or {{memory}}.", "vm.deploy.serviceoffering.inactive": "Unable to deploy Instance as the given service offering {{serviceOffering}} is inactive. Specify an active service offering.", diff --git a/server/src/main/java/com/cloud/resourcelimit/ResourceLimitManagerImpl.java b/server/src/main/java/com/cloud/resourcelimit/ResourceLimitManagerImpl.java index fad2da89cf2..555747c149e 100644 --- a/server/src/main/java/com/cloud/resourcelimit/ResourceLimitManagerImpl.java +++ b/server/src/main/java/com/cloud/resourcelimit/ResourceLimitManagerImpl.java @@ -558,6 +558,16 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim if (domainResourceLimit != Resource.RESOURCE_UNLIMITED && requestedDomainResourceCount > domainResourceLimit) { String message = "Maximum" + messageSuffix; + Map details = new HashMap<>(); + details.put("resourceTypeDisplay", StringUtils.isBlank(tag) ? type.getDisplayName() : type.getDisplayName() + " (tag: " + tag + ")"); + details.put("resourceOwner", ObjectUtils.firstNonNull(project, account)); + details.put("resourceOwnerDomain", domain); + details.put("resourceOwnerType", project == null ? "Account" : "Project"); + details.put("resourceLimit", convDomainResourceLimit); + details.put("resourceAmount", convCurrentDomainResourceCount); + details.put("resourceReserved", convCurrentResourceReservation); + details.put("resourceRequested", convNumResources); + CallContext.current().putContextParameters(details); ResourceAllocationException e = new ResourceAllocationException(message, type); logger.error(message, e); throw e; @@ -597,6 +607,15 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim if (accountResourceLimit != Resource.RESOURCE_UNLIMITED && requestedResourceCount > accountResourceLimit) { String message = "Maximum" + messageSuffix; + Map details = new HashMap<>(); + details.put("resourceTypeDisplay", StringUtils.isBlank(tag) ? type.getDisplayName() : type.getDisplayName() + " (tag: " + tag + ")"); + details.put("resourceOwner", ObjectUtils.firstNonNull(project, account)); + details.put("resourceOwnerType", project == null ? "Account" : "Project"); + details.put("resourceLimit", convertedAccountResourceLimit); + details.put("resourceAmount", convertedCurrentResourceCount); + details.put("resourceReserved", convertedCurrentResourceReservation); + details.put("resourceRequested", convertedNumResources); + CallContext.current().putContextParameters(details); ResourceAllocationException e = new ResourceAllocationException(message, type); logger.error(message, e); throw e;