From 78bdde9e981ef55f277327bf0e783e215d5af9c4 Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Tue, 22 Aug 2023 11:07:16 +0200 Subject: [PATCH] AutoScaling: support Managed User Data (#7769) --- .../cloud/network/as/AutoScaleVmProfile.java | 4 + .../org/apache/cloudstack/api/BaseCmd.java | 17 ++ .../CreateAutoScaleVmProfileCmd.java | 15 + .../UpdateAutoScaleVmProfileCmd.java | 17 ++ .../api/command/user/vm/DeployVMCmd.java | 26 +- .../command/user/vm/ResetVMUserDataCmd.java | 16 +- .../api/command/user/vm/ScaleVMCmd.java | 15 +- .../api/command/user/vm/UpdateVMCmd.java | 14 +- .../api/command/user/vm/UpgradeVMCmd.java | 15 +- .../response/AutoScaleVmProfileResponse.java | 48 ++++ .../network/as/AutoScaleVmProfileVO.java | 24 ++ .../upgrade/dao/Upgrade41800to41810.java | 5 + .../META-INF/db/schema-41800to41810.sql | 4 + .../network/as/AutoScaleVmProfileVOTest.java | 31 +++ .../com/cloud/api/ApiAsyncJobDispatcher.java | 6 + .../java/com/cloud/api/ApiResponseHelper.java | 14 + .../main/java/com/cloud/api/ApiServer.java | 3 + .../network/as/AutoScaleManagerImpl.java | 53 +++- .../main/java/com/cloud/vm/UserVmManager.java | 4 + .../java/com/cloud/vm/UserVmManagerImpl.java | 6 +- .../com/cloud/api/ApiResponseHelperTest.java | 72 +++++ .../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 ++++++- .../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 convertDetailsToMap(Map details) { + Map detailsMap = new HashMap(); + if (MapUtils.isNotEmpty(details)) { + Collection parameterCollection = details.values(); + Iterator iter = parameterCollection.iterator(); + while (iter.hasNext()) { + HashMap value = (HashMap)iter.next(); + for (Map.Entry 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 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 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 getDetails() { - Map customparameterMap = new HashMap(); - if (details != null && details.size() != 0) { - Collection parameterCollection = details.values(); - Iterator iter = parameterCollection.iterator(); - while (iter.hasNext()) { - HashMap value = (HashMap)iter.next(); - for (Map.Entry entry: value.entrySet()) { - customparameterMap.put(entry.getKey(),entry.getValue()); - } - } - } + Map 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 getUserdataDetails() { - Map userdataDetailsMap = new HashMap(); - if (userdataDetails != null && userdataDetails.size() != 0) { - Collection parameterCollection = userdataDetails.values(); - Iterator iter = parameterCollection.iterator(); - while (iter.hasNext()) { - HashMap value = (HashMap)iter.next(); - for (Map.Entry 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 getUserdataDetails() { - Map userdataDetailsMap = new HashMap(); - if (userdataDetails != null && userdataDetails.size() != 0) { - Collection parameterCollection = userdataDetails.values(); - Iterator iter = parameterCollection.iterator(); - while (iter.hasNext()) { - HashMap value = (HashMap)iter.next(); - for (Map.Entry 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 getDetails() { - Map customparameterMap = new HashMap(); - if (details != null && details.size() != 0) { - Collection parameterCollection = details.values(); - Iterator iter = parameterCollection.iterator(); - while (iter.hasNext()) { - HashMap value = (HashMap)iter.next(); - for (String key : value.keySet()) { - customparameterMap.put(key, value.get(key)); - } - } - } + Map 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 getUserdataDetails() { - Map userdataDetailsMap = new HashMap(); - if (userdataDetails != null && userdataDetails.size() != 0) { - Collection parameterCollection = userdataDetails.values(); - Iterator iter = parameterCollection.iterator(); - while (iter.hasNext()) { - HashMap value = (HashMap)iter.next(); - for (Map.Entry 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 getDetails() { - Map customparameterMap = new HashMap(); - if (details != null && details.size() != 0) { - Collection parameterCollection = details.values(); - Iterator iter = parameterCollection.iterator(); - while (iter.hasNext()) { - HashMap value = (HashMap)iter.next(); - for (String key : value.keySet()) { - customparameterMap.put(key, value.get(key)); - } - } - } + Map 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) 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 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> 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 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> 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) }} +
+
+
+ +
+ {{ userdataid }} +
+
+
+
+
+ +
+ {{ userdataname }} +
+
+
+
+
+ +
+ {{ userdatadetails }} +
+
+
+
+
+ +
+ {{ userdatapolicy }} +
+
+
+
+
+ +
+ + +
+
@@ -76,6 +117,12 @@ {{ $t('label.edit.autoscale.vmprofile') }}
+
+ + + {{ $t('label.reset.userdata.on.autoscale.vm.group') }} + +
@@ -224,20 +271,26 @@ -
-
-
- -
- - -
-
{{ $t('label.cancel') }} {{ $t('label.ok') }}
+ + + + @@ -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 @@ @@ -760,13 +760,100 @@ @select-affinity-group-item="($event) => updateAffinityGroups($event)" @handle-search-filter="($event) => handleSearchFilter('affinityGroups', $event)"/> - + - - + +
+ + Userdata "{{ $t(this.template.userdataname) }}" is linked with template "{{ $t(this.template.name) }}" with override policy "{{ $t(this.template.userdatapolicy) }}" +

+
+ + Enter the values for the variables in userdata + + + + + + +
+


+
+ + {{ $t('label.userdata.do.override') }} + + + + {{ $t('label.userdata.do.append') }} + + + + + +
+
@@ -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