Skip to content

Commit e527860

Browse files
committed
framework,api,server,ui: improve error messaging
Framework-level improvements for for contextual and localized API error messages Design doc: https://cwiki.apache.org/confluence/display/CLOUDSTACK/Error+Message+Consistency%2C+Customization%2C+and+Localization+Framework Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>
1 parent b7a834b commit e527860

41 files changed

Lines changed: 3603 additions & 1169 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎api/src/main/java/com/cloud/configuration/Resource.java‎

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,39 +22,45 @@ public interface Resource {
2222
String UNLIMITED = "Unlimited";
2323

2424
enum ResourceType { // All storage type resources are allocated_storage and not the physical storage.
25-
user_vm("user_vm", 0),
26-
public_ip("public_ip", 1),
27-
volume("volume", 2),
28-
snapshot("snapshot", 3),
29-
template("template", 4),
30-
project("project", 5),
31-
network("network", 6),
32-
vpc("vpc", 7),
33-
cpu("cpu", 8),
34-
memory("memory", 9),
35-
primary_storage("primary_storage", 10),
36-
secondary_storage("secondary_storage", 11),
37-
backup("backup", 12),
38-
backup_storage("backup_storage", 13),
39-
bucket("bucket", 14),
40-
object_storage("object_storage", 15),
41-
gpu("gpu", 16);
25+
user_vm("user_vm", "Instance", 0),
26+
public_ip("public_ip", "Public IP", 1),
27+
volume("volume", "Volume", 2),
28+
snapshot("snapshot", "Snapshot", 3),
29+
template("template", "Template", 4),
30+
project("project", "Project", 5),
31+
network("network", "Network", 6),
32+
vpc("vpc", "VPC", 7),
33+
cpu("cpu", "CPU", 8),
34+
memory("memory", "Memory", 9),
35+
primary_storage("primary_storage", "Primary Storage", 10),
36+
secondary_storage("secondary_storage", "Secondary Storage", 11),
37+
backup("backup", "Backup", 12),
38+
backup_storage("backup_storage", "Backup Storage", 13),
39+
bucket("bucket", "Bucket", 14),
40+
object_storage("object_storage", "Object Storage", 15),
41+
gpu("gpu", "GPU", 16);
4242

4343
private String name;
44+
private String displayName;
4445
private int ordinal;
4546
public static final long bytesToKiB = 1024;
4647
public static final long bytesToMiB = bytesToKiB * 1024;
4748
public static final long bytesToGiB = bytesToMiB * 1024;
4849

49-
ResourceType(String name, int ordinal) {
50+
ResourceType(String name, String displayName, int ordinal) {
5051
this.name = name;
52+
this.displayName = displayName;
5153
this.ordinal = ordinal;
5254
}
5355

5456
public String getName() {
5557
return name;
5658
}
5759

60+
public String getDisplayName() {
61+
return displayName;
62+
}
63+
5864
public int getOrdinal() {
5965
return ordinal;
6066
}

‎api/src/main/java/com/cloud/user/AccountService.java‎

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import java.util.List;
2020
import java.util.Map;
2121

22-
import com.cloud.utils.Pair;
2322
import org.apache.cloudstack.acl.ControlledEntity;
2423
import org.apache.cloudstack.acl.RolePermissionEntity;
2524
import org.apache.cloudstack.acl.RoleType;
@@ -28,14 +27,6 @@
2827
import org.apache.cloudstack.acl.apikeypair.ApiKeyPairPermission;
2928
import org.apache.cloudstack.api.BaseCmd;
3029
import org.apache.cloudstack.api.command.admin.account.CreateAccountCmd;
31-
32-
import com.cloud.dc.DataCenter;
33-
import com.cloud.domain.Domain;
34-
import com.cloud.exception.PermissionDeniedException;
35-
import com.cloud.network.vpc.VpcOffering;
36-
import com.cloud.offering.DiskOffering;
37-
import com.cloud.offering.NetworkOffering;
38-
import com.cloud.offering.ServiceOffering;
3930
import org.apache.cloudstack.api.command.admin.user.DeleteUserKeysCmd;
4031
import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd;
4132
import org.apache.cloudstack.api.command.admin.user.ListUserKeyRulesCmd;
@@ -47,6 +38,15 @@
4738
import org.apache.cloudstack.auth.UserTwoFactorAuthenticator;
4839
import org.apache.cloudstack.backup.BackupOffering;
4940

41+
import com.cloud.dc.DataCenter;
42+
import com.cloud.domain.Domain;
43+
import com.cloud.exception.PermissionDeniedException;
44+
import com.cloud.network.vpc.VpcOffering;
45+
import com.cloud.offering.DiskOffering;
46+
import com.cloud.offering.NetworkOffering;
47+
import com.cloud.offering.ServiceOffering;
48+
import com.cloud.utils.Pair;
49+
5050
public interface AccountService {
5151

5252
/**
@@ -102,6 +102,8 @@ User createUser(String userName, String password, String firstName, String lastN
102102

103103
boolean isRootAdmin(Long accountId);
104104

105+
boolean isRootAdmin(Account account);
106+
105107
boolean isDomainAdmin(Long accountId);
106108

107109
boolean isResourceDomainAdmin(Long accountId);

‎api/src/main/java/org/apache/cloudstack/api/command/user/vm/BaseDeployVMCmd.java‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import org.apache.cloudstack.api.response.UserDataResponse;
4848
import org.apache.cloudstack.api.response.ZoneResponse;
4949
import org.apache.cloudstack.context.CallContext;
50+
import org.apache.cloudstack.error.Exceptions;
5051
import org.apache.cloudstack.kms.KMSKey;
5152
import org.apache.cloudstack.vm.lease.VMLeaseManager;
5253
import org.apache.commons.collections.CollectionUtils;
@@ -673,7 +674,8 @@ private Long getNetworkIdFomIpMap(HashMap<String, String> ips) {
673674
try {
674675
networkId = Long.parseLong(networkid);
675676
} catch (NumberFormatException e) {
676-
throw new InvalidParameterValueException("Unable to translate and find entity with networkId: " + networkid);
677+
throw Exceptions.invalidParameterValueException("vm.deploy.network.not.found.ip.map",
678+
Map.of("networkId", networkid));
677679
}
678680
}
679681
return networkId;

‎api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@
3434
import org.apache.cloudstack.api.response.UserVmResponse;
3535
import org.apache.cloudstack.api.response.VolumeResponse;
3636
import org.apache.cloudstack.context.CallContext;
37+
import org.apache.cloudstack.error.Exceptions;
3738

39+
import com.cloud.configuration.Resource;
3840
import com.cloud.exception.ConcurrentOperationException;
3941
import com.cloud.exception.InsufficientCapacityException;
4042
import com.cloud.exception.InsufficientServerCapacityException;
@@ -264,7 +266,19 @@ public void create() throws ResourceAllocationException {
264266
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage());
265267
} catch (ResourceAllocationException ex) {
266268
logger.warn("Exception: ", ex);
267-
throw new ServerApiException(ApiErrorCode.RESOURCE_ALLOCATION_ERROR, ex.getMessage());
269+
handleCreateResourceAllocationException(ex);
268270
}
269271
}
272+
273+
protected void handleCreateResourceAllocationException(ResourceAllocationException ex) {
274+
Object cause = CallContext.current().getErrorContextParameters().get("resourceLimitCause");
275+
if (Resource.ResourceOwnerType.Account.equals(cause)) {
276+
throw Exceptions.serverApiException(ApiErrorCode.RESOURCE_ALLOCATION_ERROR,
277+
"vm.deploy.resourcelimit.exceeded.account");
278+
} else if (Resource.ResourceOwnerType.Domain.equals(cause)) {
279+
throw Exceptions.serverApiException(ApiErrorCode.RESOURCE_ALLOCATION_ERROR,
280+
"vm.deploy.resourcelimit.exceeded.domain");
281+
}
282+
throw new ServerApiException(ApiErrorCode.RESOURCE_ALLOCATION_ERROR, ex.getMessage());
283+
}
270284
}

‎api/src/main/java/org/apache/cloudstack/api/response/ExceptionResponse.java‎

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@
1818

1919
import java.util.ArrayList;
2020
import java.util.List;
21-
22-
import com.google.gson.annotations.SerializedName;
21+
import java.util.Map;
2322

2423
import org.apache.cloudstack.api.BaseResponse;
2524

2625
import com.cloud.serializer.Param;
2726
import com.cloud.utils.exception.ExceptionProxyObject;
27+
import com.google.gson.annotations.SerializedName;
2828

2929
public class ExceptionResponse extends BaseResponse {
3030

@@ -44,6 +44,14 @@ public class ExceptionResponse extends BaseResponse {
4444
@Param(description = "The text associated with this error")
4545
private String errorText = "Command failed due to Internal Server Error";
4646

47+
@SerializedName("errortextkey")
48+
@Param(description = "the key for the text associated with this error", since = "4.24.0")
49+
private String errorTextKey;
50+
51+
@SerializedName("errormetadata")
52+
@Param(description = "the metadata associated with this error", since = "4.24.0")
53+
private Map<String, String> errorMetadata;
54+
4755
public ExceptionResponse() {
4856
idList = new ArrayList<ExceptionProxyObject>();
4957
}
@@ -64,6 +72,22 @@ public void setErrorText(String errorText) {
6472
this.errorText = errorText;
6573
}
6674

75+
public String getErrorTextKey() {
76+
return errorTextKey;
77+
}
78+
79+
public void setErrorTextKey(String errorTextKey) {
80+
this.errorTextKey = errorTextKey;
81+
}
82+
83+
public Map<String, String> getErrorMetadata() {
84+
return errorMetadata;
85+
}
86+
87+
public void setErrorMetadata(Map<String, String> errorMetadata) {
88+
this.errorMetadata = errorMetadata;
89+
}
90+
6791
public void addProxyObject(ExceptionProxyObject id) {
6892
idList.add(id);
6993
return;

‎api/src/main/java/org/apache/cloudstack/context/CallContext.java‎

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,21 @@
2323

2424
import org.apache.cloudstack.api.ApiCommandResourceType;
2525
import org.apache.cloudstack.managed.threadlocal.ManagedThreadLocal;
26-
import org.apache.logging.log4j.Logger;
26+
import org.apache.commons.collections.MapUtils;
2727
import org.apache.logging.log4j.LogManager;
28+
import org.apache.logging.log4j.Logger;
29+
import org.apache.logging.log4j.ThreadContext;
30+
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
2831

2932
import com.cloud.exception.CloudAuthenticationException;
3033
import com.cloud.projects.Project;
3134
import com.cloud.user.Account;
35+
import com.cloud.user.AccountService;
3236
import com.cloud.user.User;
3337
import com.cloud.utils.UuidUtils;
38+
import com.cloud.utils.component.ComponentContext;
3439
import com.cloud.utils.db.EntityManager;
3540
import com.cloud.utils.exception.CloudRuntimeException;
36-
import org.apache.logging.log4j.ThreadContext;
3741

3842
/**
3943
* CallContext records information about the environment the call is made. This
@@ -53,6 +57,7 @@ protected Stack<CallContext> initialValue() {
5357
private String contextId;
5458
private Account account;
5559
private long accountId;
60+
private Boolean isAccountRootAdmin = null;
5661
private long startEventId = 0;
5762
private String eventDescription;
5863
private String eventDetails;
@@ -64,6 +69,7 @@ protected Stack<CallContext> initialValue() {
6469
private long userId;
6570
private final Map<Object, Object> context = new HashMap<Object, Object>();
6671
private final Map<String, String> apiResourcesUuids = new HashMap<>();
72+
private final Map<String, Object> errorContext = new HashMap<>();
6773
private Project project;
6874
private String apiName;
6975

@@ -135,6 +141,25 @@ public Account getCallingAccount() {
135141
return account;
136142
}
137143

144+
public boolean isCallingAccountRootAdmin() {
145+
if (isAccountRootAdmin != null) {
146+
return isAccountRootAdmin;
147+
}
148+
if (account == null && s_entityMgr == null) {
149+
return false;
150+
}
151+
Account caller = getCallingAccount();
152+
AccountService accountService;
153+
try {
154+
accountService = ComponentContext.getDelegateComponentOfType(AccountService.class);
155+
} catch (NoSuchBeanDefinitionException e) {
156+
LOGGER.warn("Falling back to account type check for isRootAdmin for account ID: {} as no AccountService bean found: {}", accountId, e.getMessage());
157+
return caller != null && caller.getType() == Account.Type.ADMIN;
158+
}
159+
isAccountRootAdmin = accountService.isRootAdmin(caller);
160+
return isAccountRootAdmin;
161+
}
162+
138163
public static CallContext current() {
139164
CallContext context = s_currentContext.get();
140165

@@ -408,6 +433,21 @@ public void putContextParameters(Map<Object, Object> details){
408433
}
409434
}
410435

436+
public Map<String, Object> getErrorContextParameters() {
437+
return errorContext;
438+
}
439+
440+
public void putErrorContextParameter(String key, Object value) {
441+
errorContext.put(key, value);
442+
}
443+
444+
public void putErrorContextParameters(Map<String, Object> details) {
445+
if (MapUtils.isEmpty(details)) {
446+
return;
447+
}
448+
errorContext.putAll(details);
449+
}
450+
411451
public static void setActionEventInfo(String eventType, String description) {
412452
CallContext context = CallContext.current();
413453
if (context != null) {

0 commit comments

Comments
 (0)