AutoScaling: support Managed User Data (#7769)

This commit is contained in:
Wei Zhou 2023-08-22 11:07:16 +02:00 committed by GitHub
parent 405ef82aef
commit 78bdde9e98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 785 additions and 128 deletions

View File

@ -35,6 +35,10 @@ public interface AutoScaleVmProfile extends ControlledEntity, InternalIdentity,
String getUserData();
Long getUserDataId();
String getUserDataDetails();
public String getUuid();
public Long getZoneId();

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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() {

View File

@ -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

View File

@ -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()));

View File

@ -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() {

View File

@ -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()));

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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");
}
}

View File

@ -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

View File

@ -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());
}
}

View File

@ -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);

View File

@ -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());

View File

@ -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;

View File

@ -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);
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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());
}
}

View File

@ -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);
}

View File

@ -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(

View File

@ -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",

View File

@ -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({

View File

@ -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 = []

View File

@ -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