Skip to content

Commit 086f57e

Browse files
committed
changes - fix CRUD for ext abd custom actions; ui
Signed-off-by: Abhishek Kumar <[email protected]>
1 parent d9d7de2 commit 086f57e

39 files changed

+1530
-339
lines changed

api/src/main/java/org/apache/cloudstack/api/ApiConstants.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,6 @@ public class ApiConstants {
217217
public static final String EXTENSION_RESOURCE_ID = "extensionresourceid";
218218
public static final String EXTENSION_RESOURCE_MAP = "extensionresourcemap";
219219

220-
public static final String EXTENSION_CUSTOM_ACTION = "extensioncustomaction";
221220
public static final String EXTENSION_CUSTOM_ACTION_ID = "customactionid";
222221
public static final String SCRIPT = "script";
223222
public static final String FENCE = "fence";

api/src/main/java/org/apache/cloudstack/api/response/ExtensionCustomActionResponse.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,18 @@ public class ExtensionCustomActionResponse extends BaseResponse {
5151
@Param(description = "Name of the extension that this extension custom action belongs to")
5252
private String extensionName;
5353

54+
@SerializedName(ApiConstants.RESOURCE_TYPE)
55+
@Param(description = "Resource type for which the action is available")
56+
private String resourceType;
57+
5458
@SerializedName(ApiConstants.ROLES_LIST)
5559
@Param(description = "Comma separated list of roles associated with the extension custom action")
5660
private String rolesList;
5761

62+
@SerializedName(ApiConstants.ENABLED)
63+
@Param(description = "Whether the extension custom action is enabled or not")
64+
private Boolean enabled;
65+
5866
@SerializedName(ApiConstants.DETAILS)
5967
@Param(description = "Details of the extension custom action")
6068
private Map<String, String> details;
@@ -106,6 +114,14 @@ public void setExtensionName(String extensionName) {
106114
this.extensionName = extensionName;
107115
}
108116

117+
public String getResourceType() {
118+
return resourceType;
119+
}
120+
121+
public void setResourceType(String resourceType) {
122+
this.resourceType = resourceType;
123+
}
124+
109125
public String getRolesList() {
110126
return rolesList;
111127
}
@@ -114,6 +130,10 @@ public void setRolesList(String rolesList) {
114130
this.rolesList = rolesList;
115131
}
116132

133+
public void setEnabled(Boolean enabled) {
134+
this.enabled = enabled;
135+
}
136+
117137
public void setParameters(Set<ExtensionCustomActionParameterResponse> parameters) {
118138
this.parameters = parameters;
119139
}

api/src/main/java/org/apache/cloudstack/extension/Extension.java

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

1818
package org.apache.cloudstack.extension;
1919

20+
import java.util.Date;
21+
2022
import org.apache.cloudstack.api.Identity;
2123
import org.apache.cloudstack.api.InternalIdentity;
2224

@@ -25,4 +27,5 @@ public interface Extension extends InternalIdentity, Identity {
2527
String getDescription();
2628
String getType();
2729
String getScript();
30+
Date getCreated();
2831
}

api/src/main/java/org/apache/cloudstack/extension/ExtensionCustomAction.java

Lines changed: 120 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,65 +17,106 @@
1717

1818
package org.apache.cloudstack.extension;
1919

20+
import java.security.InvalidParameterException;
21+
import java.util.Arrays;
22+
import java.util.Date;
23+
import java.util.HashMap;
2024
import java.util.List;
25+
import java.util.Map;
2126

27+
import org.apache.cloudstack.api.BaseCmd;
2228
import org.apache.cloudstack.api.Identity;
2329
import org.apache.cloudstack.api.InternalIdentity;
2430
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
31+
import org.apache.commons.lang3.EnumUtils;
2532
import org.apache.commons.lang3.StringUtils;
2633

34+
import com.cloud.exception.InvalidParameterValueException;
35+
import com.cloud.utils.DateUtil;
2736
import com.google.gson.Gson;
2837
import com.google.gson.GsonBuilder;
2938
import com.google.gson.reflect.TypeToken;
3039

3140
public interface ExtensionCustomAction extends InternalIdentity, Identity {
41+
enum ResourceType {
42+
VirtualMachine(com.cloud.vm.VirtualMachine.class),
43+
Host(com.cloud.host.Host.class),
44+
Cluster(com.cloud.org.Cluster.class);
45+
46+
private final Class<?> clazz;
47+
48+
ResourceType(Class<?> clazz) {
49+
this.clazz = clazz;
50+
}
51+
52+
public Class<?> getAssociatedClass() {
53+
return this.clazz;
54+
}
55+
56+
public static ResourceType fromString(String value) {
57+
if (value == null || value.trim().isEmpty()) {
58+
return null;
59+
}
60+
for (ResourceType type : ResourceType.values()) {
61+
if (type.name().equalsIgnoreCase(value.trim())) {
62+
return type;
63+
}
64+
}
65+
throw new IllegalArgumentException(String.format("Unknown resource type - %s", value));
66+
}
67+
}
68+
3269
String getName();
3370

3471
String getDescription();
3572

3673
long getExtensionId();
3774

75+
ResourceType getResourceType();
76+
3877
String getRolesList();
3978

4079
boolean isEnabled();
4180

81+
Date getCreated();
82+
83+
4284
class Parameter {
43-
public enum Type {
44-
STRING,
45-
INTEGER,
46-
DECIMAL,
47-
BOOLEAN,
48-
UUID,
49-
DATE;
50-
51-
public static Type fromString(String value) {
52-
if (StringUtils.isBlank(value)) {
53-
return STRING;
54-
}
55-
try {
56-
return Type.valueOf(value.trim().toUpperCase());
57-
} catch (IllegalArgumentException ex) {
58-
return STRING;
59-
}
60-
}
61-
}
6285
private static final Gson gson = new GsonBuilder().setPrettyPrinting().create();
6386

64-
final String name;
65-
final Type type;
66-
final boolean required;
87+
private final String name;
88+
private final BaseCmd.CommandType type;
89+
private final boolean required;
6790

68-
public Parameter (String name, Type type, boolean required) {
91+
public Parameter(String name, BaseCmd.CommandType type, boolean required) {
6992
this.name = name;
7093
this.type = type;
7194
this.required = required;
7295
}
7396

97+
public static Parameter fromMap(Map<String, String> map) throws InvalidParameterException {
98+
final String name = map.get("name");
99+
final String typeStr = map.get("type");
100+
final String required = map.get("required");
101+
if (StringUtils.isBlank(name)) {
102+
throw new InvalidParameterValueException("Invalid parameter specified with empty name");
103+
}
104+
if (StringUtils.isBlank(typeStr)) {
105+
throw new InvalidParameterException(String.format("Not type specified for parameter: %s", name));
106+
}
107+
BaseCmd.CommandType parsedType = EnumUtils.getEnumIgnoreCase(BaseCmd.CommandType.class, typeStr);
108+
if (parsedType == null) {
109+
throw new IllegalArgumentException(String.format("Invalid type: %s specified for parameter: %s",
110+
typeStr, name));
111+
}
112+
return new Parameter(name, parsedType, Boolean.parseBoolean(required));
113+
}
114+
74115
public String getName() {
75116
return name;
76117
}
77118

78-
public Type getType() {
119+
public BaseCmd.CommandType getType() {
79120
return type;
80121
}
81122

@@ -97,5 +138,60 @@ public static List<Parameter> toListFromJson(String json) {
97138
java.lang.reflect.Type listType = new TypeToken<List<Parameter>>() {}.getType();
98139
return gson.fromJson(json, listType);
99140
}
141+
142+
public Object validatedValue(String value) {
143+
if (StringUtils.isBlank(value)) {
144+
throw new InvalidParameterException("Empty value for parameter '" + name + "': " + value);
145+
}
146+
try {
147+
switch (type) {
148+
case BOOLEAN:
149+
return Arrays.asList("true", "false").contains(value);
150+
case DATE:
151+
return DateUtil.parseTZDateString(value);
152+
case FLOAT:
153+
return Float.valueOf(value);
154+
case INTEGER:
155+
return Integer.valueOf(value);
156+
case SHORT:
157+
return Short.valueOf(value);
158+
case LONG:
159+
return Long.valueOf(value);
160+
case UUID:
161+
return java.util.UUID.fromString(value);
162+
default:
163+
return value;
164+
}
165+
} catch (Exception e) {
166+
throw new InvalidParameterException("Invalid value for parameter '" + name + "': " + value);
167+
}
168+
}
169+
170+
public static Map<String, Object> validateParameterValues(List<Parameter> parameterDefinitions,
171+
Map<String, String> suppliedValues) throws InvalidParameterException {
172+
for (Parameter param : parameterDefinitions) {
173+
String value = suppliedValues.get(param.getName());
174+
if (param.isRequired()) {
175+
if (value == null || value.trim().isEmpty()) {
176+
throw new InvalidParameterException("Missing or empty required parameter: " + param.getName());
177+
}
178+
}
179+
}
180+
Map<String, Object> validatedParams = new HashMap<>();
181+
for (Map.Entry<String, String> entry : suppliedValues.entrySet()) {
182+
String name = entry.getKey();
183+
String value = entry.getValue();
184+
Parameter param = parameterDefinitions.stream()
185+
.filter(p -> p.getName().equals(name))
186+
.findFirst()
187+
.orElse(null);
188+
if (param != null) {
189+
validatedParams.put(name, param.validatedValue(value));
190+
} else {
191+
validatedParams.put(name, value);
192+
}
193+
}
194+
return validatedParams;
195+
}
100196
}
101197
}

core/src/main/java/com/cloud/agent/api/RunCustomActionCommand.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,23 @@
2424
public class RunCustomActionCommand extends Command {
2525

2626
String actionName;
27-
27+
Map<String, Object> parameters;
2828
Map<String, String> details;
2929

30-
public RunCustomActionCommand(String actionName, Map<String, String> details) {
30+
public RunCustomActionCommand(String actionName, Map<String, Object> parameters, Map<String, String> details) {
3131
this.actionName = actionName;
32+
this.parameters = parameters;
3233
this.details = details;
3334
}
3435

3536
public String getActionName() {
3637
return actionName;
3738
}
3839

40+
public Map<String, Object> getParameters() {
41+
return parameters;
42+
}
43+
3944
public Map<String, String> getDetails() {
4045
return details;
4146
}

framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/AddCustomActionCmd.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import javax.inject.Inject;
2424

25+
import org.apache.cloudstack.acl.RoleType;
2526
import org.apache.cloudstack.api.APICommand;
2627
import org.apache.cloudstack.api.ApiCommandResourceType;
2728
import org.apache.cloudstack.api.ApiConstants;
@@ -30,18 +31,23 @@
3031
import org.apache.cloudstack.api.ServerApiException;
3132
import org.apache.cloudstack.api.response.ExtensionCustomActionResponse;
3233
import org.apache.cloudstack.api.response.ExtensionResponse;
34+
import org.apache.cloudstack.extension.ExtensionCustomAction;
3335
import org.apache.cloudstack.framework.extensions.manager.ExtensionsManager;
3436

3537
import com.cloud.exception.ConcurrentOperationException;
3638
import com.cloud.exception.InsufficientCapacityException;
3739
import com.cloud.exception.NetworkRuleConflictException;
3840
import com.cloud.exception.ResourceAllocationException;
3941
import com.cloud.exception.ResourceUnavailableException;
40-
import org.apache.cloudstack.extension.ExtensionCustomAction;
4142
import com.cloud.user.Account;
4243

43-
@APICommand(name = "addCustomAction", description = "Register the custom action",
44-
responseObject = ExtensionCustomActionResponse.class, responseHasSensitiveInfo = false, since = "4.21.0")
44+
@APICommand(name = "addCustomAction",
45+
description = "Add a custom action for an extension",
46+
responseObject = ExtensionCustomActionResponse.class,
47+
responseHasSensitiveInfo = false,
48+
entityType = {ExtensionCustomAction.class},
49+
authorized = {RoleType.Admin},
50+
since = "4.21.0")
4551
public class AddCustomActionCmd extends BaseCmd {
4652

4753
@Inject

framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/CreateExtensionCmd.java

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,38 +17,39 @@
1717

1818
package org.apache.cloudstack.framework.extensions.api;
1919

20-
import com.cloud.exception.ConcurrentOperationException;
21-
import com.cloud.exception.NetworkRuleConflictException;
22-
import com.cloud.exception.ResourceAllocationException;
23-
import com.cloud.user.Account;
20+
import java.util.EnumSet;
21+
import java.util.Map;
22+
23+
import javax.inject.Inject;
2424

25+
import org.apache.cloudstack.acl.RoleType;
2526
import org.apache.cloudstack.api.APICommand;
2627
import org.apache.cloudstack.api.ApiCommandResourceType;
2728
import org.apache.cloudstack.api.ApiConstants;
2829
import org.apache.cloudstack.api.BaseCmd;
2930
import org.apache.cloudstack.api.Parameter;
3031
import org.apache.cloudstack.api.ServerApiException;
31-
import org.apache.cloudstack.framework.extensions.manager.ExtensionsManager;
3232
import org.apache.cloudstack.api.response.ExtensionResponse;
3333
import org.apache.cloudstack.extension.Extension;
34+
import org.apache.cloudstack.framework.extensions.manager.ExtensionsManager;
3435

35-
import javax.inject.Inject;
36-
37-
import java.util.EnumSet;
38-
import java.util.Map;
36+
import com.cloud.exception.ConcurrentOperationException;
37+
import com.cloud.exception.NetworkRuleConflictException;
38+
import com.cloud.exception.ResourceAllocationException;
39+
import com.cloud.user.Account;
3940

4041
@APICommand(name = "createExtension",
4142
description = "Create an extension",
4243
responseObject = ExtensionResponse.class,
4344
responseHasSensitiveInfo = false,
45+
entityType = {Extension.class},
46+
authorized = {RoleType.Admin},
4447
since = "4.21.0")
4548
public class CreateExtensionCmd extends BaseCmd {
4649

4750
@Inject
4851
ExtensionsManager extensionsManager;
4952

50-
public static final String APINAME = "createExtension";
51-
5253
/////////////////////////////////////////////////////
5354
//////////////// API parameters /////////////////////
5455
/////////////////////////////////////////////////////
@@ -98,6 +99,7 @@ public void execute() throws ServerApiException, ConcurrentOperationException, R
9899
Extension extension = extensionsManager.createExtension(this);
99100
ExtensionResponse response = extensionsManager.createExtensionResponse(extension,
100101
EnumSet.of(ApiConstants.ExtensionDetails.all));
102+
response.setResponseName(getCommandName());
101103
setResponseObject(response);
102104
}
103105

0 commit comments

Comments
 (0)