This is an automated email from the ASF dual-hosted git repository.
weizhou pushed a commit to branch 4.18
in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/4.18 by this push:
new 78bdde9e981 AutoScaling: support Managed User Data (#7769)
78bdde9e981 is described below
commit 78bdde9e981ef55f277327bf0e783e215d5af9c4
Author: Wei Zhou <[email protected]>
AuthorDate: Tue Aug 22 11:07:16 2023 +0200
AutoScaling: support Managed User Data (#7769)
---
.../com/cloud/network/as/AutoScaleVmProfile.java | 4 +
.../java/org/apache/cloudstack/api/BaseCmd.java | 17 ++
.../autoscale/CreateAutoScaleVmProfileCmd.java | 15 ++
.../autoscale/UpdateAutoScaleVmProfileCmd.java | 17 ++
.../api/command/user/vm/DeployVMCmd.java | 26 +--
.../api/command/user/vm/ResetVMUserDataCmd.java | 16 +-
.../cloudstack/api/command/user/vm/ScaleVMCmd.java | 15 +-
.../api/command/user/vm/UpdateVMCmd.java | 14 +-
.../api/command/user/vm/UpgradeVMCmd.java | 15 +-
.../api/response/AutoScaleVmProfileResponse.java | 48 ++++
.../com/cloud/network/as/AutoScaleVmProfileVO.java | 24 ++
.../com/cloud/upgrade/dao/Upgrade41800to41810.java | 5 +
.../resources/META-INF/db/schema-41800to41810.sql | 4 +
.../cloud/network/as/AutoScaleVmProfileVOTest.java | 31 +++
.../java/com/cloud/api/ApiAsyncJobDispatcher.java | 6 +
.../main/java/com/cloud/api/ApiResponseHelper.java | 14 ++
server/src/main/java/com/cloud/api/ApiServer.java | 3 +
.../com/cloud/network/as/AutoScaleManagerImpl.java | 53 ++++-
.../src/main/java/com/cloud/vm/UserVmManager.java | 4 +
.../main/java/com/cloud/vm/UserVmManagerImpl.java | 6 +-
.../java/com/cloud/api/ApiResponseHelperTest.java | 72 ++++++
.../cloud/network/as/AutoScaleManagerImplTest.java | 75 +++++-
test/integration/smoke/test_vm_autoscaling.py | 69 ++++++
ui/public/locales/en.json | 1 +
ui/src/views/compute/AutoScaleVmProfile.vue | 92 ++++++--
ui/src/views/compute/CreateAutoScaleVmGroup.vue | 257 +++++++++++++++++++--
ui/src/views/compute/ResetUserData.vue | 10 +-
27 files changed, 785 insertions(+), 128 deletions(-)
diff --git a/api/src/main/java/com/cloud/network/as/AutoScaleVmProfile.java
b/api/src/main/java/com/cloud/network/as/AutoScaleVmProfile.java
index afeba0bd749..68d18a0edc9 100644
--- a/api/src/main/java/com/cloud/network/as/AutoScaleVmProfile.java
+++ b/api/src/main/java/com/cloud/network/as/AutoScaleVmProfile.java
@@ -35,6 +35,10 @@ public interface AutoScaleVmProfile extends
ControlledEntity, InternalIdentity,
String getUserData();
+ Long getUserDataId();
+
+ String getUserDataDetails();
+
public String getUuid();
public Long getZoneId();
diff --git a/api/src/main/java/org/apache/cloudstack/api/BaseCmd.java
b/api/src/main/java/org/apache/cloudstack/api/BaseCmd.java
index e431f63557c..79e103a291d 100644
--- a/api/src/main/java/org/apache/cloudstack/api/BaseCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/BaseCmd.java
@@ -21,8 +21,10 @@ import java.lang.reflect.Field;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
@@ -42,6 +44,7 @@ import
org.apache.cloudstack.network.lb.InternalLoadBalancerVMService;
import org.apache.cloudstack.query.QueryService;
import org.apache.cloudstack.storage.ImageStoreService;
import org.apache.cloudstack.usage.UsageService;
+import org.apache.commons.collections.MapUtils;
import org.apache.log4j.Logger;
import com.cloud.configuration.ConfigurationService;
@@ -456,4 +459,18 @@ public abstract class BaseCmd {
return ApiCommandResourceType.None;
}
+ public Map<String, String> convertDetailsToMap(Map details) {
+ Map<String, String> detailsMap = new HashMap<String, String>();
+ if (MapUtils.isNotEmpty(details)) {
+ Collection parameterCollection = details.values();
+ Iterator iter = parameterCollection.iterator();
+ while (iter.hasNext()) {
+ HashMap<String, String> value = (HashMap<String,
String>)iter.next();
+ for (Map.Entry<String,String> entry: value.entrySet()) {
+ detailsMap.put(entry.getKey(),entry.getValue());
+ }
+ }
+ }
+ return detailsMap;
+ }
}
diff --git
a/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/CreateAutoScaleVmProfileCmd.java
b/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/CreateAutoScaleVmProfileCmd.java
index ba7149ea3d8..db6ccd9ce53 100644
---
a/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/CreateAutoScaleVmProfileCmd.java
+++
b/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/CreateAutoScaleVmProfileCmd.java
@@ -35,6 +35,7 @@ import org.apache.cloudstack.api.response.DomainResponse;
import org.apache.cloudstack.api.response.ProjectResponse;
import org.apache.cloudstack.api.response.ServiceOfferingResponse;
import org.apache.cloudstack.api.response.TemplateResponse;
+import org.apache.cloudstack.api.response.UserDataResponse;
import org.apache.cloudstack.api.response.UserResponse;
import org.apache.cloudstack.api.response.ZoneResponse;
import org.apache.cloudstack.context.CallContext;
@@ -107,6 +108,12 @@ public class CreateAutoScaleVmProfileCmd extends
BaseAsyncCreateCmd {
since = "4.18.0")
private String userData;
+ @Parameter(name = ApiConstants.USER_DATA_ID, type = CommandType.UUID,
entityType = UserDataResponse.class, description = "the ID of the Userdata",
since = "4.18.1")
+ private Long userDataId;
+
+ @Parameter(name = ApiConstants.USER_DATA_DETAILS, type = CommandType.MAP,
description = "used to specify the parameters values for the variables in
userdata.", since = "4.18.1")
+ private Map userDataDetails;
+
@Parameter(name = ApiConstants.AUTOSCALE_USER_ID,
type = CommandType.UUID,
entityType = UserResponse.class,
@@ -163,6 +170,14 @@ public class CreateAutoScaleVmProfileCmd extends
BaseAsyncCreateCmd {
return userData;
}
+ public Long getUserDataId() {
+ return userDataId;
+ }
+
+ public Map<String, String> getUserDataDetails() {
+ return convertDetailsToMap(userDataDetails);
+ }
+
public Long getAutoscaleUserId() {
return autoscaleUserId;
}
diff --git
a/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/UpdateAutoScaleVmProfileCmd.java
b/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/UpdateAutoScaleVmProfileCmd.java
index c0b385932a9..3e65d38e520 100644
---
a/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/UpdateAutoScaleVmProfileCmd.java
+++
b/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/UpdateAutoScaleVmProfileCmd.java
@@ -35,6 +35,7 @@ import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.AutoScaleVmProfileResponse;
import org.apache.cloudstack.api.response.ServiceOfferingResponse;
import org.apache.cloudstack.api.response.TemplateResponse;
+import org.apache.cloudstack.api.response.UserDataResponse;
import org.apache.cloudstack.api.response.UserResponse;
import org.apache.cloudstack.context.CallContext;
@@ -102,6 +103,14 @@ public class UpdateAutoScaleVmProfileCmd extends
BaseAsyncCustomIdCmd {
since = "4.18.0")
private String userData;
+ @Parameter(name = ApiConstants.USER_DATA_ID, type = CommandType.UUID,
entityType = UserDataResponse.class, description = "the ID of the userdata",
+ since = "4.18.1")
+ private Long userDataId;
+
+ @Parameter(name = ApiConstants.USER_DATA_DETAILS, type = CommandType.MAP,
description = "used to specify the parameters values for the variables in
userdata.",
+ since = "4.18.1")
+ private Map userDataDetails;
+
@Parameter(name = ApiConstants.AUTOSCALE_USER_ID,
type = CommandType.UUID,
entityType = UserResponse.class,
@@ -156,6 +165,14 @@ public class UpdateAutoScaleVmProfileCmd extends
BaseAsyncCustomIdCmd {
return userData;
}
+ public Long getUserDataId() {
+ return userDataId;
+ }
+
+ public Map<String, String> getUserDataDetails() {
+ return convertDetailsToMap(userDataDetails);
+ }
+
public Long getAutoscaleUserId() {
return autoscaleUserId;
}
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 12212dac417..ef6dba63166 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
@@ -309,17 +309,8 @@ public class DeployVMCmd extends
BaseAsyncCreateCustomIdCmd implements SecurityG
}
public Map<String, String> getDetails() {
- Map<String, String> customparameterMap = new HashMap<String, String>();
- if (details != null && details.size() != 0) {
- Collection parameterCollection = details.values();
- Iterator iter = parameterCollection.iterator();
- while (iter.hasNext()) {
- HashMap<String, String> value = (HashMap<String,
String>)iter.next();
- for (Map.Entry<String,String> entry: value.entrySet()) {
- customparameterMap.put(entry.getKey(),entry.getValue());
- }
- }
- }
+ Map<String, String> customparameterMap = convertDetailsToMap(details);
+
if (getBootType() != null) {
customparameterMap.put(getBootType().toString(),
getBootMode().toString());
}
@@ -450,18 +441,7 @@ public class DeployVMCmd extends
BaseAsyncCreateCustomIdCmd implements SecurityG
}
public Map<String, String> getUserdataDetails() {
- Map<String, String> userdataDetailsMap = new HashMap<String, String>();
- if (userdataDetails != null && userdataDetails.size() != 0) {
- Collection parameterCollection = userdataDetails.values();
- Iterator iter = parameterCollection.iterator();
- while (iter.hasNext()) {
- HashMap<String, String> value = (HashMap<String,
String>)iter.next();
- for (Map.Entry<String,String> entry: value.entrySet()) {
- userdataDetailsMap.put(entry.getKey(),entry.getValue());
- }
- }
- }
- return userdataDetailsMap;
+ return convertDetailsToMap(userdataDetails);
}
public Long getZoneId() {
diff --git
a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ResetVMUserDataCmd.java
b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ResetVMUserDataCmd.java
index ad0592ca4ac..3ead67e2106 100644
---
a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ResetVMUserDataCmd.java
+++
b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ResetVMUserDataCmd.java
@@ -39,9 +39,6 @@ import org.apache.cloudstack.api.response.UserVmResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.log4j.Logger;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Iterator;
import java.util.Map;
@APICommand(name = "resetUserDataForVirtualMachine", responseObject =
UserVmResponse.class, description = "Resets the UserData for virtual machine. "
+
@@ -117,18 +114,7 @@ public class ResetVMUserDataCmd extends BaseCmd implements
UserCmd {
}
public Map<String, String> getUserdataDetails() {
- Map<String, String> userdataDetailsMap = new HashMap<String, String>();
- if (userdataDetails != null && userdataDetails.size() != 0) {
- Collection parameterCollection = userdataDetails.values();
- Iterator iter = parameterCollection.iterator();
- while (iter.hasNext()) {
- HashMap<String, String> value = (HashMap<String,
String>)iter.next();
- for (Map.Entry<String,String> entry: value.entrySet()) {
- userdataDetailsMap.put(entry.getKey(),entry.getValue());
- }
- }
- }
- return userdataDetailsMap;
+ return convertDetailsToMap(userdataDetails);
}
@Override
diff --git
a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ScaleVMCmd.java
b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ScaleVMCmd.java
index e60bce2810a..5af45762ece 100644
---
a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ScaleVMCmd.java
+++
b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ScaleVMCmd.java
@@ -16,9 +16,6 @@
// under the License.
package org.apache.cloudstack.api.command.user.vm;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -99,17 +96,7 @@ public class ScaleVMCmd extends BaseAsyncCmd implements
UserCmd {
//it is because details.values() cannot be cast to a map.
//it gives a exception
public Map<String, String> getDetails() {
- Map<String, String> customparameterMap = new HashMap<String, String>();
- if (details != null && details.size() != 0) {
- Collection parameterCollection = details.values();
- Iterator iter = parameterCollection.iterator();
- while (iter.hasNext()) {
- HashMap<String, String> value = (HashMap<String,
String>)iter.next();
- for (String key : value.keySet()) {
- customparameterMap.put(key, value.get(key));
- }
- }
- }
+ Map<String, String> customparameterMap = convertDetailsToMap(details);
if (shrinkOk != null) customparameterMap.put(ApiConstants.SHRINK_OK,
String.valueOf(isShrinkOk()));
if (autoMigrate != null)
customparameterMap.put(ApiConstants.AUTO_MIGRATE,
String.valueOf(getAutoMigrate()));
diff --git
a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMCmd.java
b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMCmd.java
index 3e0ef75ecdd..32ce1f6db52 100644
---
a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMCmd.java
+++
b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMCmd.java
@@ -18,7 +18,6 @@ package org.apache.cloudstack.api.command.user.vm;
import java.util.Collection;
import java.util.HashMap;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -176,18 +175,7 @@ public class UpdateVMCmd extends BaseCustomIdCmd
implements SecurityGroupAction,
}
public Map<String, String> getUserdataDetails() {
- Map<String, String> userdataDetailsMap = new HashMap<String, String>();
- if (userdataDetails != null && userdataDetails.size() != 0) {
- Collection parameterCollection = userdataDetails.values();
- Iterator iter = parameterCollection.iterator();
- while (iter.hasNext()) {
- HashMap<String, String> value = (HashMap<String,
String>)iter.next();
- for (Map.Entry<String,String> entry: value.entrySet()) {
- userdataDetailsMap.put(entry.getKey(),entry.getValue());
- }
- }
- }
- return userdataDetailsMap;
+ return convertDetailsToMap(userdataDetails);
}
public Boolean getDisplayVm() {
diff --git
a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpgradeVMCmd.java
b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpgradeVMCmd.java
index b6acd71b4c4..4b31c12ec0a 100644
---
a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpgradeVMCmd.java
+++
b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpgradeVMCmd.java
@@ -16,9 +16,6 @@
// under the License.
package org.apache.cloudstack.api.command.user.vm;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Iterator;
import java.util.Map;
import org.apache.log4j.Logger;
@@ -95,17 +92,7 @@ public class UpgradeVMCmd extends BaseCmd implements UserCmd
{
}
public Map<String, String> getDetails() {
- Map<String, String> customparameterMap = new HashMap<String, String>();
- if (details != null && details.size() != 0) {
- Collection parameterCollection = details.values();
- Iterator iter = parameterCollection.iterator();
- while (iter.hasNext()) {
- HashMap<String, String> value = (HashMap<String,
String>)iter.next();
- for (String key : value.keySet()) {
- customparameterMap.put(key, value.get(key));
- }
- }
- }
+ Map<String, String> customparameterMap = convertDetailsToMap(details);
if (shrinkOk != null) customparameterMap.put(ApiConstants.SHRINK_OK,
String.valueOf(isShrinkOk()));
if (autoMigrate != null)
customparameterMap.put(ApiConstants.AUTO_MIGRATE,
String.valueOf(getAutoMigrate()));
diff --git
a/api/src/main/java/org/apache/cloudstack/api/response/AutoScaleVmProfileResponse.java
b/api/src/main/java/org/apache/cloudstack/api/response/AutoScaleVmProfileResponse.java
index 66d7296f406..9f238344730 100644
---
a/api/src/main/java/org/apache/cloudstack/api/response/AutoScaleVmProfileResponse.java
+++
b/api/src/main/java/org/apache/cloudstack/api/response/AutoScaleVmProfileResponse.java
@@ -72,6 +72,18 @@ public class AutoScaleVmProfileResponse extends BaseResponse
implements Controll
@Param(description = "Base 64 encoded VM user data")
private String userData;
+ @SerializedName(ApiConstants.USER_DATA_ID) @Param(description="the id of
userdata used for the VM", since = "4.18.1")
+ private String userDataId;
+
+ @SerializedName(ApiConstants.USER_DATA_NAME) @Param(description="the name
of userdata used for the VM", since = "4.18.1")
+ private String userDataName;
+
+ @SerializedName(ApiConstants.USER_DATA_POLICY) @Param(description="the
userdata override policy with the userdata provided while deploying VM", since
= "4.18.1")
+ private String userDataPolicy;
+
+ @SerializedName(ApiConstants.USER_DATA_DETAILS) @Param(description="list
of variables and values for the variables declared in userdata", since =
"4.18.1")
+ private String userDataDetails;
+
@SerializedName(ApiConstants.AUTOSCALE_USER_ID)
@Param(description = "the ID of the user used to launch and destroy the
VMs")
private String autoscaleUserId;
@@ -153,6 +165,22 @@ public class AutoScaleVmProfileResponse extends
BaseResponse implements Controll
this.userData = userData;
}
+ public void setUserDataId(String userDataId) {
+ this.userDataId = userDataId;
+ }
+
+ public void setUserDataName(String userDataName) {
+ this.userDataName = userDataName;
+ }
+
+ public void setUserDataPolicy(String userDataPolicy) {
+ this.userDataPolicy = userDataPolicy;
+ }
+
+ public void setUserDataDetails(String userDataDetails) {
+ this.userDataDetails = userDataDetails;
+ }
+
@Override
public void setAccountName(String accountName) {
this.accountName = accountName;
@@ -193,4 +221,24 @@ public class AutoScaleVmProfileResponse extends
BaseResponse implements Controll
public void setForDisplay(Boolean forDisplay) {
this.forDisplay = forDisplay;
}
+
+ public String getUserData() {
+ return userData;
+ }
+
+ public String getUserDataId() {
+ return userDataId;
+ }
+
+ public String getUserDataName() {
+ return userDataName;
+ }
+
+ public String getUserDataPolicy() {
+ return userDataPolicy;
+ }
+
+ public String getUserDataDetails() {
+ return userDataDetails;
+ }
}
diff --git
a/engine/schema/src/main/java/com/cloud/network/as/AutoScaleVmProfileVO.java
b/engine/schema/src/main/java/com/cloud/network/as/AutoScaleVmProfileVO.java
index 0a0ba1e62aa..21291062756 100644
--- a/engine/schema/src/main/java/com/cloud/network/as/AutoScaleVmProfileVO.java
+++ b/engine/schema/src/main/java/com/cloud/network/as/AutoScaleVmProfileVO.java
@@ -88,6 +88,12 @@ public class AutoScaleVmProfileVO implements
AutoScaleVmProfile, Identity, Inter
@Basic(fetch = FetchType.LAZY)
private String userData;
+ @Column(name = "user_data_id", nullable = true)
+ private Long userDataId = null;
+
+ @Column(name = "user_data_details", updatable = true, length = 4096)
+ private String userDataDetails;
+
@Column(name = GenericDao.REMOVED_COLUMN)
protected Date removed;
@@ -228,6 +234,24 @@ public class AutoScaleVmProfileVO implements
AutoScaleVmProfile, Identity, Inter
return userData;
}
+ @Override
+ public Long getUserDataId() {
+ return userDataId;
+ }
+
+ public void setUserDataId(Long userDataId) {
+ this.userDataId = userDataId;
+ }
+
+ @Override
+ public String getUserDataDetails() {
+ return userDataDetails;
+ }
+
+ public void setUserDataDetails(String userDataDetails) {
+ this.userDataDetails = userDataDetails;
+ }
+
@Override
public String getUuid() {
return uuid;
diff --git
a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41800to41810.java
b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41800to41810.java
index a2733215060..53bd497ca22 100644
--- a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41800to41810.java
+++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41800to41810.java
@@ -63,6 +63,7 @@ public class Upgrade41800to41810 implements DbUpgrade,
DbUpgradeSystemVmTemplate
fixForeignKeyNames(conn);
updateGuestOsMappings(conn);
copyGuestOsMappingsToVMware80u1();
+ addForeignKeyToAutoscaleVmprofiles(conn);
}
@Override
@@ -225,4 +226,8 @@ public class Upgrade41800to41810 implements DbUpgrade,
DbUpgradeSystemVmTemplate
DbUpgradeUtils.dropKeysIfExist(conn, "cloud.volumes", keys, false);
DbUpgradeUtils.addForeignKey(conn, "volumes",
"passphrase_id","passphrase", "id");
}
+
+ private void addForeignKeyToAutoscaleVmprofiles(Connection conn) {
+ DbUpgradeUtils.addForeignKey(conn, "autoscale_vmprofiles",
"user_data_id", "user_data", "id");
+ }
}
diff --git
a/engine/schema/src/main/resources/META-INF/db/schema-41800to41810.sql
b/engine/schema/src/main/resources/META-INF/db/schema-41800to41810.sql
index e07e12871f3..495c184918c 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-41800to41810.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-41800to41810.sql
@@ -35,6 +35,10 @@ CALL ADD_GUEST_OS_AND_HYPERVISOR_MAPPING (6, 'Windows Server
2022 (64-bit)', 'VM
CALL ADD_GUEST_OS_AND_HYPERVISOR_MAPPING (6, 'Windows Server 2022 (64-bit)',
'VMware', '8.0.0.1', 'windows2019srvNext_64Guest');
CALL ADD_GUEST_OS_AND_HYPERVISOR_MAPPING (6, 'Windows Server 2022 (64-bit)',
'Xenserver', '8.2.0', 'Windows Server 2022 (64-bit)');
+-- Support userdata ids and details in VM AutoScaling
+CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.autoscale_vmprofiles',
'user_data_id', 'bigint unsigned DEFAULT NULL COMMENT "id of the user data"
AFTER `user_data`');
+CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.autoscale_vmprofiles',
'user_data_details', 'mediumtext DEFAULT NULL COMMENT "value of the
comma-separated list of parameters" AFTER `user_data_id`');
+
-- Don't enable CPU cap for default system offerings, fixes regression from
https://github.com/apache/cloudstack/pull/6420
UPDATE `cloud`.`service_offering` so
SET so.limit_cpu_use = 0
diff --git
a/engine/schema/src/test/java/com/cloud/network/as/AutoScaleVmProfileVOTest.java
b/engine/schema/src/test/java/com/cloud/network/as/AutoScaleVmProfileVOTest.java
index 6f49c3d4e1e..7e9658e1dd3 100755
---
a/engine/schema/src/test/java/com/cloud/network/as/AutoScaleVmProfileVOTest.java
+++
b/engine/schema/src/test/java/com/cloud/network/as/AutoScaleVmProfileVOTest.java
@@ -26,6 +26,18 @@ import org.junit.Test;
public class AutoScaleVmProfileVOTest {
+ static long zoneId = 1L;
+ static long domainId = 2L;
+ static long accountId = 3L;
+ static long serviceOfferingId = 4L;
+ static long templateId = 5L;
+ static String userdata = "userdata";
+ static long userdataId = 6L;
+ static String userdataDetails = "userdataDetails";
+ static String userdataNew = "userdataNew";
+
+ static long autoScaleUserId = 7L;
+
@Test
public void testCounterParamsForUpdate() {
AutoScaleVmProfileVO profile = new AutoScaleVmProfileVO();
@@ -62,4 +74,23 @@ public class AutoScaleVmProfileVOTest {
Assert.assertEquals("rootdisksize",
otherDeployParamsList.get(1).first());
Assert.assertEquals("10", otherDeployParamsList.get(1).second());
}
+
+ @Test
+ public void testProperties() {
+ AutoScaleVmProfileVO profile = new AutoScaleVmProfileVO(zoneId,
domainId, accountId, serviceOfferingId, templateId, null, null, userdata, null,
autoScaleUserId);
+ Assert.assertEquals(new Long(zoneId), profile.getZoneId());
+ Assert.assertEquals(domainId, profile.getDomainId());
+ Assert.assertEquals(accountId, profile.getAccountId());
+ Assert.assertEquals(new Long(serviceOfferingId),
profile.getServiceOfferingId());
+ Assert.assertEquals(new Long(templateId), profile.getTemplateId());
+ Assert.assertEquals(userdata, profile.getUserData());
+ Assert.assertEquals(new Long(autoScaleUserId),
profile.getAutoScaleUserId());
+
+ profile.setUserData(userdataNew);
+ profile.setUserDataId(userdataId);
+ profile.setUserDataDetails(userdataDetails);
+ Assert.assertEquals(userdataNew, profile.getUserData());
+ Assert.assertEquals(new Long(userdataId), profile.getUserDataId());
+ Assert.assertEquals(userdataDetails, profile.getUserDataDetails());
+ }
}
diff --git a/server/src/main/java/com/cloud/api/ApiAsyncJobDispatcher.java
b/server/src/main/java/com/cloud/api/ApiAsyncJobDispatcher.java
index b596254994c..e09e95e2ce6 100644
--- a/server/src/main/java/com/cloud/api/ApiAsyncJobDispatcher.java
+++ b/server/src/main/java/com/cloud/api/ApiAsyncJobDispatcher.java
@@ -21,6 +21,7 @@ import java.util.Map;
import javax.inject.Inject;
+import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseAsyncCmd;
import org.apache.cloudstack.api.BaseAsyncCreateCmd;
@@ -101,6 +102,11 @@ public class ApiAsyncJobDispatcher extends AdapterBase
implements AsyncJobDispat
ctx.putContextParameters((Map<Object, Object>)
gson.fromJson(contextDetails, objectMapType));
}
+ String httpmethod = params.get(ApiConstants.HTTPMETHOD);
+ if (httpmethod != null) {
+ cmdObj.setHttpMethod(httpmethod);
+ }
+
try {
// dispatch could ultimately queue the job
_dispatcher.dispatch(cmdObj, params, true);
diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java
b/server/src/main/java/com/cloud/api/ApiResponseHelper.java
index 8fffebb3303..55f00a609e9 100644
--- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java
+++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java
@@ -365,6 +365,7 @@ import com.cloud.user.User;
import com.cloud.user.UserAccount;
import com.cloud.user.UserData;
import com.cloud.user.UserStatisticsVO;
+import com.cloud.user.dao.UserDataDao;
import com.cloud.user.dao.UserStatisticsDao;
import com.cloud.uservm.UserVm;
import com.cloud.utils.Pair;
@@ -455,6 +456,8 @@ public class ApiResponseHelper implements ResponseGenerator
{
UserVmJoinDao userVmJoinDao;
@Inject
NetworkServiceMapDao ntwkSrvcDao;
+ @Inject
+ UserDataDao userDataDao;
@Override
public UserResponse createUserResponse(User user) {
@@ -3393,9 +3396,20 @@ public class ApiResponseHelper implements
ResponseGenerator {
VMTemplateVO template =
ApiDBUtils.findTemplateById(profile.getTemplateId());
if (template != null) {
response.setTemplateId(template.getUuid());
+ if (template.getUserDataOverridePolicy() != null) {
+
response.setUserDataPolicy(template.getUserDataOverridePolicy().toString());
+ }
}
}
response.setUserData(profile.getUserData());
+ if (profile.getUserDataId() != null) {
+ UserData userData = userDataDao.findById(profile.getUserDataId());
+ if (userData != null) {
+ response.setUserDataId(userData.getUuid());
+ response.setUserDataName(userData.getName());
+ }
+ }
+ response.setUserDataDetails(profile.getUserDataDetails());
response.setOtherDeployParams(profile.getOtherDeployParamsList());
response.setCounterParams(profile.getCounterParams());
response.setExpungeVmGracePeriod(profile.getExpungeVmGracePeriod());
diff --git a/server/src/main/java/com/cloud/api/ApiServer.java
b/server/src/main/java/com/cloud/api/ApiServer.java
index b62e59f5c27..e88d7cf8b53 100644
--- a/server/src/main/java/com/cloud/api/ApiServer.java
+++ b/server/src/main/java/com/cloud/api/ApiServer.java
@@ -740,6 +740,9 @@ public class ApiServer extends ManagerBase implements
HttpRequestHandler, ApiSer
params.put("ctxStartEventId", String.valueOf(startEventId));
params.put("cmdEventType", asyncCmd.getEventType().toString());
params.put("ctxDetails",
ApiGsonHelper.getBuilder().create().toJson(ctx.getContextParameters()));
+ if (asyncCmd.getHttpMethod() != null) {
+ params.put(ApiConstants.HTTPMETHOD,
asyncCmd.getHttpMethod().toString());
+ }
Long instanceId = (objectId == null) ? asyncCmd.getApiResourceId()
: objectId;
diff --git
a/server/src/main/java/com/cloud/network/as/AutoScaleManagerImpl.java
b/server/src/main/java/com/cloud/network/as/AutoScaleManagerImpl.java
index 29cc4cead0b..de8a3ff3c83 100644
--- a/server/src/main/java/com/cloud/network/as/AutoScaleManagerImpl.java
+++ b/server/src/main/java/com/cloud/network/as/AutoScaleManagerImpl.java
@@ -532,7 +532,6 @@ public class AutoScaleManagerImpl extends ManagerBase
implements AutoScaleManage
long zoneId = cmd.getZoneId();
long serviceOfferingId = cmd.getServiceOfferingId();
Long autoscaleUserId = cmd.getAutoscaleUserId();
- String userData = cmd.getUserData();
DataCenter zone = entityMgr.findById(DataCenter.class, zoneId);
@@ -545,6 +544,11 @@ public class AutoScaleManagerImpl extends ManagerBase
implements AutoScaleManage
throw new InvalidParameterValueException("Unable to find service
offering by id");
}
+ VirtualMachineTemplate template =
entityMgr.findById(VirtualMachineTemplate.class, cmd.getTemplateId());
+ if (template == null) {
+ throw new InvalidParameterValueException("Unable to find template
by id " + cmd.getTemplateId());
+ }
+
// validations
HashMap<String, String> deployParams = cmd.getDeployParamMap();
/*
@@ -562,9 +566,23 @@ public class AutoScaleManagerImpl extends ManagerBase
implements AutoScaleManage
profileVO.setDisplay(cmd.getDisplay());
}
+ String userData = cmd.getUserData();
+ Long userDataId = cmd.getUserDataId();
+ String userDataDetails = null;
+ if (MapUtils.isNotEmpty(cmd.getUserDataDetails())) {
+ userDataDetails = cmd.getUserDataDetails().toString();
+ }
+ userData = userVmMgr.finalizeUserData(userData, userDataId, template);
+ userData = userVmMgr.validateUserData(userData, cmd.getHttpMethod());
if (userData != null) {
profileVO.setUserData(userData);
}
+ if (userDataId != null) {
+ profileVO.setUserDataId(userDataId);
+ }
+ if (userDataDetails != null) {
+ profileVO.setUserDataDetails(userDataDetails);
+ }
profileVO = checkValidityAndPersist(profileVO, true);
s_logger.info("Successfully create AutoScale Vm Profile with Id: " +
profileVO.getId());
@@ -582,12 +600,19 @@ public class AutoScaleManagerImpl extends ManagerBase
implements AutoScaleManage
Map<String, HashMap<String, String>> otherDeployParams =
cmd.getOtherDeployParams();
Map counterParamList = cmd.getCounterParamList();
String userData = cmd.getUserData();
+ Long userDataId = cmd.getUserDataId();
+ String userDataDetails = null;
+ if (MapUtils.isNotEmpty(cmd.getUserDataDetails())) {
+ userDataDetails = cmd.getUserDataDetails().toString();
+ }
+ boolean userdataUpdate = userData != null || userDataId != null ||
MapUtils.isNotEmpty(cmd.getUserDataDetails());
Integer expungeVmGracePeriod = cmd.getExpungeVmGracePeriod();
AutoScaleVmProfileVO vmProfile =
getEntityInDatabase(CallContext.current().getCallingAccount(), "Auto Scale Vm
Profile", profileId, autoScaleVmProfileDao);
- boolean physicalParameterUpdate = (templateId != null ||
autoscaleUserId != null || counterParamList != null || otherDeployParams !=
null || expungeVmGracePeriod != null || userData != null);
+ boolean physicalParameterUpdate = (templateId != null ||
autoscaleUserId != null || counterParamList != null
+ || otherDeployParams != null || expungeVmGracePeriod != null
|| userdataUpdate);
if (serviceOfferingId != null) {
vmProfile.setServiceOfferingId(serviceOfferingId);
@@ -609,10 +634,6 @@ public class AutoScaleManagerImpl extends ManagerBase
implements AutoScaleManage
vmProfile.setCounterParamsForUpdate(counterParamList);
}
- if (userData != null) {
- vmProfile.setUserData(userData);
- }
-
if (expungeVmGracePeriod != null) {
vmProfile.setExpungeVmGracePeriod(expungeVmGracePeriod);
}
@@ -625,6 +646,18 @@ public class AutoScaleManagerImpl extends ManagerBase
implements AutoScaleManage
vmProfile.setDisplay(cmd.getDisplay());
}
+ if (userdataUpdate) {
+ if (templateId == null) {
+ templateId = vmProfile.getTemplateId();
+ }
+ VirtualMachineTemplate template =
entityMgr.findByIdIncludingRemoved(VirtualMachineTemplate.class, templateId);
+ userData = userVmMgr.finalizeUserData(userData, userDataId,
template);
+ userData = userVmMgr.validateUserData(userData,
cmd.getHttpMethod());
+ vmProfile.setUserDataId(userDataId);
+ vmProfile.setUserData(userData);
+ vmProfile.setUserDataDetails(userDataDetails);
+ }
+
List<AutoScaleVmGroupVO> vmGroupList =
autoScaleVmGroupDao.listByAll(null, profileId);
for (AutoScaleVmGroupVO vmGroupVO : vmGroupList) {
if (physicalParameterUpdate &&
!vmGroupVO.getState().equals(AutoScaleVmGroup.State.DISABLED)) {
@@ -1740,6 +1773,8 @@ public class AutoScaleManagerImpl extends ManagerBase
implements AutoScaleManage
}
String userData = profileVo.getUserData();
+ Long userDataId = profileVo.getUserDataId();
+ String userDataDetails = profileVo.getUserDataDetails();
UserVm vm = null;
IpAddresses addrs = new IpAddresses(null, null);
@@ -1763,20 +1798,20 @@ public class AutoScaleManagerImpl extends ManagerBase
implements AutoScaleManage
if (zone.getNetworkType() == NetworkType.Basic) {
vm =
userVmService.createBasicSecurityGroupVirtualMachine(zone, serviceOffering,
template, null, owner, vmHostName,
vmHostName, diskOfferingId, dataDiskSize, null,
- hypervisorType, HTTPMethod.GET, userData, null, null,
sshKeyPairs,
+ hypervisorType, HTTPMethod.GET, userData, userDataId,
userDataDetails, sshKeyPairs,
null, null, true, null, affinityGroupIdList,
customParameters, null, null, null,
null, true, overrideDiskOfferingId);
} else {
if (zone.isSecurityGroupEnabled()) {
vm =
userVmService.createAdvancedSecurityGroupVirtualMachine(zone, serviceOffering,
template, networkIds, null,
owner, vmHostName,vmHostName, diskOfferingId,
dataDiskSize, null,
- hypervisorType, HTTPMethod.GET, userData, null,
null, sshKeyPairs,
+ hypervisorType, HTTPMethod.GET, userData,
userDataId, userDataDetails, sshKeyPairs,
null, null, true, null, affinityGroupIdList,
customParameters, null, null, null,
null, true, overrideDiskOfferingId, null);
} else {
vm = userVmService.createAdvancedVirtualMachine(zone,
serviceOffering, template, networkIds, owner, vmHostName, vmHostName,
diskOfferingId, dataDiskSize, null,
- hypervisorType, HTTPMethod.GET, userData, null,
null, sshKeyPairs,
+ hypervisorType, HTTPMethod.GET, userData,
userDataId, userDataDetails, sshKeyPairs,
null, addrs, true, null, affinityGroupIdList,
customParameters, null, null, null,
null, true, null, overrideDiskOfferingId);
}
diff --git a/server/src/main/java/com/cloud/vm/UserVmManager.java
b/server/src/main/java/com/cloud/vm/UserVmManager.java
index 39f1e5d2d28..4f1396913cc 100644
--- a/server/src/main/java/com/cloud/vm/UserVmManager.java
+++ b/server/src/main/java/com/cloud/vm/UserVmManager.java
@@ -92,6 +92,10 @@ public interface UserVmManager extends UserVmService {
void removeInstanceFromInstanceGroup(long vmId);
+ String finalizeUserData(String userData, Long userDataId,
VirtualMachineTemplate template);
+
+ String validateUserData(String userData, HTTPMethod httpmethod);
+
boolean isVMUsingLocalStorage(VMInstanceVO vm);
boolean expunge(UserVmVO vm);
diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
index ecf60556db6..94ceb0de363 100644
--- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
+++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
@@ -4769,7 +4769,8 @@ public class UserVmManagerImpl extends ManagerBase
implements UserVmManager, Vir
}
}
- protected String validateUserData(String userData, HTTPMethod httpmethod) {
+ @Override
+ public String validateUserData(String userData, HTTPMethod httpmethod) {
byte[] decodedUserData = null;
if (userData != null) {
@@ -5703,7 +5704,8 @@ public class UserVmManagerImpl extends ManagerBase
implements UserVmManager, Vir
return userVm.getHypervisorType();
}
- protected String finalizeUserData(String userData, Long userDataId,
VirtualMachineTemplate template) {
+ @Override
+ public String finalizeUserData(String userData, Long userDataId,
VirtualMachineTemplate template) {
if (StringUtils.isEmpty(userData) && userDataId == null && (template
== null || template.getUserDataId() == null)) {
return null;
}
diff --git a/server/src/test/java/com/cloud/api/ApiResponseHelperTest.java
b/server/src/test/java/com/cloud/api/ApiResponseHelperTest.java
index f7cf7f58b2c..fff6fb2a950 100644
--- a/server/src/test/java/com/cloud/api/ApiResponseHelperTest.java
+++ b/server/src/test/java/com/cloud/api/ApiResponseHelperTest.java
@@ -32,6 +32,7 @@ import java.util.UUID;
import org.apache.cloudstack.annotation.dao.AnnotationDao;
import org.apache.cloudstack.api.response.AutoScaleVmGroupResponse;
+import org.apache.cloudstack.api.response.AutoScaleVmProfileResponse;
import org.apache.cloudstack.api.response.DirectDownloadCertificateResponse;
import org.apache.cloudstack.api.response.NicSecondaryIpResponse;
import org.apache.cloudstack.api.response.UsageRecordResponse;
@@ -52,17 +53,22 @@ import org.powermock.modules.junit4.PowerMockRunner;
import com.cloud.domain.DomainVO;
import com.cloud.network.as.AutoScaleVmGroup;
import com.cloud.network.as.AutoScaleVmGroupVO;
+import com.cloud.network.as.AutoScaleVmProfileVO;
import com.cloud.network.as.dao.AutoScaleVmGroupVmMapDao;
import com.cloud.network.dao.IPAddressVO;
import com.cloud.network.dao.LoadBalancerVO;
import com.cloud.network.dao.NetworkServiceMapDao;
import com.cloud.network.dao.NetworkVO;
+import com.cloud.storage.VMTemplateVO;
import com.cloud.usage.UsageVO;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.user.AccountVO;
import com.cloud.user.User;
+import com.cloud.user.UserData;
+import com.cloud.user.UserDataVO;
import com.cloud.user.UserVO;
+import com.cloud.user.dao.UserDataDao;
import com.cloud.utils.net.Ip;
import com.cloud.vm.NicSecondaryIp;
@@ -86,12 +92,27 @@ public class ApiResponseHelperTest {
@Mock
AutoScaleVmGroupVmMapDao autoScaleVmGroupVmMapDaoMock;
+ @Mock
+ UserDataDao userDataDaoMock;
+
@Spy
@InjectMocks
ApiResponseHelper apiResponseHelper = new ApiResponseHelper();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss
ZZZ");
+ static long zoneId = 1L;
+ static long domainId = 2L;
+ static long accountId = 3L;
+ static long serviceOfferingId = 4L;
+ static long templateId = 5L;
+ static String userdata = "userdata";
+ static long userdataId = 6L;
+ static String userdataDetails = "userdataDetails";
+ static String userdataNew = "userdataNew";
+
+ static long autoScaleUserId = 7L;
+
@Before
public void injectMocks() throws SecurityException, NoSuchFieldException,
IllegalArgumentException, IllegalAccessException {
@@ -297,4 +318,55 @@ public class ApiResponseHelperTest {
assertEquals("8080", response.getPublicPort());
assertEquals("8081", response.getPrivatePort());
}
+
+ @Test
+ @PrepareForTest(ApiDBUtils.class)
+ public void testAutoScaleVmProfileResponse() {
+ AutoScaleVmProfileVO vmProfile = new AutoScaleVmProfileVO(zoneId,
domainId, accountId, serviceOfferingId, templateId, null, null, userdata, null,
autoScaleUserId);
+ vmProfile.setUserDataId(userdataId);
+ vmProfile.setUserDataDetails(userdataDetails);
+
+ PowerMockito.mockStatic(ApiDBUtils.class);
+ when(ApiDBUtils.findAccountById(anyLong())).thenReturn(new
AccountVO());
+ when(ApiDBUtils.findDomainById(anyLong())).thenReturn(new DomainVO());
+
+ UserData.UserDataOverridePolicy templatePolicy =
UserData.UserDataOverridePolicy.APPEND;
+ VMTemplateVO templateVO = Mockito.mock(VMTemplateVO.class);
+ when(ApiDBUtils.findTemplateById(anyLong())).thenReturn(templateVO);
+
when(templateVO.getUserDataOverridePolicy()).thenReturn(templatePolicy);
+
+ UserDataVO userDataVO = Mockito.mock(UserDataVO.class);
+ String userDataUuid = "userDataUuid";
+ String userDataName = "userDataName";
+ when(userDataDaoMock.findById(anyLong())).thenReturn(userDataVO);
+ when(userDataVO.getUuid()).thenReturn(userDataUuid);
+ when(userDataVO.getName()).thenReturn(userDataName);
+
+ AutoScaleVmProfileResponse response =
apiResponseHelper.createAutoScaleVmProfileResponse(vmProfile);
+ assertEquals(templatePolicy.toString(), response.getUserDataPolicy());
+ assertEquals(userdata, response.getUserData());
+ assertEquals(userDataUuid, response.getUserDataId());
+ assertEquals(userDataName, response.getUserDataName());
+ assertEquals(userdataDetails, response.getUserDataDetails());
+ }
+
+ @Test
+ @PrepareForTest(ApiDBUtils.class)
+ public void testAutoScaleVmProfileResponseWithoutUserData() {
+ AutoScaleVmProfileVO vmProfile = new AutoScaleVmProfileVO(zoneId,
domainId, accountId, serviceOfferingId, templateId, null, null, null, null,
autoScaleUserId);
+
+ PowerMockito.mockStatic(ApiDBUtils.class);
+ when(ApiDBUtils.findAccountById(anyLong())).thenReturn(new
AccountVO());
+ when(ApiDBUtils.findDomainById(anyLong())).thenReturn(new DomainVO());
+
+ VMTemplateVO templateVO = Mockito.mock(VMTemplateVO.class);
+ when(ApiDBUtils.findTemplateById(anyLong())).thenReturn(templateVO);
+
+ AutoScaleVmProfileResponse response =
apiResponseHelper.createAutoScaleVmProfileResponse(vmProfile);
+ assertNull(response.getUserDataPolicy());
+ assertNull(response.getUserData());
+ assertNull(response.getUserDataId());
+ assertNull(response.getUserDataName());
+ assertNull(response.getUserDataDetails());
+ }
}
diff --git
a/server/src/test/java/com/cloud/network/as/AutoScaleManagerImplTest.java
b/server/src/test/java/com/cloud/network/as/AutoScaleManagerImplTest.java
index e60ce86fc3f..08070faf92c 100644
--- a/server/src/test/java/com/cloud/network/as/AutoScaleManagerImplTest.java
+++ b/server/src/test/java/com/cloud/network/as/AutoScaleManagerImplTest.java
@@ -47,6 +47,7 @@ import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
import org.apache.cloudstack.annotation.AnnotationService;
import org.apache.cloudstack.annotation.dao.AnnotationDao;
import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.command.admin.autoscale.CreateCounterCmd;
import
org.apache.cloudstack.api.command.user.autoscale.CreateAutoScalePolicyCmd;
import
org.apache.cloudstack.api.command.user.autoscale.CreateAutoScaleVmGroupCmd;
@@ -340,6 +341,10 @@ public class AutoScaleManagerImplTest {
private static final Long scaleDownCounterId = 38L;
private static final Long nextVmSeq = 39L;
private static final Long networkOfferingId = 40L;
+ private static final String userData = "VGVzdFVzZXJEYXRh"; //TestUserData
+ private static final Long userDataId = 41L;
+ private static final Map<String, HashMap<String, String>> userDataDetails
= new HashMap<>();
+ private static final String userDataFinal = "VGVzdFVzZXJEYXRhRmluYWw=";
//TestUserDataFinal
@Mock
DataCenterVO zoneMock;
@@ -404,6 +409,10 @@ public class AutoScaleManagerImplTest {
Mockito.doNothing().when(accountManager).checkAccess(Mockito.any(Account.class),
Mockito.isNull(), Mockito.anyBoolean(), Mockito.any());
when(asPolicyDao.persist(any(AutoScalePolicyVO.class))).thenReturn(asScaleUpPolicyMock);
+
+ userDataDetails.put("0", new HashMap<>() {{ put("key1", "value1");
put("key2", "value2"); }});
+
Mockito.doReturn(userDataFinal).when(userVmMgr).finalizeUserData(any(), any(),
any());
+
Mockito.doReturn(userDataFinal).when(userVmMgr).validateUserData(eq(userDataFinal),
nullable(BaseCmd.HTTPMethod.class));
}
@After
@@ -748,10 +757,48 @@ public class AutoScaleManagerImplTest {
ReflectionTestUtils.setField(cmd, "otherDeployParams",
otherDeployParams);
ReflectionTestUtils.setField(cmd, "counterParamList",
counterParamList);
+ ReflectionTestUtils.setField(cmd, "userData", userData);
+ ReflectionTestUtils.setField(cmd, "userDataId", userDataId);
+ ReflectionTestUtils.setField(cmd, "userDataDetails", userDataDetails);
+
AutoScaleVmProfile vmProfile =
autoScaleManagerImplSpy.createAutoScaleVmProfile(cmd);
Assert.assertEquals(asVmProfileMock, vmProfile);
Mockito.verify(autoScaleVmProfileDao).persist(Mockito.any());
+
+ Mockito.verify(userVmMgr).finalizeUserData(any(), any(), any());
+ Mockito.verify(userVmMgr).validateUserData(eq(userDataFinal),
nullable(BaseCmd.HTTPMethod.class));
+ }
+
+ @Test(expected = InvalidParameterValueException.class)
+ @PrepareForTest(ComponentContext.class)
+ public void testCreateAutoScaleVmProfileFail() {
+ when(entityManager.findById(DataCenter.class,
zoneId)).thenReturn(zoneMock);
+ when(entityManager.findById(ServiceOffering.class,
serviceOfferingId)).thenReturn(serviceOfferingMock);
+ when(entityManager.findByIdIncludingRemoved(ServiceOffering.class,
serviceOfferingId)).thenReturn(serviceOfferingMock);
+ when(entityManager.findById(VirtualMachineTemplate.class,
templateId)).thenReturn(templateMock);
+ when(serviceOfferingMock.isDynamic()).thenReturn(false);
+
Mockito.doThrow(InvalidParameterValueException.class).when(userVmMgr).finalizeUserData(any(),
any(), any());
+
+ DispatchChain dispatchChainMock = Mockito.mock(DispatchChain.class);
+
when(dispatchChainFactory.getStandardDispatchChain()).thenReturn(dispatchChainMock);
+ Mockito.doNothing().when(dispatchChainMock).dispatch(any());
+ PowerMockito.mockStatic(ComponentContext.class);
+
when(ComponentContext.inject(DeployVMCmd.class)).thenReturn(Mockito.mock(DeployVMCmd.class));
+
+ CreateAutoScaleVmProfileCmd cmd = new CreateAutoScaleVmProfileCmd();
+
+ ReflectionTestUtils.setField(cmd, "zoneId", zoneId);
+ ReflectionTestUtils.setField(cmd, "serviceOfferingId",
serviceOfferingId);
+ ReflectionTestUtils.setField(cmd, "templateId", templateId);
+ ReflectionTestUtils.setField(cmd, "expungeVmGracePeriod",
expungeVmGracePeriod);
+ ReflectionTestUtils.setField(cmd, "otherDeployParams",
otherDeployParams);
+ ReflectionTestUtils.setField(cmd, "counterParamList",
counterParamList);
+
+ ReflectionTestUtils.setField(cmd, "userData", userData);
+ ReflectionTestUtils.setField(cmd, "userDataId", userDataId);
+
+ AutoScaleVmProfile vmProfile =
autoScaleManagerImplSpy.createAutoScaleVmProfile(cmd);
}
@Test
@@ -774,10 +821,17 @@ public class AutoScaleManagerImplTest {
ReflectionTestUtils.setField(cmd, "serviceOfferingId",
serviceOfferingId);
ReflectionTestUtils.setField(cmd, "templateId", templateId);
+ ReflectionTestUtils.setField(cmd, "userData", userData);
+ ReflectionTestUtils.setField(cmd, "userDataId", userDataId);
+ ReflectionTestUtils.setField(cmd, "userDataDetails", userDataDetails);
+
AutoScaleVmProfile vmProfile =
autoScaleManagerImplSpy.updateAutoScaleVmProfile(cmd);
Assert.assertEquals(asVmProfileMock, vmProfile);
Mockito.verify(autoScaleVmProfileDao).persist(Mockito.any());
+
+ Mockito.verify(userVmMgr).finalizeUserData(any(), any(), any());
+ Mockito.verify(userVmMgr).validateUserData(eq(userDataFinal),
nullable(BaseCmd.HTTPMethod.class));
}
@Test
@@ -1208,6 +1262,9 @@ public class AutoScaleManagerImplTest {
when(asVmProfileMock.getAccountId()).thenReturn(accountId);
when(asVmProfileMock.getZoneId()).thenReturn(zoneId);
when(asVmProfileMock.getOtherDeployParams()).thenReturn("");
+ when(asVmProfileMock.getUserData()).thenReturn(userData);
+ when(asVmProfileMock.getUserDataId()).thenReturn(userDataId);
+
when(asVmProfileMock.getUserDataDetails()).thenReturn(userDataDetails.toString());
when(accountService.getActiveAccountById(accountId)).thenReturn(account);
when(entityManager.findById(DataCenter.class,
zoneId)).thenReturn(zoneMock);
@@ -1224,7 +1281,7 @@ public class AutoScaleManagerImplTest {
when(userVmMock.getId()).thenReturn(virtualMachineId);
when(zoneMock.getNetworkType()).thenReturn(DataCenter.NetworkType.Basic);
when(userVmService.createBasicSecurityGroupVirtualMachine(any(),
any(), any(), any(), any(), any(), any(),
- any(), any(), any(), any(), any(), any(), any(), any(), any(),
any(), any(), eq(true), any(), any(), any(),
+ any(), any(), any(), any(), any(), eq(userData),
eq(userDataId), eq(userDataDetails.toString()), any(), any(), any(), eq(true),
any(), any(), any(),
any(), any(), any(), any(), eq(true),
any())).thenReturn(userVmMock);
long result = autoScaleManagerImplSpy.createNewVM(asVmGroupMock);
@@ -1235,7 +1292,7 @@ public class AutoScaleManagerImplTest {
"-" + asVmGroupMock.getNextVmSeq() + "-[a-z]{6}";
Mockito.verify(userVmService).createBasicSecurityGroupVirtualMachine(any(),
any(), any(), any(), any(),
matches(vmHostNamePattern), matches(vmHostNamePattern),
- any(), any(), any(), any(), any(), any(), any(), any(), any(),
any(), any(), eq(true), any(), any(), any(),
+ any(), any(), any(), any(), any(), eq(userData),
eq(userDataId), eq(userDataDetails.toString()), any(), any(), any(), eq(true),
any(), any(), any(),
any(), any(), any(), any(), eq(true), any());
Mockito.verify(asVmGroupMock).setNextVmSeq(nextVmSeq + 1);
}
@@ -1253,6 +1310,9 @@ public class AutoScaleManagerImplTest {
when(asVmProfileMock.getAccountId()).thenReturn(accountId);
when(asVmProfileMock.getZoneId()).thenReturn(zoneId);
when(asVmProfileMock.getOtherDeployParams()).thenReturn("");
+ when(asVmProfileMock.getUserData()).thenReturn(userData);
+ when(asVmProfileMock.getUserDataId()).thenReturn(userDataId);
+
when(asVmProfileMock.getUserDataDetails()).thenReturn(userDataDetails.toString());
when(accountService.getActiveAccountById(accountId)).thenReturn(account);
when(entityManager.findById(DataCenter.class,
zoneId)).thenReturn(zoneMock);
@@ -1270,7 +1330,7 @@ public class AutoScaleManagerImplTest {
when(zoneMock.getNetworkType()).thenReturn(DataCenter.NetworkType.Advanced);
when(zoneMock.isSecurityGroupEnabled()).thenReturn(true);
when(userVmService.createAdvancedSecurityGroupVirtualMachine(any(),
any(), any(), any(), any(), any(), any(),
- any(), any(), any(), any(), any(), any(), any(), any(), any(),
any(), any(), any(), any(), any(), any(),
+ any(), any(), any(), any(), any(), any(), eq(userData),
eq(userDataId), eq(userDataDetails.toString()), any(), any(), any(), any(),
any(), any(),
any(), any(), any(), any(), any(), eq(true), any(),
any())).thenReturn(userVmMock);
long result = autoScaleManagerImplSpy.createNewVM(asVmGroupMock);
@@ -1281,7 +1341,7 @@ public class AutoScaleManagerImplTest {
"-" + asVmGroupMock.getNextVmSeq() + "-[a-z]{6}";
Mockito.verify(userVmService).createAdvancedSecurityGroupVirtualMachine(any(),
any(), any(), any(), any(), any(),
matches(vmHostNamePattern), matches(vmHostNamePattern),
- any(), any(), any(), any(), any(), any(), any(), any(), any(),
any(), any(), any(), any(), any(),
+ any(), any(), any(), any(), any(), eq(userData),
eq(userDataId), eq(userDataDetails.toString()), any(), any(), any(), any(),
any(), any(),
any(), any(), any(), any(), any(), eq(true), any(), any());
Mockito.verify(asVmGroupMock).setNextVmSeq(nextVmSeq + 2);
}
@@ -1299,6 +1359,9 @@ public class AutoScaleManagerImplTest {
when(asVmProfileMock.getAccountId()).thenReturn(accountId);
when(asVmProfileMock.getZoneId()).thenReturn(zoneId);
when(asVmProfileMock.getOtherDeployParams()).thenReturn("");
+ when(asVmProfileMock.getUserData()).thenReturn(userData);
+ when(asVmProfileMock.getUserDataId()).thenReturn(userDataId);
+
when(asVmProfileMock.getUserDataDetails()).thenReturn(userDataDetails.toString());
when(accountService.getActiveAccountById(accountId)).thenReturn(account);
when(entityManager.findById(DataCenter.class,
zoneId)).thenReturn(zoneMock);
@@ -1316,7 +1379,7 @@ public class AutoScaleManagerImplTest {
when(zoneMock.getNetworkType()).thenReturn(DataCenter.NetworkType.Advanced);
when(zoneMock.isSecurityGroupEnabled()).thenReturn(false);
when(userVmService.createAdvancedVirtualMachine(any(), any(), any(),
any(), any(), any(), any(),
- any(), any(), any(), any(), any(), any(), any(), any(), any(),
any(), any(), eq(true), any(), any(), any(),
+ any(), any(), any(), any(), any(), eq(userData),
eq(userDataId), eq(userDataDetails.toString()), any(), any(), any(), eq(true),
any(), any(), any(),
any(), any(), any(), any(), eq(true), any(),
any())).thenReturn(userVmMock);
long result = autoScaleManagerImplSpy.createNewVM(asVmGroupMock);
@@ -1327,7 +1390,7 @@ public class AutoScaleManagerImplTest {
"-" + asVmGroupMock.getNextVmSeq() + "-[a-z]{6}";
Mockito.verify(userVmService).createAdvancedVirtualMachine(any(),
any(), any(), any(), any(),
matches(vmHostNamePattern), matches(vmHostNamePattern),
- any(), any(), any(), any(), any(), any(), any(), any(), any(),
any(), any(), eq(true), any(), any(), any(),
+ any(), any(), any(), any(), any(), eq(userData),
eq(userDataId), eq(userDataDetails.toString()), any(), any(), any(), eq(true),
any(), any(), any(),
any(), any(), any(), any(), eq(true), any(), any());
Mockito.verify(asVmGroupMock).setNextVmSeq(nextVmSeq + 3);
}
diff --git a/test/integration/smoke/test_vm_autoscaling.py
b/test/integration/smoke/test_vm_autoscaling.py
index d9fa7e23b79..316f94fd5fd 100644
--- a/test/integration/smoke/test_vm_autoscaling.py
+++ b/test/integration/smoke/test_vm_autoscaling.py
@@ -38,6 +38,7 @@ from marvin.lib.base import (Account,
Domain,
Project,
ServiceOffering,
+ Template,
VirtualMachine,
Volume,
Zone,
@@ -47,6 +48,7 @@ from marvin.lib.base import (Account,
LoadBalancerRule,
VPC,
VpcOffering,
+ UserData,
SSHKeyPair)
from marvin.lib.common import (get_domain,
@@ -198,6 +200,37 @@ class TestVmAutoScaling(cloudstackTestCase):
name="keypair2"
)
+ # 8-2. Register userdata
+ cls.apiUserdata = UserData.register(
+ cls.apiclient,
+ name="ApiUserdata",
+ userdata="QVBJdXNlcmRhdGE=", #APIuserdata
+ account=cls.regular_user.name,
+ domainid=cls.regular_user.domainid
+ )
+
+ # 8-3. Register userdata for template
+ cls.templateUserdata = UserData.register(
+ cls.apiclient,
+ name="TemplateUserdata",
+
userdata="IyMgdGVtcGxhdGU6IGppbmphCiNjbG91ZC1jb25maWcKcnVuY21kOgogICAgLSBlY2hvICdrZXkge3sgZHMubWV0YV9kYXRhLmtleTEgfX0nID4+IC9yb290L2luc3RhbmNlX21ldGFkYXRhCgo=",
+ # ## template: jinja
+ # #cloud-config
+ # runcmd:
+ # - echo 'key {{ ds.meta_data.key1 }}' >>
/root/instance_metadata
+ #
+ account=cls.regular_user.name,
+ domainid=cls.regular_user.domainid
+ )
+
+ # 8-3. Link userdata to template
+ cls.template = Template.linkUserDataToTemplate(
+ cls.apiclient,
+ templateid=cls.template.id,
+ userdataid=cls.templateUserdata.userdata.id,
+ userdatapolicy="append"
+ )
+
# 9. Get counters for cpu and memory
counters = Autoscale.listCounters(
cls.regular_user_apiclient,
@@ -294,6 +327,7 @@ class TestVmAutoScaling(cloudstackTestCase):
serviceofferingid=cls.service_offering.id,
zoneid=cls.zone.id,
templateid=cls.template.id,
+ userdata="VGVzdFVzZXJEYXRh", #TestUserData
expungevmgraceperiod=DEFAULT_EXPUNGE_VM_GRACE_PERIOD,
otherdeployparams=cls.otherdeployparams
)
@@ -349,6 +383,10 @@ class TestVmAutoScaling(cloudstackTestCase):
@classmethod
def tearDownClass(cls):
+ cls.template = Template.linkUserDataToTemplate(
+ cls.apiclient,
+ templateid=cls.template.id
+ )
Configurations.update(cls.apiclient,
CONFIG_NAME_DISK_CONTROLLER,
cls.initial_vmware_root_disk_controller)
@@ -390,6 +428,7 @@ class TestVmAutoScaling(cloudstackTestCase):
self.regular_user_apiclient,
autoscalevmgroupid=autoscalevmgroupid,
projectid=projectid,
+ userdata=True,
listall=True
)
self.assertEqual(
@@ -505,6 +544,31 @@ class TestVmAutoScaling(cloudstackTestCase):
else:
self.assertEquals(affinitygroupids, '')
+ userdata = None
+ userdatadetails = None
+ userdataid = None
+ if vm.userdata:
+ userdata = vm.userdata
+ if vm.userdatadetails:
+ userdatadetails = vm.userdatadetails
+ if vm.userdataid:
+ userdataid = vm.userdataid
+
+ if vmprofile.userdataid:
+ self.assertEquals(userdataid, vmprofile.userdataid)
+ else:
+ self.assertIsNone(userdataid)
+
+ if vmprofile.userdatadetails:
+ self.assertEquals(userdatadetails, vmprofile.userdatadetails)
+ else:
+ self.assertIsNone(userdatadetails)
+
+ if vmprofile.userdata:
+ self.assertEquals(userdata, vmprofile.userdata)
+ else:
+ self.assertIsNone(userdata)
+
def wait_for_vm_start(self, vm=None, project_id=None):
""" Wait until vm is Running """
def check_user_vm_state():
@@ -512,6 +576,7 @@ class TestVmAutoScaling(cloudstackTestCase):
self.apiclient,
id=vm.id,
projectid=project_id,
+ userdata=True,
listall=True
)
if isinstance(vms, list):
@@ -576,6 +641,8 @@ class TestVmAutoScaling(cloudstackTestCase):
Autoscale.updateAutoscaleVMProfile(
self.regular_user_apiclient,
id = self.autoscaling_vmprofile.id,
+ userdataid=self.apiUserdata.userdata.id,
+ userdatadetails=[{"key1": "value2"}],
serviceofferingid = self.service_offering_new.id,
expungevmgraceperiod = DEFAULT_EXPUNGE_VM_GRACE_PERIOD + 1,
otherdeployparams = otherdeployparams_new
@@ -712,6 +779,7 @@ class TestVmAutoScaling(cloudstackTestCase):
vms = VirtualMachine.list(
self.regular_user_apiclient,
autoscalevmgroupid=self.autoscaling_vmgroup.id,
+ userdata=True,
listall=True
)
self.assertEqual(
@@ -889,6 +957,7 @@ class TestVmAutoScaling(cloudstackTestCase):
self.regular_user_apiclient,
autoscalevmgroupid=autoscaling_vmgroup_project.id,
projectid=project.id,
+ userdata=True,
listall=True
)
self.assertEqual(
diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json
index f93cbd233f7..dcdd0bf97c1 100644
--- a/ui/public/locales/en.json
+++ b/ui/public/locales/en.json
@@ -1625,6 +1625,7 @@
"label.reset.config.value": "Reset to default value",
"label.reset.ssh.key.pair": "Reset SSH key pair",
"label.reset.to.default": "Reset to default",
+"label.reset.userdata.on.autoscale.vm.group": "Reset Userdata on AutoScale VM
Group",
"label.reset.userdata.on.vm": "Reset Userdata on VM",
"label.reset.vpn.connection": "Reset VPN connection",
"label.resource": "Resource",
diff --git a/ui/src/views/compute/AutoScaleVmProfile.vue
b/ui/src/views/compute/AutoScaleVmProfile.vue
index 45947753b06..12081dfad72 100644
--- a/ui/src/views/compute/AutoScaleVmProfile.vue
+++ b/ui/src/views/compute/AutoScaleVmProfile.vue
@@ -69,6 +69,47 @@
{{ getServiceOfferingName(serviceofferingid) }}
</div>
</div>
+ <div class="form" v-if="userdataid">
+ <div class="form__item">
+ <div class="form__label">
+ <tooltip-label :title="$t('label.userdataid')"/>
+ </div>
+ {{ userdataid }}
+ </div>
+ </div>
+ <div class="form" v-if="userdataname">
+ <div class="form__item">
+ <div class="form__label">
+ <tooltip-label :title="$t('label.userdataname')"/>
+ </div>
+ {{ userdataname }}
+ </div>
+ </div>
+ <div class="form" v-if="userdatadetails">
+ <div class="form__item">
+ <div class="form__label">
+ <tooltip-label :title="$t('label.userdatadetails')"/>
+ </div>
+ {{ userdatadetails }}
+ </div>
+ </div>
+ <div class="form" v-if="userdatapolicy">
+ <div class="form__item">
+ <div class="form__label">
+ <tooltip-label :title="$t('label.userdatapolicy')"/>
+ </div>
+ {{ userdatapolicy }}
+ </div>
+ </div>
+ <div class="form">
+ <div class="form__item">
+ <div class="form__label">
+ <tooltip-label :title="$t('label.userdata')"
:tooltip="createAutoScaleVmProfileApiParams.userdata.description"/>
+ </div>
+ <a-textarea v-model:value="userdata" rows="5" :disabled="true">
+ </a-textarea>
+ </div>
+ </div>
<div class="form">
<div class="form__item">
<a-button ref="submit" :disabled="!('updateAutoScaleVmProfile' in
$store.getters.apis) || resource.state !== 'DISABLED'" type="primary"
@click="editProfileModalVisible = true">
@@ -76,6 +117,12 @@
{{ $t('label.edit.autoscale.vmprofile') }}
</a-button>
</div>
+ <div class="form__item">
+ <a-button ref="submit" :disabled="!('updateAutoScaleVmProfile' in
$store.getters.apis) || resource.state !== 'DISABLED'" type="primary"
@click="showUpdateUserDataForm = true">
+ <template #icon><solution-outlined /></template>
+ {{ $t('label.reset.userdata.on.autoscale.vm.group') }}
+ </a-button>
+ </div>
</div>
<a-divider/>
@@ -224,20 +271,26 @@
</a-select>
</div>
</div>
- <div class="form">
- <div class="form__item">
- <div class="form__label">
- <tooltip-label :title="$t('label.userdata')"
:tooltip="createAutoScaleVmProfileApiParams.userdata.description"/>
- </div>
- <a-textarea v-model:value="userdata">
- </a-textarea>
- </div>
- </div>
<div :span="24" class="action-button">
<a-button :loading="loading" @click="closeModal">{{ $t('label.cancel')
}}</a-button>
<a-button :loading="loading" ref="submit" type="primary"
@click="updateAutoScaleVmProfile">{{ $t('label.ok') }}</a-button>
</div>
</a-modal>
+
+ <a-modal
+ :visible="showUpdateUserDataForm"
+ :title="$t('label.reset.userdata.on.autoscale.vm.group')"
+ :closable="true"
+ :maskClosable="false"
+ :footer="null"
+ @cancel="showUpdateUserDataForm = false"
+ centered
+ width="auto">
+ <reset-user-data
+ :resource="{ ...resource, ...{ resetUserDataApiName:
'updateAutoScaleVmProfile', resetUserDataResourceId: this.resource.vmprofileid,
templateid: this.templateid}}"
+ @close-action="showUpdateUserDataForm = false"
+ />
+ </a-modal>
</div>
</template>
@@ -247,10 +300,12 @@ import { isAdmin, isAdminOrDomainAdmin } from '@/role'
import Status from '@/components/widgets/Status'
import TooltipButton from '@/components/widgets/TooltipButton'
import TooltipLabel from '@/components/widgets/TooltipLabel'
+import ResetUserData from '@views/compute/ResetUserData'
export default {
name: 'conditionsTab',
components: {
+ ResetUserData,
Status,
TooltipButton,
TooltipLabel
@@ -266,12 +321,17 @@ export default {
filterColumns: ['Action'],
loading: true,
editProfileModalVisible: false,
+ showUpdateUserDataForm: false,
profileid: null,
autoscaleuserid: null,
expungevmgraceperiod: null,
templateid: null,
serviceofferingid: null,
userdata: null,
+ userdataid: null,
+ userdataname: null,
+ userdatadetails: null,
+ userdatapolicy: null,
usersList: [],
templatesList: [],
serviceOfferingsList: [],
@@ -384,6 +444,11 @@ export default {
this.serviceofferingid =
response.listautoscalevmprofilesresponse?.autoscalevmprofile?.[0]?.serviceofferingid
this.templateid =
response.listautoscalevmprofilesresponse?.autoscalevmprofile?.[0]?.templateid
this.userdata =
this.decodeUserData(decodeURIComponent(response.listautoscalevmprofilesresponse?.autoscalevmprofile?.[0]?.userdata
|| ''))
+ this.userdataid =
response.listautoscalevmprofilesresponse?.autoscalevmprofile?.[0]?.userdataid
+ this.userdataname =
response.listautoscalevmprofilesresponse?.autoscalevmprofile?.[0]?.userdataname
+ this.userdatadetails =
response.listautoscalevmprofilesresponse?.autoscalevmprofile?.[0]?.userdatadetails
+ this.userdatapolicy =
response.listautoscalevmprofilesresponse?.autoscalevmprofile?.[0]?.userdatapolicy
+
const counterparam =
response.listautoscalevmprofilesresponse?.autoscalevmprofile?.[0]?.counterparam
|| {}
const otherdeployparams =
response.listautoscalevmprofilesresponse?.autoscalevmprofile?.[0]?.otherdeployparams
|| {}
this.finalizeParams(counterparam, otherdeployparams)
@@ -518,13 +583,10 @@ export default {
if (this.autoscaleuserid) {
params.autoscaleuserid = this.autoscaleuserid
}
- if (this.userdata && this.userdata.length > 0) {
- params.userdata = this.$toBase64AndURIEncoded(this.userdata)
- }
- const httpMethod = params.userdata ? 'POST' : 'GET'
- const args = httpMethod === 'POST' ? {} : params
- const data = httpMethod === 'POST' ? params : {}
+ const httpMethod = 'GET'
+ const args = params
+ const data = {}
api('updateAutoScaleVmProfile', args, httpMethod, data).then(response =>
{
this.$pollJob({
diff --git a/ui/src/views/compute/CreateAutoScaleVmGroup.vue
b/ui/src/views/compute/CreateAutoScaleVmGroup.vue
index b764f8185e2..81da8a9b6d7 100644
--- a/ui/src/views/compute/CreateAutoScaleVmGroup.vue
+++ b/ui/src/views/compute/CreateAutoScaleVmGroup.vue
@@ -407,7 +407,7 @@
<span v-if="property.type &&
property.type==='boolean'">
<a-switch
- v-model:cheked="form['properties.' +
escapePropertyKey(property.key)]"
+ v-model:checked="form['properties.' +
escapePropertyKey(property.key)]"
:placeholder="property.description"
/>
</span>
@@ -760,13 +760,100 @@
@select-affinity-group-item="($event) =>
updateAffinityGroups($event)"
@handle-search-filter="($event) =>
handleSearchFilter('affinityGroups', $event)"/>
</a-form-item>
- <a-form-item name="userdata" ref="userdata">
+ <a-form-item>
<template #label>
<tooltip-label :title="$t('label.userdata')"
:tooltip="createAutoScaleVmProfileApiParams.userdata.description"/>
</template>
- <a-textarea
- v-model:value="form.userdata">
- </a-textarea>
+ <a-card>
+ <div v-if="this.template && this.template.userdataid">
+ <a-text type="primary">
+ Userdata "{{ $t(this.template.userdataname) }}" is
linked with template "{{ $t(this.template.name) }}" with override policy "{{
$t(this.template.userdatapolicy) }}"
+ </a-text><br/><br/>
+ <div v-if="templateUserDataParams.length > 0 &&
!doUserdataOverride">
+ <a-text type="primary" v-if="this.template &&
this.template.userdataid && templateUserDataParams.length > 0">
+ Enter the values for the variables in userdata
+ </a-text>
+ <a-input-group>
+ <a-table
+ size="small"
+ style="overflow-y: auto"
+ :columns="userDataParamCols"
+ :dataSource="templateUserDataParams"
+ :pagination="false"
+ :rowKey="record => record.key">
+ <template #value="{ record }">
+ <a-input
v-model:value="templateUserDataValues[record.key]" />
+ </template>
+ </a-table>
+ </a-input-group>
+ </div>
+ </div><br/><br/>
+ <div v-if="userdataDefaultOverridePolicy ===
'ALLOWOVERRIDE' || userdataDefaultOverridePolicy === 'APPEND' ||
!userdataDefaultOverridePolicy">
+ <span v-if="userdataDefaultOverridePolicy ===
'ALLOWOVERRIDE'" >
+ {{ $t('label.userdata.do.override') }}
+ <a-switch v-model:checked="doUserdataOverride"
style="margin-left: 10px"/>
+ </span>
+ <span v-if="userdataDefaultOverridePolicy ===
'APPEND'">
+ {{ $t('label.userdata.do.append') }}
+ <a-switch v-model:checked="doUserdataAppend"
style="margin-left: 10px"/>
+ </span>
+ <a-step
+ :status="zoneSelected ? 'process' : 'wait'">
+ <template #description>
+ <div v-if="doUserdataOverride ||
doUserdataAppend || !userdataDefaultOverridePolicy" style="margin-top: 15px">
+ <a-card
+ :tabList="userdataTabList"
+ :activeTabKey="userdataTabKey"
+ @tabChange="key => onUserdataTabChange(key,
'userdataTabKey')">
+ <div v-if="userdataTabKey ===
'userdataregistered'">
+ <a-step
+ v-if="isUserAllowedToListUserDatas"
+ :status="zoneSelected ? 'process' :
'wait'">
+ <template #description>
+ <div v-if="zoneSelected">
+ <user-data-selection
+ :items="options.userDatas"
+ :row-count="rowCount.userDatas"
+ :zoneId="zoneId"
+ :disabled="template.userdatapolicy
=== 'DENYOVERRIDE'"
+ :loading="loading.userDatas"
+ :preFillContent="dataPreFill"
+ @select-user-data-item="($event)
=> updateUserData($event)"
+ @handle-search-filter="($event) =>
handleSearchFilter('userData', $event)"
+ />
+ <div v-if="userDataParams.length >
0">
+ <a-input-group>
+ <a-table
+ size="small"
+ style="overflow-y: auto"
+ :columns="userDataParamCols"
+ :dataSource="userDataParams"
+ :pagination="false"
+ :rowKey="record => record.key">
+ <template #value="{ record }">
+ <a-input
v-model:value="userDataValues[record.key]" />
+ </template>
+ </a-table>
+ </a-input-group>
+ </div>
+ </div>
+ </template>
+ </a-step>
+ </div>
+ <div v-else>
+ <a-form-item name="userdata"
ref="userdata" >
+ <a-textarea
+ placeholder="Userdata"
+ v-model:value="form.userdata">
+ </a-textarea>
+ </a-form-item>
+ </div>
+ </a-card>
+ </div>
+ </template>
+ </a-step>
+ </div>
+ </a-card>
</a-form-item>
</div>
</template>
@@ -952,6 +1039,7 @@ import NetworkSelection from
'@views/compute/wizard/NetworkSelection'
import NetworkConfiguration from '@views/compute/wizard/NetworkConfiguration'
import LoadBalancerSelection from '@views/compute/wizard/LoadBalancerSelection'
import SshKeyPairSelection from '@views/compute/wizard/SshKeyPairSelection'
+import UserDataSelection from '@views/compute/wizard/UserDataSelection'
import SecurityGroupSelection from
'@views/compute/wizard/SecurityGroupSelection'
import TooltipLabel from '@/components/widgets/TooltipLabel'
import InstanceNicsNetworkSelectListView from
'@/components/view/InstanceNicsNetworkSelectListView.vue'
@@ -964,6 +1052,7 @@ export default {
name: 'Wizard',
components: {
SshKeyPairSelection,
+ UserDataSelection,
NetworkConfiguration,
NetworkSelection,
LoadBalancerSelection,
@@ -1010,6 +1099,10 @@ export default {
zoneSelected: false,
dynamicscalingenabled: true,
templateKey: 0,
+ showRegisteredUserdata: true,
+ doUserdataOverride: false,
+ doUserdataAppend: false,
+ userdataDefaultOverridePolicy: 'ALLOWOVERRIDE',
vm: {
name: null,
zoneid: null,
@@ -1034,6 +1127,7 @@ export default {
affinityGroups: [],
networks: [],
sshKeyPairs: [],
+ UserDatas: [],
loadbalancers: []
},
rowCount: {},
@@ -1045,6 +1139,7 @@ export default {
affinityGroups: false,
networks: false,
sshKeyPairs: false,
+ userDatas: false,
loadbalancers: false,
zones: false
},
@@ -1123,6 +1218,32 @@ export default {
zone: {},
sshKeyPairs: [],
sshKeyPair: {},
+ userData: {},
+ userDataParams: [],
+ userDataParamCols: [
+ {
+ title: this.$t('label.key'),
+ dataIndex: 'key'
+ },
+ {
+ title: this.$t('label.value'),
+ dataIndex: 'value',
+ slots: { customRender: 'value' }
+ }
+ ],
+ userDataValues: {},
+ templateUserDataCols: [
+ {
+ title: this.$t('label.userdata'),
+ dataIndex: 'userdata'
+ },
+ {
+ title: this.$t('label.userdatapolicy'),
+ dataIndex: 'userdataoverridepolicy'
+ }
+ ],
+ templateUserDataParams: [],
+ templateUserDataValues: {},
overrideDiskOffering: {},
templateFilter: [
'featured',
@@ -1134,6 +1255,7 @@ export default {
defaultNetworkId: '',
dataNetworkCreated: [],
tabKey: 'templateid',
+ userdataTabKey: 'userdataregistered',
dataPreFill: {},
showDetails: false,
showRootDiskSizeChanger: false,
@@ -1224,6 +1346,15 @@ export default {
listall: false
}
},
+ userDatas: {
+ list: 'listUserData',
+ options: {
+ page: 1,
+ pageSize: 10,
+ keyword: undefined,
+ listall: false
+ }
+ },
networks: {
list: 'listNetworks',
options: {
@@ -1285,12 +1416,27 @@ export default {
}]
return tabList
},
+ userdataTabList () {
+ let tabList = []
+ tabList = [{
+ key: 'userdataregistered',
+ tab: this.$t('label.userdata.registered')
+ },
+ {
+ key: 'userdatatext',
+ tab: this.$t('label.userdata.text')
+ }]
+ return tabList
+ },
showSecurityGroupSection () {
return (this.networks.length > 0 && this.zone.securitygroupsenabled) ||
(this.zone && this.zone.networktype === 'Basic')
},
isUserAllowedToListSshKeys () {
return Boolean('listSSHKeyPairs' in this.$store.getters.apis)
},
+ isUserAllowedToListUserDatas () {
+ return Boolean('listUserData' in this.$store.getters.apis)
+ },
dynamicScalingVmConfigValue () {
return this.options.dynamicScalingVmConfig?.[0]?.value === 'true'
},
@@ -1421,6 +1567,8 @@ export default {
template (oldValue, newValue) {
if (oldValue && newValue && oldValue.id !== newValue.id) {
this.dynamicscalingenabled = this.isDynamicallyScalable()
+ this.doUserdataOverride = false
+ this.doUserdataAppend = false
}
},
beforeCreate () {
@@ -1853,6 +2001,8 @@ export default {
if (template) {
var size = template.size / (1024 * 1024 * 1024) || 0 // bytes to GB
this.dataPreFill.minrootdisksize = Math.ceil(size)
+ this.updateTemplateLinkedUserData(this.template.userdataid)
+ this.userdataDefaultOverridePolicy = this.template.userdatapolicy
}
} else if (['cpuspeed', 'cpunumber', 'memory'].includes(name)) {
this.vm[name] = value
@@ -2017,6 +2167,56 @@ export default {
this.form.keypairs = names
this.sshKeyPairs = names.map((sshKeyPair) => { return sshKeyPair.name })
},
+ updateUserData (id) {
+ if (id === '0') {
+ this.form.userdataid = undefined
+ return
+ }
+ this.form.userdataid = id
+ this.userDataParams = []
+ api('listUserData', { id: id }).then(json => {
+ const resp = json?.listuserdataresponse?.userdata || []
+ if (resp) {
+ var params = resp[0].params
+ if (params) {
+ var dataParams = params.split(',')
+ }
+ var that = this
+ dataParams.forEach(function (val, index) {
+ that.userDataParams.push({
+ id: index,
+ key: val
+ })
+ })
+ }
+ })
+ },
+ updateTemplateLinkedUserData (id) {
+ if (id === '0') {
+ return
+ }
+ this.templateUserDataParams = []
+
+ api('listUserData', { id: id }).then(json => {
+ const resp = json?.listuserdataresponse?.userdata || []
+ if (resp) {
+ var params = resp[0].params
+ if (params) {
+ var dataParams = params.split(',')
+ }
+ var that = this
+ that.templateUserDataParams = []
+ if (dataParams) {
+ dataParams.forEach(function (val, index) {
+ that.templateUserDataParams.push({
+ id: index,
+ key: val
+ })
+ })
+ }
+ }
+ })
+ },
updateAffinityGroups (ids) {
this.form.affinitygroupids = ids
},
@@ -2035,16 +2235,20 @@ export default {
}, 1000)
})
},
- createVmProfile (createVmGroupData) {
+ createVmProfile (createVmGroupData, createVmGroupUserDataDetails) {
this.addStep('message.creating.autoscale.vmprofile', 'createVmProfile')
return new Promise((resolve, reject) => {
const params = {
- expungevmgraceperiod: createVmGroupData.expungevmgraceperiod,
- serviceofferingid: createVmGroupData.serviceofferingid,
- templateid: createVmGroupData.templateid,
- userdata: createVmGroupData.userdata,
- zoneid: createVmGroupData.zoneid
+ ...createVmGroupUserDataDetails,
+ ...{
+ expungevmgraceperiod: createVmGroupData.expungevmgraceperiod,
+ serviceofferingid: createVmGroupData.serviceofferingid,
+ templateid: createVmGroupData.templateid,
+ userdata: createVmGroupData.userdata,
+ userdataid: createVmGroupData.userdataid,
+ zoneid: createVmGroupData.zoneid
+ }
}
if (createVmGroupData.autoscaleuserid) {
params.autoscaleuserid = createVmGroupData.autoscaleuserid
@@ -2425,9 +2629,13 @@ export default {
// advanced settings
createVmGroupData.keypairs = this.sshKeyPairs.join(',')
createVmGroupData.affinitygroupids = (values.affinitygroupids ||
[]).join(',')
- if (values.userdata && values.userdata.length > 0) {
+ const isUserdataAllowed = !this.userdataDefaultOverridePolicy ||
(this.userdataDefaultOverridePolicy === 'ALLOWOVERRIDE' &&
this.doUserdataOverride) || (this.userdataDefaultOverridePolicy === 'APPEND' &&
this.doUserdataAppend)
+ if (isUserdataAllowed && values.userdata && values.userdata.length >
0) {
createVmGroupData.userdata =
this.$toBase64AndURIEncoded(values.userdata)
}
+ if (isUserdataAllowed) {
+ createVmGroupData.userdataid = values.userdataid
+ }
// vm profile details
createVmGroupData.autoscaleuserid = values.autoscaleuserid
@@ -2436,11 +2644,26 @@ export default {
createVmGroupData = Object.fromEntries(
Object.entries(createVmGroupData).filter(([key, value]) => value !==
undefined))
+ const createVmGroupUserDataDetails = {}
+ var idx = 0
+ if (this.templateUserDataValues) {
+ for (const [key, value] of
Object.entries(this.templateUserDataValues)) {
+ createVmGroupUserDataDetails['userdatadetails[' + idx + '].' +
`${key}`] = value
+ idx++
+ }
+ }
+ if (isUserdataAllowed && this.userDataValues) {
+ for (const [key, value] of Object.entries(this.userDataValues)) {
+ createVmGroupUserDataDetails['userdatadetails[' + idx + '].' +
`${key}`] = value
+ idx++
+ }
+ }
+
this.processStatusModalVisible = true
this.processStatus = null
// create autoscale vm profile
- const vmprofile = await this.createVmProfile(createVmGroupData)
+ const vmprofile = await this.createVmProfile(createVmGroupData,
createVmGroupUserDataDetails)
// create scaleup conditions and policy
const scaleUpPolicyIds = []
@@ -2547,7 +2770,7 @@ export default {
return new Promise((resolve) => {
this.loading.zones = true
const param = this.params.zones
- const args = { listall: true, showicon: true }
+ const args = { showicon: true }
if (zoneId) args.id = zoneId
api(param.list, args).then(json => {
const zoneResponse = (json.listzonesresponse.zone || []).filter(item
=> item.securitygroupsenabled === false)
@@ -2579,7 +2802,7 @@ export default {
param.loading = true
param.opts = []
const options = param.options || {}
- if (!('listall' in options)) {
+ if (!('listall' in options) && !['zones', 'pods', 'clusters', 'hosts',
'dynamicScalingVmConfig', 'hypervisors'].includes(name)) {
options.listall = true
}
api(param.list, options).then((response) => {
@@ -2703,6 +2926,10 @@ export default {
this.params[name].options = { ...this.params[name].options, ...options }
this.fetchOptions(this.params[name], name)
},
+ onUserdataTabChange (key, type) {
+ this[type] = key
+ this.userDataParams = []
+ },
fetchTemplateNics (template) {
var nics = []
this.nicToNetworkSelection = []
diff --git a/ui/src/views/compute/ResetUserData.vue
b/ui/src/views/compute/ResetUserData.vue
index 46561f15c1a..b9753f4bc19 100644
--- a/ui/src/views/compute/ResetUserData.vue
+++ b/ui/src/views/compute/ResetUserData.vue
@@ -335,7 +335,6 @@ export default {
this.loadingData = true
console.log(values)
const params = {
- id: this.resource.id
}
if (values.userdata && values.userdata.length > 0) {
params.userdata = this.$toBase64AndURIEncoded(values.userdata)
@@ -356,7 +355,14 @@ export default {
idx++
}
}
- api('resetUserDataForVirtualMachine', params).then(json => {
+ params.id = this.resource.resetUserDataResourceId ?
this.resource.resetUserDataResourceId : this.resource.id
+
+ const resetUserDataApiName = this.resource.resetUserDataApiName ?
this.resource.resetUserDataApiName : 'resetUserDataForVirtualMachine'
+ const httpMethod = params.userdata ? 'POST' : 'GET'
+ const args = httpMethod === 'POST' ? {} : params
+ const data = httpMethod === 'POST' ? params : {}
+
+ api(resetUserDataApiName, args, httpMethod, data).then(json => {
this.$message.success({
content: `${this.$t('label.action.userdata.reset')} -
${this.$t('label.success')}`,
duration: 2