Merge branch '4.22'

This commit is contained in:
Daan Hoogland 2026-02-05 16:07:46 +01:00
commit e929f2024a
23 changed files with 671 additions and 174 deletions

View File

@ -153,8 +153,8 @@ public abstract class BaseUpdateTemplateOrIsoCmd extends BaseCmd {
return (Map) (paramsCollection.toArray())[0];
}
public boolean isCleanupDetails(){
return cleanupDetails == null ? false : cleanupDetails.booleanValue();
public boolean isCleanupDetails() {
return cleanupDetails != null && cleanupDetails;
}
public CPU.CPUArch getCPUArch() {

View File

@ -188,7 +188,7 @@ public abstract class BaseDeployVMCmd extends BaseAsyncCreateCustomIdCmd impleme
@Parameter(name = ApiConstants.MAC_ADDRESS, type = CommandType.STRING, description = "the mac address for default vm's network")
private String macAddress;
@Parameter(name = ApiConstants.KEYBOARD, type = CommandType.STRING, description = "an optional keyboard device type for the virtual machine. valid value can be one of de,de-ch,es,fi,fr,fr-be,fr-ch,is,it,jp,nl-be,no,pt,uk,us")
@Parameter(name = ApiConstants.KEYBOARD, type = CommandType.STRING, description = "an optional keyboard device type for the virtual machine. valid value can be one of de,de-ch,es,es-latam,fi,fr,fr-be,fr-ch,is,it,jp,nl-be,no,pt,uk,us")
private String keyboard;
@Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, entityType = ProjectResponse.class, description = "Deploy vm for the project")

View File

@ -340,6 +340,10 @@ public class UserVmResponse extends BaseResponseWithTagInformation implements Co
@Param(description = "List of read-only Instance details as comma separated string.", since = "4.16.0")
private String readOnlyDetails;
@SerializedName("alloweddetails")
@Param(description = "List of allowed Vm details as comma separated string if VM instance settings are read from OVA.", since = "4.22.1")
private String allowedDetails;
@SerializedName(ApiConstants.SSH_KEYPAIRS)
@Param(description = "SSH key-pairs")
private String keyPairNames;
@ -1091,6 +1095,10 @@ public class UserVmResponse extends BaseResponseWithTagInformation implements Co
this.readOnlyDetails = readOnlyDetails;
}
public void setAllowedDetails(String allowedDetails) {
this.allowedDetails = allowedDetails;
}
public void setOsTypeId(String osTypeId) {
this.osTypeId = osTypeId;
}
@ -1115,6 +1123,10 @@ public class UserVmResponse extends BaseResponseWithTagInformation implements Co
return readOnlyDetails;
}
public String getAllowedDetails() {
return allowedDetails;
}
public Boolean getDynamicallyScalable() {
return isDynamicallyScalable;
}

View File

@ -457,7 +457,7 @@ public class VMTemplateDaoImpl extends GenericDaoBase<VMTemplateVO, Long> implem
if (detailsStr == null) {
return;
}
List<VMTemplateDetailVO> details = new ArrayList<VMTemplateDetailVO>();
List<VMTemplateDetailVO> details = new ArrayList<>();
for (String key : detailsStr.keySet()) {
VMTemplateDetailVO detail = new VMTemplateDetailVO(tmpl.getId(), key, detailsStr.get(key), true);
details.add(detail);
@ -479,7 +479,7 @@ public class VMTemplateDaoImpl extends GenericDaoBase<VMTemplateVO, Long> implem
}
if (tmplt.getDetails() != null) {
List<VMTemplateDetailVO> details = new ArrayList<VMTemplateDetailVO>();
List<VMTemplateDetailVO> details = new ArrayList<>();
for (String key : tmplt.getDetails().keySet()) {
details.add(new VMTemplateDetailVO(tmplt.getId(), key, tmplt.getDetails().get(key), true));
}

View File

@ -65,20 +65,20 @@
<!-- Plugins versions -->
<cs.antrun-plugin.version>1.8</cs.antrun-plugin.version>
<cs.builder-helper-plugin.version>3.0.0</cs.builder-helper-plugin.version>
<cs.checkstyle-plugin.version>3.1.0</cs.checkstyle-plugin.version>
<cs.checkstyle-plugin.version>3.6.0</cs.checkstyle-plugin.version>
<cs.jacoco-plugin.version>0.8.11</cs.jacoco-plugin.version>
<cs.compiler-plugin.version>3.8.1</cs.compiler-plugin.version>
<cs.dependency-plugin.version>3.1.1</cs.dependency-plugin.version>
<cs.dependency-plugin.version>3.9.0</cs.dependency-plugin.version>
<cs.failsafe-plugin.version>2.22.2</cs.failsafe-plugin.version>
<cs.spotbugs.version>3.1.12</cs.spotbugs.version>
<cs.spotbugs-maven-plugin.version>3.1.12.2</cs.spotbugs-maven-plugin.version>
<cs.jar-plugin.version>3.2.0</cs.jar-plugin.version>
<cs.pmd-plugin.version>3.12.0</cs.pmd-plugin.version>
<cs.pmd-plugin.version>3.28.0</cs.pmd-plugin.version>
<cs.project-info-plugin.version>3.0.0</cs.project-info-plugin.version>
<cs.owasp.dependency-checker-plugin.version>7.4.4</cs.owasp.dependency-checker-plugin.version>
<cs.release-plugin.version>2.5.3</cs.release-plugin.version>
<cs.resources-plugin.version>3.1.0</cs.resources-plugin.version>
<cs.site-plugin.version>3.8.2</cs.site-plugin.version>
<cs.site-plugin.version>3.21.0</cs.site-plugin.version>
<cs.surefire-plugin.version>2.22.2</cs.surefire-plugin.version>
<cs.clover-maven-plugin.version>4.4.1</cs.clover-maven-plugin.version>
<cs.exec-maven-plugin.version>3.2.0</cs.exec-maven-plugin.version>

View File

@ -5408,7 +5408,7 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
options.put(ApiConstants.BootType.UEFI.toString(), Arrays.asList(ApiConstants.BootMode.LEGACY.toString(),
ApiConstants.BootMode.SECURE.toString()));
options.put(VmDetailConstants.KEYBOARD, Arrays.asList("uk", "us", "jp", "fr"));
options.put(VmDetailConstants.KEYBOARD, Arrays.asList("uk", "us", "jp", "fr", "es-latam"));
options.put(VmDetailConstants.CPU_CORE_PER_SOCKET, Collections.emptyList());
options.put(VmDetailConstants.ROOT_DISK_SIZE, Collections.emptyList());

View File

@ -69,9 +69,11 @@ import com.cloud.service.ServiceOfferingDetailsVO;
import com.cloud.storage.DiskOfferingVO;
import com.cloud.storage.GuestOS;
import com.cloud.storage.Storage.TemplateType;
import com.cloud.storage.VMTemplateVO;
import com.cloud.storage.VnfTemplateDetailVO;
import com.cloud.storage.VnfTemplateNicVO;
import com.cloud.storage.Volume;
import com.cloud.storage.dao.VMTemplateDao;
import com.cloud.storage.dao.VnfTemplateDetailsDao;
import com.cloud.storage.dao.VnfTemplateNicDao;
import com.cloud.user.Account;
@ -124,6 +126,8 @@ public class UserVmJoinDaoImpl extends GenericDaoBaseWithTagInformation<UserVmJo
private ServiceOfferingDao serviceOfferingDao;
@Inject
private VgpuProfileDao vgpuProfileDao;
@Inject
VMTemplateDao vmTemplateDao;
private final SearchBuilder<UserVmJoinVO> VmDetailSearch;
private final SearchBuilder<UserVmJoinVO> activeVmByIsoSearch;
@ -465,6 +469,10 @@ public class UserVmJoinDaoImpl extends GenericDaoBaseWithTagInformation<UserVmJo
if (caller.getType() != Account.Type.ADMIN) {
userVmResponse.setReadOnlyDetails(QueryService.UserVMReadOnlyDetails.value());
}
VMTemplateVO template = vmTemplateDao.findByIdIncludingRemoved(userVm.getTemplateId());
if (template != null && template.isDeployAsIs() && UserVmManager.VmwareAdditionalDetailsFromOvaEnabled.valueIn(userVm.getDataCenterId())) {
userVmResponse.setAllowedDetails(UserVmManager.VmwareAllowedAdditionalDetailsFromOva.valueIn(userVm.getDataCenterId()));
}
}
userVmResponse.setObjectName(objectName);

View File

@ -2256,7 +2256,7 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager,
templateTag == null &&
forCks == null &&
arch == null &&
(! cleanupDetails && details == null) //update details in every case except this one
(!cleanupDetails && details == null) //update details in every case except this one
);
if (!updateNeeded) {
return template;
@ -2360,8 +2360,7 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager,
if (cleanupDetails) {
template.setDetails(null);
_tmpltDetailsDao.removeDetails(id);
}
else if (details != null && !details.isEmpty()) {
} else if (details != null && !details.isEmpty()) {
template.setDetails(details);
_tmpltDao.saveDetails(template);
}

View File

@ -99,6 +99,15 @@ public interface UserVmManager extends UserVmService {
ConfigKey.Scope.Account);
ConfigKey<Boolean> VmwareAdditionalDetailsFromOvaEnabled = new ConfigKey<Boolean>("Advanced", Boolean.class,
"vmware.additional.details.from.ova.enabled", "false",
"If true, allow users to add additional VM settings if VM instance settings are read from OVA.", true, ConfigKey.Scope.Zone);
ConfigKey<String> VmwareAllowedAdditionalDetailsFromOva = new ConfigKey<>(String.class,
"vmware.allowed.additional.details.from.ova", "Advanced", "",
"Comma separated list of allowed additional VM settings if VM instance settings are read from OVA.",
true, ConfigKey.Scope.Zone, null, null, null, null, null, ConfigKey.Kind.CSV, null);
static final int MAX_USER_DATA_LENGTH_BYTES = 2048;
public static final String CKS_NODE = "cksnode";

View File

@ -150,7 +150,6 @@ import org.apache.cloudstack.utils.bytescale.ByteScaleUtils;
import org.apache.cloudstack.utils.security.ParserUtils;
import org.apache.cloudstack.vm.UnmanagedVMsManager;
import org.apache.cloudstack.vm.lease.VMLeaseManager;
import org.apache.cloudstack.vm.schedule.VMScheduleManager;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.math.NumberUtils;
@ -315,7 +314,6 @@ import com.cloud.resource.ResourceState;
import com.cloud.resourcelimit.CheckedReservation;
import com.cloud.server.ManagementService;
import com.cloud.server.ResourceTag;
import com.cloud.server.StatsCollector;
import com.cloud.service.ServiceOfferingVO;
import com.cloud.service.dao.ServiceOfferingDao;
import com.cloud.service.dao.ServiceOfferingDetailsDao;
@ -615,28 +613,28 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
SnapshotPolicyDao snapshotPolicyDao;
@Inject
BackupScheduleDao backupScheduleDao;
@Inject
private StatsCollector statsCollector;
@Inject
private UserDataDao userDataDao;
@Inject
protected SnapshotHelper snapshotHelper;
@Inject
private AutoScaleManager autoScaleManager;
@Inject
VMScheduleManager vmScheduleManager;
@Inject
NsxProviderDao nsxProviderDao;
@Inject
NetworkService networkService;
@Inject
SnapshotDataFactory snapshotDataFactory;
@Inject
private OrchestrationService _orchSrvc;
@Inject
private VolumeOrchestrationService volumeMgr;
@Inject
private ManagementService _mgr;
@Inject
private UserDataManager userDataManager;
@Inject
VnfTemplateManager vnfTemplateManager;
private ScheduledExecutorService _executor = null;
private ScheduledExecutorService _vmIpFetchExecutor = null;
@ -646,8 +644,6 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
private int capacityReleaseInterval;
private ExecutorService _vmIpFetchThreadExecutor;
private List<KubernetesServiceHelper> kubernetesServiceHelpers;
private String _instance;
private boolean _instanceNameFlag;
private int _scaleRetry;
@ -655,10 +651,6 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
protected static long ROOT_DEVICE_ID = 0;
private static final int MAX_HTTP_GET_LENGTH = 2 * MAX_USER_DATA_LENGTH_BYTES;
private static final int NUM_OF_2K_BLOCKS = 512;
private static final int MAX_HTTP_POST_LENGTH = NUM_OF_2K_BLOCKS * MAX_USER_DATA_LENGTH_BYTES;
public List<KubernetesServiceHelper> getKubernetesServiceHelpers() {
return kubernetesServiceHelpers;
}
@ -667,34 +659,19 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
this.kubernetesServiceHelpers = kubernetesServiceHelpers;
}
@Inject
private OrchestrationService _orchSrvc;
@Inject
private VolumeOrchestrationService volumeMgr;
@Inject
private ManagementService _mgr;
@Inject
private UserDataManager userDataManager;
@Inject
VnfTemplateManager vnfTemplateManager;
private static final ConfigKey<Integer> VmIpFetchWaitInterval = new ConfigKey<Integer>("Advanced", Integer.class, "externaldhcp.vmip.retrieval.interval", "180",
private static final ConfigKey<Integer> VmIpFetchWaitInterval = new ConfigKey<>("Advanced", Integer.class, "externaldhcp.vmip.retrieval.interval", "180",
"Wait Interval (in seconds) for shared network vm dhcp ip addr fetch for next iteration ", true);
private static final ConfigKey<Integer> VmIpFetchTrialMax = new ConfigKey<Integer>("Advanced", Integer.class, "externaldhcp.vmip.max.retry", "10",
private static final ConfigKey<Integer> VmIpFetchTrialMax = new ConfigKey<>("Advanced", Integer.class, "externaldhcp.vmip.max.retry", "10",
"The max number of retrieval times for shared network vm dhcp ip fetch, in case of failures", true);
private static final ConfigKey<Integer> VmIpFetchThreadPoolMax = new ConfigKey<Integer>("Advanced", Integer.class, "externaldhcp.vmipFetch.threadPool.max", "10",
private static final ConfigKey<Integer> VmIpFetchThreadPoolMax = new ConfigKey<>("Advanced", Integer.class, "externaldhcp.vmipFetch.threadPool.max", "10",
"number of threads for fetching vms ip address", true);
private static final ConfigKey<Integer> VmIpFetchTaskWorkers = new ConfigKey<Integer>("Advanced", Integer.class, "externaldhcp.vmipfetchtask.workers", "10",
private static final ConfigKey<Integer> VmIpFetchTaskWorkers = new ConfigKey<>("Advanced", Integer.class, "externaldhcp.vmipfetchtask.workers", "10",
"number of worker threads for vm ip fetch task ", true);
private static final ConfigKey<Boolean> AllowDeployVmIfGivenHostFails = new ConfigKey<Boolean>("Advanced", Boolean.class, "allow.deploy.vm.if.deploy.on.given.host.fails", "false",
private static final ConfigKey<Boolean> AllowDeployVmIfGivenHostFails = new ConfigKey<>("Advanced", Boolean.class, "allow.deploy.vm.if.deploy.on.given.host.fails", "false",
"allow vm to deploy on different host if vm fails to deploy on the given host ", true);
private static final ConfigKey<String> KvmAdditionalConfigAllowList = new ConfigKey<>(String.class,
@ -706,7 +683,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
private static final ConfigKey<String> VmwareAdditionalConfigAllowList = new ConfigKey<>(String.class,
"allow.additional.vm.configuration.list.vmware", "Advanced", "", "Comma separated list of allowed additional configuration options.", true, ConfigKey.Scope.Global, null, null, EnableAdditionalVmConfig.key(), null, null, ConfigKey.Kind.CSV, null);
private static final ConfigKey<Boolean> VmDestroyForcestop = new ConfigKey<Boolean>("Advanced", Boolean.class, "vm.destroy.forcestop", "false",
private static final ConfigKey<Boolean> VmDestroyForcestop = new ConfigKey<>("Advanced", Boolean.class, "vm.destroy.forcestop", "false",
"On destroy, force-stop takes this value ", true);
@Override
@ -735,7 +712,6 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
long vmId;
int retrievalCount = VmIpFetchTrialMax.value();
public VmAndCountDetails() {
}
@ -1192,7 +1168,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
if (dc.getNetworkType() == DataCenter.NetworkType.Advanced) {
//List all networks of vm
List<Long> vmNetworks = _vmNetworkMapDao.getNetworks(vmId);
List<DomainRouterVO> routers = new ArrayList<DomainRouterVO>();
List<DomainRouterVO> routers = new ArrayList<>();
//List the stopped routers
for(long vmNetworkId : vmNetworks) {
List<DomainRouterVO> router = _routerDao.listStopped(vmNetworkId);
@ -2903,11 +2879,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
UserVmVO vmInstance = _vmDao.findById(cmd.getId());
VMTemplateVO template = _templateDao.findById(vmInstance.getTemplateId());
if (MapUtils.isNotEmpty(details) || cmd.isCleanupDetails()) {
if (template != null && template.isDeployAsIs()) {
throw new CloudRuntimeException("Detail settings are read from OVA, it cannot be changed by API call.");
}
}
UserVmVO userVm = _vmDao.findById(cmd.getId());
if (userVm != null && UserVmManager.SHAREDFSVM.equals(userVm.getUserVmType())) {
throw new InvalidParameterValueException("Operation not supported on Shared FileSystem Instance");
@ -2937,6 +2909,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
.collect(Collectors.toList());
List<VMInstanceDetailVO> existingDetails = vmInstanceDetailsDao.listDetails(id);
if (cleanupDetails) {
if (template != null && template.isDeployAsIs()) {
throw new InvalidParameterValueException("Detail settings are read from OVA, it cannot be cleaned up by API call.");
}
if (caller != null && caller.getType() == Account.Type.ADMIN) {
for (final VMInstanceDetailVO detail : existingDetails) {
if (detail != null && detail.isDisplay() && !isExtraConfig(detail.getName())) {
@ -2965,6 +2940,23 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
throw new InvalidParameterValueException("'extraconfig' should not be included in details as key");
}
if (template != null && template.isDeployAsIs()) {
final List<String> vmwareAllowedDetailsFromOva = VmwareAdditionalDetailsFromOvaEnabled.valueIn(vmInstance.getDataCenterId()) ?
Stream.of(VmwareAllowedAdditionalDetailsFromOva.valueIn(vmInstance.getDataCenterId()).split(","))
.map(String::trim)
.collect(Collectors.toList()) : List.of();
for (String detailKey : details.keySet()) {
if (vmwareAllowedDetailsFromOva.contains(detailKey)) {
continue;
}
VMInstanceDetailVO detailVO = existingDetails.stream().filter(d -> Objects.equals(d.getName(), detailKey)).findFirst().orElse(null);
if (detailVO != null && ObjectUtils.allNotNull(detailVO.getValue(), details.get(detailKey)) && detailVO.getValue().equals(details.get(detailKey))) {
continue;
}
throw new InvalidParameterValueException("Detail settings are read from OVA, it cannot be changed by API call.");
}
}
details.entrySet().removeIf(detail -> isExtraConfig(detail.getKey()));
if (caller != null && caller.getType() != Account.Type.ADMIN) {
@ -3214,7 +3206,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
// Verify that vm's hostName is unique
List<NetworkVO> vmNtwks = new ArrayList<NetworkVO>(nics.size());
List<NetworkVO> vmNtwks = new ArrayList<>(nics.size());
for (Nic nic : nics) {
vmNtwks.add(_networkDao.findById(nic.getNetworkId()));
}
@ -3784,7 +3776,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
StorageUnavailableException, ResourceAllocationException {
Account caller = CallContext.current().getCallingAccount();
List<NetworkVO> networkList = new ArrayList<NetworkVO>();
List<NetworkVO> networkList = new ArrayList<>();
// Verify that caller can perform actions in behalf of vm owner
_accountMgr.checkAccess(caller, null, true, owner);
@ -3810,7 +3802,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
//add the default securityGroup only if no security group is specified
if (securityGroupIdList == null || securityGroupIdList.isEmpty()) {
if (securityGroupIdList == null) {
securityGroupIdList = new ArrayList<Long>();
securityGroupIdList = new ArrayList<>();
}
SecurityGroup defaultGroup = _securityGroupMgr.getDefaultSecurityGroup(owner.getId());
if (defaultGroup != null) {
@ -3842,7 +3834,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
Map<Long, DiskOffering> dataDiskTemplateToDiskOfferingMap, Map<String, String> userVmOVFProperties, boolean dynamicScalingEnabled, Long overrideDiskOfferingId, String vmType, Volume volume, Snapshot snapshot) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException {
Account caller = CallContext.current().getCallingAccount();
List<NetworkVO> networkList = new ArrayList<NetworkVO>();
List<NetworkVO> networkList = new ArrayList<>();
boolean isSecurityGroupEnabledNetworkUsed = false;
boolean isVmWare = (template.getHypervisorType() == HypervisorType.VMware || (hypervisor != null && hypervisor == HypervisorType.VMware));
@ -3920,7 +3912,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
//add the default securityGroup only if no security group is specified
if (securityGroupIdList == null || securityGroupIdList.isEmpty()) {
if (securityGroupIdList == null) {
securityGroupIdList = new ArrayList<Long>();
securityGroupIdList = new ArrayList<>();
}
SecurityGroup defaultGroup = _securityGroupMgr.getDefaultSecurityGroup(owner.getId());
@ -3955,7 +3947,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
StorageUnavailableException, ResourceAllocationException {
Account caller = CallContext.current().getCallingAccount();
List<NetworkVO> networkList = new ArrayList<NetworkVO>();
List<NetworkVO> networkList = new ArrayList<>();
// Verify that caller can perform actions in behalf of vm owner
_accountMgr.checkAccess(caller, null, true, owner);
@ -4910,10 +4902,10 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
logger.debug("Allocating in the DB for vm");
DataCenterDeployment plan = new DataCenterDeployment(zone.getId());
List<String> computeTags = new ArrayList<String>();
List<String> computeTags = new ArrayList<>();
computeTags.add(offering.getHostTag());
List<String> rootDiskTags = new ArrayList<String>();
List<String> rootDiskTags = new ArrayList<>();
DiskOfferingVO rootDiskOfferingVO = _diskOfferingDao.findById(rootDiskOfferingId);
rootDiskTags.add(rootDiskOfferingVO.getTags());
@ -5127,7 +5119,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
VirtualMachine.class.getName(), vm.getUuid(), isDisplay);
}
else {
Map<String, String> customParameters = new HashMap<String, String>();
Map<String, String> customParameters = new HashMap<>();
customParameters.put(UsageEventVO.DynamicParameters.cpuNumber.name(), serviceOffering.getCpu().toString());
customParameters.put(UsageEventVO.DynamicParameters.cpuSpeed.name(), serviceOffering.getSpeed().toString());
customParameters.put(UsageEventVO.DynamicParameters.memory.name(), serviceOffering.getRamSize().toString());
@ -5144,7 +5136,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
}
logger.debug("Collect vm network statistics from host before stopping Vm");
long hostId = userVm.getHostId();
List<String> vmNames = new ArrayList<String>();
List<String> vmNames = new ArrayList<>();
vmNames.add(userVm.getInstanceName());
final HostVO host = _hostDao.findById(hostId);
Account account = _accountMgr.getAccount(userVm.getAccountId());
@ -5736,7 +5728,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
SecurityGroup defaultSecurityGroup = _securityGroupMgr.getDefaultSecurityGroup(vm.getAccountId());
if (defaultSecurityGroup != null) {
List<Long> groupList = new ArrayList<Long>();
List<Long> groupList = new ArrayList<>();
groupList.add(defaultSecurityGroup.getId());
_securityGroupMgr.addInstanceToGroups(vm, groupList);
}
@ -6025,7 +6017,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
return;
}
long hostId = userVm.getHostId();
List<String> vmNames = new ArrayList<String>();
List<String> vmNames = new ArrayList<>();
vmNames.add(userVm.getInstanceName());
final HostVO host = _hostDao.findById(hostId);
Account account = _accountMgr.getAccount(userVm.getAccountId());
@ -6433,7 +6425,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
String userData = null;
Long userDataId = null;
String userDataDetails = null;
List<String> sshKeyPairNames = new ArrayList<String>();
List<String> sshKeyPairNames = new ArrayList<>();
if (cmd instanceof CreateVMFromBackupCmd) {
if (cmd.getUserData() != null) {
throw new InvalidParameterValueException("User data not supported for instance created from backup");
@ -6890,7 +6882,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
//transform group names to ids here
if (cmd.getSecurityGroupNameList() != null) {
List<Long> securityGroupIds = new ArrayList<Long>();
List<Long> securityGroupIds = new ArrayList<>();
for (String groupName : cmd.getSecurityGroupNameList()) {
SecurityGroup sg = _securityGroupMgr.getSecurityGroup(groupName, cmd.getEntityOwnerId());
if (sg == null) {
@ -7675,7 +7667,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
}
private Map<Long, Long> getVolumePoolMappingForMigrateVmWithStorage(VMInstanceVO vm, Map<String, String> volumeToPool) {
Map<Long, Long> volToPoolObjectMap = new HashMap<Long, Long>();
Map<Long, Long> volToPoolObjectMap = new HashMap<>();
List<VolumeVO> vmVolumes = getVmVolumesForMigrateVmWithStorage(vm);
@ -9352,7 +9344,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
return new ConfigKey<?>[] {EnableDynamicallyScaleVm, AllowDiskOfferingChangeDuringScaleVm, AllowUserExpungeRecoverVm, VmIpFetchWaitInterval, VmIpFetchTrialMax,
VmIpFetchThreadPoolMax, VmIpFetchTaskWorkers, AllowDeployVmIfGivenHostFails, EnableAdditionalVmConfig, DisplayVMOVFProperties,
KvmAdditionalConfigAllowList, XenServerAdditionalConfigAllowList, VmwareAdditionalConfigAllowList, DestroyRootVolumeOnVmDestruction,
EnforceStrictResourceLimitHostTagCheck, StrictHostTags, AllowUserForceStopVm, VmDistinctHostNameScope};
EnforceStrictResourceLimitHostTagCheck, StrictHostTags, AllowUserForceStopVm, VmDistinctHostNameScope,
VmwareAdditionalDetailsFromOvaEnabled, VmwareAllowedAdditionalDetailsFromOva};
}
@Override
@ -9712,7 +9705,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
Map<Long, IpAddresses> ipToNetworkMap = cmd.getIpToNetworkMap();
if (networkIds == null && ipToNetworkMap == null) {
networkIds = new ArrayList<Long>();
networkIds = new ArrayList<>();
ipToNetworkMap = backupManager.getIpToNetworkMapFromBackup(backup, cmd.getPreserveIp(), networkIds);
}

View File

@ -23,6 +23,7 @@ import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
@ -182,6 +183,11 @@ public class VMSnapshotManagerImpl extends MutualExclusiveIdsManagerBase impleme
Long.class, "vm.job.check.interval", "3000",
"Interval in milliseconds to check if the job is complete", false);
private static final Set<String> VM_SNAPSHOT_CUSTOM_SERVICE_OFFERING_DETAILS = Set.of(
VmDetailConstants.CPU_NUMBER.toLowerCase(),
VmDetailConstants.CPU_SPEED.toLowerCase(),
VmDetailConstants.MEMORY.toLowerCase());
@Override
public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
_name = name;
@ -472,7 +478,8 @@ public class VMSnapshotManagerImpl extends MutualExclusiveIdsManagerBase impleme
}
/**
* Add entries on vm_snapshot_details if service offering is dynamic. This will allow setting details when revert to vm snapshot
* Add entries about cpu, cpu_speed and memory in vm_snapshot_details if service offering is dynamic.
* This will allow setting details when revert to vm snapshot.
* @param vmId vm id
* @param serviceOfferingId service offering id
* @param vmSnapshotId vm snapshot id
@ -483,7 +490,7 @@ public class VMSnapshotManagerImpl extends MutualExclusiveIdsManagerBase impleme
List<VMInstanceDetailVO> vmDetails = _vmInstanceDetailsDao.listDetails(vmId);
List<VMSnapshotDetailsVO> vmSnapshotDetails = new ArrayList<VMSnapshotDetailsVO>();
for (VMInstanceDetailVO detail : vmDetails) {
if(detail.getName().equalsIgnoreCase(VmDetailConstants.CPU_NUMBER) || detail.getName().equalsIgnoreCase(VmDetailConstants.CPU_SPEED) || detail.getName().equalsIgnoreCase(VmDetailConstants.MEMORY)) {
if (VM_SNAPSHOT_CUSTOM_SERVICE_OFFERING_DETAILS.contains(detail.getName().toLowerCase())) {
vmSnapshotDetails.add(new VMSnapshotDetailsVO(vmSnapshotId, detail.getName(), detail.getValue(), detail.isDisplay()));
}
}
@ -936,7 +943,7 @@ public class VMSnapshotManagerImpl extends MutualExclusiveIdsManagerBase impleme
Transaction.execute(new TransactionCallbackWithExceptionNoReturn<CloudRuntimeException>() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) throws CloudRuntimeException {
revertUserVmDetailsFromVmSnapshot(userVm, vmSnapshotVo);
revertCustomServiceOfferingDetailsFromVmSnapshot(userVm, vmSnapshotVo);
updateUserVmServiceOffering(userVm, vmSnapshotVo);
}
});
@ -948,19 +955,19 @@ public class VMSnapshotManagerImpl extends MutualExclusiveIdsManagerBase impleme
}
/**
* Update or add user vm details from vm snapshot for vms with custom service offerings
* Update or add user vm details (cpu, cpu_speed and memory) from vm snapshot for vms with custom service offerings
* @param userVm user vm
* @param vmSnapshotVo vm snapshot
*/
protected void revertUserVmDetailsFromVmSnapshot(UserVmVO userVm, VMSnapshotVO vmSnapshotVo) {
protected void revertCustomServiceOfferingDetailsFromVmSnapshot(UserVmVO userVm, VMSnapshotVO vmSnapshotVo) {
ServiceOfferingVO serviceOfferingVO = _serviceOfferingDao.findById(vmSnapshotVo.getServiceOfferingId());
if (serviceOfferingVO.isDynamic()) {
List<VMSnapshotDetailsVO> vmSnapshotDetails = _vmSnapshotDetailsDao.listDetails(vmSnapshotVo.getId());
List<VMInstanceDetailVO> userVmDetails = new ArrayList<VMInstanceDetailVO>();
for (VMSnapshotDetailsVO detail : vmSnapshotDetails) {
userVmDetails.add(new VMInstanceDetailVO(userVm.getId(), detail.getName(), detail.getValue(), detail.isDisplay()));
if (VM_SNAPSHOT_CUSTOM_SERVICE_OFFERING_DETAILS.contains(detail.getName().toLowerCase())) {
_vmInstanceDetailsDao.addDetail(userVm.getId(), detail.getName(), detail.getValue(), detail.isDisplay());
}
}
_vmInstanceDetailsDao.saveDetails(userVmDetails);
}
}

View File

@ -22,6 +22,7 @@ import static org.mockito.MockitoAnnotations.openMocks;
import java.util.Arrays;
import java.util.EnumSet;
import com.cloud.storage.dao.VMTemplateDao;
import org.apache.cloudstack.annotation.dao.AnnotationDao;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ResponseObject;
@ -78,6 +79,9 @@ public class UserVmJoinDaoImplTest extends GenericDaoBaseWithTagInformationBaseT
@Mock
private VnfTemplateDetailsDao vnfTemplateDetailsDao;
@Mock
private VMTemplateDao vmTemplateDao;
private UserVmJoinVO userVm = new UserVmJoinVO();
private UserVmResponse userVmResponse = new UserVmResponse();

View File

@ -52,6 +52,7 @@ import com.cloud.vm.UserVmManager;
import com.cloud.vm.UserVmVO;
import com.cloud.vm.VirtualMachine.State;
import com.cloud.vm.VirtualMachineManager;
import com.cloud.vm.VmDetailConstants;
import com.cloud.vm.dao.UserVmDao;
import com.cloud.vm.dao.VMInstanceDetailsDao;
import com.cloud.vm.dao.VMInstanceDao;
@ -68,7 +69,6 @@ import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
@ -80,13 +80,18 @@ import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ -228,13 +233,13 @@ public class VMSnapshotManagerTest {
when(_serviceOfferingDao.findById(SERVICE_OFFERING_ID)).thenReturn(serviceOffering);
for (ResourceDetail detail : Arrays.asList(userVmDetailCpuNumber, vmSnapshotDetailCpuNumber)) {
when(detail.getName()).thenReturn("cpuNumber");
when(detail.getName()).thenReturn(VmDetailConstants.CPU_NUMBER);
when(detail.getValue()).thenReturn("2");
when(detail.isDisplay()).thenReturn(true);
}
for (ResourceDetail detail : Arrays.asList(userVmDetailMemory, vmSnapshotDetailMemory)) {
when(detail.getName()).thenReturn("memory");
when(detail.getName()).thenReturn(VmDetailConstants.MEMORY);
when(detail.getValue()).thenReturn("2048");
when(detail.isDisplay()).thenReturn(true);
}
@ -363,12 +368,12 @@ public class VMSnapshotManagerTest {
@Test
public void testUpdateUserVmServiceOfferingDifferentServiceOffering() throws ConcurrentOperationException, ResourceUnavailableException, ManagementServerException, VirtualMachineMigrationException {
when(userVm.getServiceOfferingId()).thenReturn(SERVICE_OFFERING_DIFFERENT_ID);
when(_userVmManager.upgradeVirtualMachine(ArgumentMatchers.eq(TEST_VM_ID), ArgumentMatchers.eq(SERVICE_OFFERING_ID), mapDetailsCaptor.capture())).thenReturn(true);
when(_userVmManager.upgradeVirtualMachine(eq(TEST_VM_ID), eq(SERVICE_OFFERING_ID), mapDetailsCaptor.capture())).thenReturn(true);
_vmSnapshotMgr.updateUserVmServiceOffering(userVm, vmSnapshotVO);
verify(_vmSnapshotMgr).changeUserVmServiceOffering(userVm, vmSnapshotVO);
verify(_vmSnapshotMgr).getVmMapDetails(userVm);
verify(_vmSnapshotMgr).upgradeUserVmServiceOffering(ArgumentMatchers.eq(userVm), ArgumentMatchers.eq(SERVICE_OFFERING_ID), mapDetailsCaptor.capture());
verify(_vmSnapshotMgr).upgradeUserVmServiceOffering(eq(userVm), eq(SERVICE_OFFERING_ID), mapDetailsCaptor.capture());
}
@Test
@ -383,18 +388,18 @@ public class VMSnapshotManagerTest {
@Test
public void testChangeUserVmServiceOffering() throws ConcurrentOperationException, ResourceUnavailableException, ManagementServerException, VirtualMachineMigrationException {
when(_userVmManager.upgradeVirtualMachine(ArgumentMatchers.eq(TEST_VM_ID), ArgumentMatchers.eq(SERVICE_OFFERING_ID), mapDetailsCaptor.capture())).thenReturn(true);
when(_userVmManager.upgradeVirtualMachine(eq(TEST_VM_ID), eq(SERVICE_OFFERING_ID), mapDetailsCaptor.capture())).thenReturn(true);
_vmSnapshotMgr.changeUserVmServiceOffering(userVm, vmSnapshotVO);
verify(_vmSnapshotMgr).getVmMapDetails(userVm);
verify(_vmSnapshotMgr).upgradeUserVmServiceOffering(ArgumentMatchers.eq(userVm), ArgumentMatchers.eq(SERVICE_OFFERING_ID), mapDetailsCaptor.capture());
verify(_vmSnapshotMgr).upgradeUserVmServiceOffering(eq(userVm), eq(SERVICE_OFFERING_ID), mapDetailsCaptor.capture());
}
@Test(expected=CloudRuntimeException.class)
public void testChangeUserVmServiceOfferingFailOnUpgradeVMServiceOffering() throws ConcurrentOperationException, ResourceUnavailableException, ManagementServerException, VirtualMachineMigrationException {
when(_userVmManager.upgradeVirtualMachine(ArgumentMatchers.eq(TEST_VM_ID), ArgumentMatchers.eq(SERVICE_OFFERING_ID), mapDetailsCaptor.capture())).thenReturn(false);
when(_userVmManager.upgradeVirtualMachine(eq(TEST_VM_ID), eq(SERVICE_OFFERING_ID), mapDetailsCaptor.capture())).thenReturn(false);
_vmSnapshotMgr.changeUserVmServiceOffering(userVm, vmSnapshotVO);
verify(_vmSnapshotMgr).getVmMapDetails(userVm);
verify(_vmSnapshotMgr).upgradeUserVmServiceOffering(ArgumentMatchers.eq(userVm), ArgumentMatchers.eq(SERVICE_OFFERING_ID), mapDetailsCaptor.capture());
verify(_vmSnapshotMgr).upgradeUserVmServiceOffering(eq(userVm), eq(SERVICE_OFFERING_ID), mapDetailsCaptor.capture());
}
@Test
@ -411,16 +416,27 @@ public class VMSnapshotManagerTest {
@Test
public void testRevertUserVmDetailsFromVmSnapshotNotDynamicServiceOffering() {
_vmSnapshotMgr.revertUserVmDetailsFromVmSnapshot(vmMock, vmSnapshotVO);
_vmSnapshotMgr.revertCustomServiceOfferingDetailsFromVmSnapshot(vmMock, vmSnapshotVO);
verify(_vmSnapshotDetailsDao, never()).listDetails(anyLong());
}
@Test
public void testRevertUserVmDetailsFromVmSnapshotDynamicServiceOffering() {
when(serviceOffering.isDynamic()).thenReturn(true);
_vmSnapshotMgr.revertUserVmDetailsFromVmSnapshot(vmMock, vmSnapshotVO);
verify(_vmSnapshotDetailsDao).listDetails(VM_SNAPSHOT_ID);
verify(_vmInstanceDetailsDao).saveDetails(listUserVmDetailsCaptor.capture());
}
VMSnapshotDetailsVO uefiSnapshotDetail = new VMSnapshotDetailsVO(VM_SNAPSHOT_ID, "UEFI", "SECURE", true);
List<VMSnapshotDetailsVO> snapshotDetailsWithUefi = Arrays.asList(
vmSnapshotDetailCpuNumber, vmSnapshotDetailMemory, uefiSnapshotDetail);
when(_vmSnapshotDetailsDao.listDetails(VM_SNAPSHOT_ID)).thenReturn(snapshotDetailsWithUefi);
_vmSnapshotMgr.revertCustomServiceOfferingDetailsFromVmSnapshot(vmMock, vmSnapshotVO);
verify(_vmSnapshotDetailsDao).listDetails(VM_SNAPSHOT_ID);
verify(_vmInstanceDetailsDao, never()).saveDetails(any());
ArgumentCaptor<String> detailNameCaptor = ArgumentCaptor.forClass(String.class);
verify(_vmInstanceDetailsDao, times(2)).addDetail(eq(TEST_VM_ID), detailNameCaptor.capture(), anyString(), anyBoolean());
List<String> appliedNames = detailNameCaptor.getAllValues();
assertTrue(appliedNames.contains(VmDetailConstants.CPU_NUMBER));
assertTrue(appliedNames.contains(VmDetailConstants.MEMORY));
assertFalse("UEFI must not be applied from snapshot so that existing UEFI setting is preserved", appliedNames.contains("UEFI"));
}
}

View File

@ -39,6 +39,7 @@ import ZRLEDecoder from "./decoders/zrle.js";
import JPEGDecoder from "./decoders/jpeg.js";
import H264Decoder from "./decoders/h264.js";
import SCANCODES_JP from "../keymaps/keymap-ja-atset1.js"
import SCANCODES_ES_LATAM from "../keymaps/keymap-es-latam-atset1.js"
// How many seconds to wait for a disconnect to finish
const DISCONNECT_TIMEOUT = 3;
@ -127,6 +128,8 @@ export default class RFB extends EventTargetMixin {
this._scancodes = {};
if (this._language === "jp") {
this._scancodes = SCANCODES_JP;
} else if (this._language === "es-latam") {
this._scancodes = SCANCODES_ES_LATAM;
}
// Internal state
@ -197,6 +200,7 @@ export default class RFB extends EventTargetMixin {
// Keys
this._shiftPressed = false;
this._shiftKey = KeyTable.XK_Shift_L;
this._altgrPressed = false;
// Mouse state
this._mousePos = {};
@ -531,6 +535,10 @@ export default class RFB extends EventTargetMixin {
this._shiftKey = down ? keysym : KeyTable.XK_Shift_L;
}
if (keysym === KeyTable.XK_Alt_R) {
this._altgrPressed = down;
}
if (this._qemuExtKeyEventSupported && scancode) {
// 0 is NoSymbol
keysym = keysym || 0;
@ -538,31 +546,10 @@ export default class RFB extends EventTargetMixin {
Log.Info("Sending key (" + (down ? "down" : "up") + "): keysym " + keysym + ", scancode " + scancode);
RFB.messages.QEMUExtendedKeyEvent(this._sock, keysym, down, scancode);
} else if (Object.keys(this._scancodes).length > 0) {
let vscancode = this._scancodes[keysym]
if (vscancode) {
let shifted = vscancode.includes("shift");
let vscancode_int = parseInt(vscancode);
let isLetter = (keysym >= 65 && keysym <=90) || (keysym >=97 && keysym <=122);
if (shifted && ! this._shiftPressed && ! isLetter) {
RFB.messages.keyEvent(this._sock, this._shiftKey, 1);
}
if (! shifted && this._shiftPressed && ! isLetter) {
RFB.messages.keyEvent(this._sock, this._shiftKey, 0);
}
RFB.messages.VMwareExtendedKeyEvent(this._sock, keysym, down, vscancode_int);
if (shifted && ! this._shiftPressed && ! isLetter) {
RFB.messages.keyEvent(this._sock, this._shiftKey, 0);
}
if (! shifted && this._shiftPressed && ! isLetter) {
RFB.messages.keyEvent(this._sock, this._shiftKey, 1);
}
} else {
if (this._language === "jp" && keysym === 65328) {
keysym = 65509; // Caps lock
}
RFB.messages.keyEvent(this._sock, keysym, down ? 1 : 0);
}
} else if (Object.keys(this._scancodes).length > 0 && this._language === "jp") {
this.sendKeyWithJapaneseKeyboard(keysym, down)
} else if (Object.keys(this._scancodes).length > 0 && this._language === "es-latam") {
this.sendKeyWithSpanishLatamKeyboard(keysym, down)
} else {
if (!keysym) {
return;
@ -572,6 +559,93 @@ export default class RFB extends EventTargetMixin {
}
}
sendKeyWithJapaneseKeyboard(keysym, down) {
let vscancode = this._scancodes[keysym]
if (vscancode) {
let shifted = vscancode.includes("shift");
let vscancode_int = parseInt(vscancode);
let isLetter = (keysym >= 65 && keysym <= 90) || (keysym >= 97 && keysym <= 122);
if (shifted && !this._shiftPressed && !isLetter) {
RFB.messages.keyEvent(this._sock, this._shiftKey, 1);
}
if (!shifted && this._shiftPressed && !isLetter) {
RFB.messages.keyEvent(this._sock, this._shiftKey, 0);
}
RFB.messages.VMwareExtendedKeyEvent(this._sock, keysym, down, vscancode_int);
if (shifted && !this._shiftPressed && !isLetter) {
RFB.messages.keyEvent(this._sock, this._shiftKey, 0);
}
if (!shifted && this._shiftPressed && !isLetter) {
RFB.messages.keyEvent(this._sock, this._shiftKey, 1);
}
} else {
if (keysym === 65328) {
keysym = 65509; // Caps lock
}
RFB.messages.keyEvent(this._sock, keysym, down ? 1 : 0);
}
}
sendKeyWithSpanishLatamKeyboard(keysym, down) {
const VSCODE_ACUTE_LATAM = 26; // The ASCII code of acute is 180
let vscancode = this._scancodes[keysym]
if (vscancode) {
let shifted = vscancode.includes("shift");
let altgr = vscancode.includes("altgr");
let acute = vscancode.includes("acute");
let vscancode_int = parseInt(vscancode);
if (acute) {
let shifted_1 = vscancode.includes("shift1"); // Shift with Acute accent
let shifted_2 = vscancode.includes("shift2"); // Shift with a/e/i/o/u
if (down) {
if (shifted_1 && ! this._shiftPressed) {
RFB.messages.keyEvent(this._sock, this._shiftKey, 1);
} else if (! shifted_1 && this._shiftPressed) {
RFB.messages.keyEvent(this._sock, this._shiftKey, 0);
}
RFB.messages.VMwareExtendedKeyEvent(this._sock, keysym, 1, VSCODE_ACUTE_LATAM);
RFB.messages.VMwareExtendedKeyEvent(this._sock, keysym, 0, VSCODE_ACUTE_LATAM);
if (shifted_2) {
RFB.messages.keyEvent(this._sock, this._shiftKey, 1);
} else {
RFB.messages.keyEvent(this._sock, this._shiftKey, 0);
}
} else {
RFB.messages.VMwareExtendedKeyEvent(this._sock, keysym, 0, VSCODE_ACUTE_LATAM);
if (shifted_2 && ! this._shiftPressed) {
RFB.messages.keyEvent(this._sock, this._shiftKey, 0);
} else if (! shifted_2 && this._shiftPressed) {
RFB.messages.keyEvent(this._sock, this._shiftKey, 1);
}
}
RFB.messages.VMwareExtendedKeyEvent(this._sock, keysym, down, vscancode_int);
return;
}
let isLetter = (keysym >= 65 && keysym <= 90) || (keysym >= 97 && keysym <= 122);
if (shifted && !this._shiftPressed && !isLetter && down) {
RFB.messages.keyEvent(this._sock, this._shiftKey, 1);
}
if (!shifted && this._shiftPressed && !isLetter && down) {
RFB.messages.keyEvent(this._sock, this._shiftKey, 0);
}
if (altgr && !this._altgrPressed && down) {
RFB.messages.keyEvent(this._sock, KeyTable.XK_Alt_R, 1);
}
RFB.messages.VMwareExtendedKeyEvent(this._sock, keysym, down, vscancode_int);
if (altgr && !this._altgrPressed && !down) {
RFB.messages.keyEvent(this._sock, KeyTable.XK_Alt_R, 0);
}
if (shifted && !this._shiftPressed && !isLetter && !down) {
RFB.messages.keyEvent(this._sock, this._shiftKey, 0);
}
if (!shifted && this._shiftPressed && !isLetter && !down) {
RFB.messages.keyEvent(this._sock, this._shiftKey, 1);
}
} else {
RFB.messages.keyEvent(this._sock, keysym, down ? 1 : 0);
}
}
focus(options) {
this._canvas.focus(options);
}

View File

@ -2,7 +2,7 @@
# This script
# (1) loads keysym name and keycode mappings from noVNC/core/input/keysym.js and
# (2) loads keysyn name to atset1 code mappings from keymap files which can be downloadeded from https://github.com/qemu/qemu/blob/master/pc-bios/keymaps
# (2) loads keysym name to atset1 code mappings from keymap files which can be downloadeded from https://github.com/qemu/qemu/blob/master/pc-bios/keymaps
# (3) generates the mappings of keycode and atset1 code
#
# Note: please add language specific mappings if needed.
@ -96,7 +96,10 @@ def generate_js_file(keymap_file):
js_config.append(" */\n")
js_config.append("export default {\n")
for keycode in dict(sorted(list(result_mappings.items()), key=lambda item: int(item[0]))):
js_config.append("%10s : \"%s\",\n" % ("\"" + str(keycode) + "\"", result_mappings[keycode].strip()))
if keycode not in list(keycode_to_x11name.keys()):
js_config.append("%10s : \"%s\",\n" % ("\"" + str(keycode) + "\"", result_mappings[keycode].strip()))
else:
js_config.append("%10s : \"%s\", // %s\n" % ("\"" + str(keycode) + "\"", result_mappings[keycode].strip(), keycode_to_x11name[keycode]))
js_config.append("}\n")
for line in js_config:
handle.write(line)

View File

@ -0,0 +1,131 @@
/* This file is auto-generated by generate-language-keymaps.py
* command : generate-language-keymaps.py keymap-es
* layout : es-latam
*/
export default {
"32" : "57", // XK_space
"33" : "2 shift", // XK_exclam
"34" : "3 shift", // XK_quotedbl
"35" : "4 shift", // XK_numbersign
"36" : "5 shift", // XK_dollar
"37" : "6 shift", // XK_percent
"38" : "7 shift", // XK_ampersand
"39" : "12", // XK_apostrophe
"40" : "9 shift", // XK_parenleft
"41" : "10 shift", // XK_parenright
"42" : "27 shift", // XK_asterisk
"43" : "27", // XK_plus
"44" : "51", // XK_comma
"45" : "53", // XK_minus
"46" : "52", // XK_period
"47" : "8 shift", // XK_slash
"48" : "11", // XK_0
"49" : "2", // XK_1
"50" : "3", // XK_2
"51" : "4", // XK_3
"52" : "5", // XK_4
"53" : "6", // XK_5
"54" : "7", // XK_6
"55" : "8", // XK_7
"56" : "9", // XK_8
"57" : "10", // XK_9
"58" : "52 shift", // XK_colon
"59" : "51 shift", // XK_semicolon
"60" : "86", // XK_less
"61" : "11 shift", // XK_equal
"62" : "86 shift", // XK_greater
"63" : "12 shift", // XK_question
"64" : "16 altgr", // XK_at
"65" : "30 shift", // XK_A
"66" : "48 shift", // XK_B
"67" : "46 shift", // XK_C
"68" : "32 shift", // XK_D
"69" : "18 shift", // XK_E
"70" : "33 shift", // XK_F
"71" : "34 shift", // XK_G
"72" : "35 shift", // XK_H
"73" : "23 shift", // XK_I
"74" : "36 shift", // XK_J
"75" : "37 shift", // XK_K
"76" : "38 shift", // XK_L
"77" : "50 shift", // XK_M
"78" : "49 shift", // XK_N
"79" : "24 shift", // XK_O
"80" : "25 shift", // XK_P
"81" : "16 shift", // XK_Q
"82" : "19 shift", // XK_R
"83" : "31 shift", // XK_S
"84" : "20 shift", // XK_T
"85" : "22 shift", // XK_U
"86" : "47 shift", // XK_V
"87" : "17 shift", // XK_W
"88" : "45 shift", // XK_X
"89" : "21 shift", // XK_Y
"90" : "44 shift", // XK_Z
"91" : "40 shift", // XK_bracketleft
"92" : "12 altgr", // XK_backslash
"93" : "43 shift", // XK_bracketright
"94": "40 altgr", // ^
"95" : "53 shift", // XK_underscore
"96": "43 altgr", // `
"97" : "30", // XK_a
"98" : "48", // XK_b
"99" : "46", // XK_c
"100" : "32", // XK_d
"101" : "18", // XK_e
"102" : "33", // XK_f
"103" : "34", // XK_g
"104" : "35", // XK_h
"105" : "23", // XK_i
"106" : "36", // XK_j
"107" : "37", // XK_k
"108" : "38", // XK_l
"109" : "50", // XK_m
"110" : "49", // XK_n
"111" : "24", // XK_o
"112" : "25", // XK_p
"113" : "16", // XK_q
"114" : "19", // XK_r
"115" : "31", // XK_s
"116" : "20", // XK_t
"117" : "22", // XK_u
"118" : "47", // XK_v
"119" : "17", // XK_w
"120" : "45", // XK_x
"121" : "21", // XK_y
"122" : "44", // XK_z
"123" : "40", // XK_braceleft
"124" : "41", // XK_bar
"125" : "43", // XK_braceright
"126" : "27 altgr", // XK_asciitilde
"161" : "13 shift", // XK_exclamdown
"168" : "26 shift", // ¨
"171" : "44 altgr", // XK_guillemotleft
"172" : "41 altgr", // XK_notsign
"176" : "41 shift", // XK_degree
"180" : "26", // ´
"186" : "41", // XK_masculine
"191" : "13", // XK_questiondown
"193" : "30 acute shift2", // Á
"196" : "30 shift1 acute shift2", // Ä
"201" : "18 acute shift2", // É
"203" : "18 shift1 acute shift2", // Ë
"205" : "23 acute shift2", // Í
"207" : "23 shift1 acute shift2", // Ï
"209" : "39 shift", // XK_Ntilde
"211" : "24 acute shift2", // Ó
"214" : "24 shift1 acute shift2", // Ö
"218" : "22 acute shift2", // Ú
"220" : "22 shift1 acute shift2", // Ü
"225" : "30 acute", // á
"228" : "30 shift1 acute", // ä
"233" : "18 acute", // é
"235" : "18 shift1 acute", // ë
"237" : "23 acute", // í
"239" : "23 shift1 acute", // ï
"241" : "39", // XK_ntilde
"243" : "24 acute", // ó
"246" : "24 shift1 acute", // ö
"250" : "22 acute", // ú
"252" : "22 shift1 acute", // ü
}

View File

@ -19,11 +19,8 @@
under the License.
-->
<ruleset name="Maven Ruleset"
xmlns="http://pmd.sf.net/ruleset/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd"
xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd">
<ruleset name="CloudStack PMD Ruleset"
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0">
<description>
Ruleset that brings all the rulesets we want from the pmd jar, because
@ -31,16 +28,16 @@
to add our own future rulesets, if any.
</description>
<rule ref="rulesets/java/basic.xml"/>
<rule ref="rulesets/java/braces.xml"/>
<rule ref="rulesets/java/clone.xml"/>
<rule ref="rulesets/java/codesize.xml"/>
<rule ref="rulesets/java/comments.xml">
<rule ref="category/java/basic.xml"/>
<rule ref="category/java/braces.xml"/>
<rule ref="category/java/clone.xml"/>
<rule ref="category/java/codesize.xml"/>
<rule ref="category/java/comments.xml">
<!-- We shouldn't limit the number of lines in the header of a class -->
<exclude name="CommentSize"/>
<exclude name="CommentRequired" />
</rule>
<rule ref="rulesets/java/controversial.xml">
<rule ref="category/java/controversial.xml">
<!-- The rule is good, but is not properly applied. It forces you to statically declare it as ConcurrentHashMap -->
<exclude name="UseConcurrentHashMap"/>
<exclude name="CallSuperInConstructor"/>
@ -50,35 +47,35 @@
<exclude name="DataflowAnomalyAnalysis" />
<exclude name="UseObjectForClearerAPI" />
</rule>
<rule ref="rulesets/java/coupling.xml">
<rule ref="category/java/coupling.xml">
<exclude name="ExcessiveImports" />
<exclude name="LawOfDemeter"/>
</rule>
<rule ref="rulesets/java/design.xml">
<rule ref="category/java/design.xml">
<exclude name="ConstructorCallsOverridableMethod"/>
<exclude name="AbstractClassWithoutAbstractMethod"/>
<exclude name="AvoidSynchronizedAtMethodLevel"/>
</rule>
<rule ref="rulesets/java/empty.xml"/>
<rule ref="rulesets/java/finalizers.xml"/>
<rule ref="rulesets/java/imports.xml"/>
<rule ref="rulesets/java/j2ee.xml"/>
<rule ref="rulesets/java/junit.xml"/>
<rule ref="rulesets/java/logging-java.xml"/>
<rule ref="rulesets/java/naming.xml">
<rule ref="category/java/empty.xml"/>
<rule ref="category/java/finalizers.xml"/>
<rule ref="category/java/imports.xml"/>
<rule ref="category/java/j2ee.xml"/>
<rule ref="category/java/junit.xml"/>
<rule ref="category/java/logging-java.xml"/>
<rule ref="category/java/naming.xml">
<exclude name="ShortVariable"/>
<exclude name="AbstractNaming"/>
</rule>
<rule ref="rulesets/java/naming.xml/LongVariable">
<rule ref="category/java/naming.xml/LongVariable">
<properties>
<property name="minimum" value="32"/>
</properties>
</rule>
<rule ref="rulesets/java/optimizations.xml"/>
<rule ref="rulesets/java/strictexception.xml"/>
<rule ref="rulesets/java/strings.xml"/>
<rule ref="rulesets/java/sunsecure.xml"/>
<rule ref="rulesets/java/typeresolution.xml"/>
<rule ref="rulesets/java/unnecessary.xml"/>
<rule ref="rulesets/java/unusedcode.xml"/>
<rule ref="category/java/optimizations.xml"/>
<rule ref="category/java/strictexception.xml"/>
<rule ref="category/java/strings.xml"/>
<rule ref="category/java/sunsecure.xml"/>
<rule ref="category/java/typeresolution.xml"/>
<rule ref="category/java/unnecessary.xml"/>
<rule ref="category/java/unusedcode.xml"/>
</ruleset>

View File

@ -61,7 +61,8 @@
"uk": "label.uk.keyboard",
"fr": "label.french.azerty.keyboard",
"jp": "label.japanese.keyboard",
"sc": "label.simplified.chinese.keyboard"
"sc": "label.simplified.chinese.keyboard",
"es-latam": "Spanish Latin American Keyboard"
},
"userCard": {
"enabled": true,

View File

@ -19,6 +19,7 @@
"error.release.dedicate.pod": "Failed to release dedicated Pod.",
"error.release.dedicate.zone": "Failed to release dedicated Zone.",
"error.unable.to.add.setting.extraconfig": "It is not allowed to add setting for extraconfig. Please update VirtualMachine with extraconfig parameter.",
"error.unable.to.add.setting": "Unable to add or edit setting",
"error.unable.to.proceed": "Unable to proceed. Please contact your administrator.",
"firewall.close": "Firewall",
"icmp.code.desc": "Please specify -1 if you want to allow all ICMP codes (except NSX zones).",
@ -1023,6 +1024,7 @@
"label.endpoint": "Endpoint",
"label.endport": "End port",
"label.enter.account.name": "Enter the account name",
"label.enter.domain.name": "Enter the domain name",
"label.enter.code": "Enter 2FA code to verify",
"label.enter.static.pin": "Enter static PIN to verify",
"label.enter.token": "Enter token",
@ -3299,6 +3301,9 @@
"message.delete.account.processing": "Deleting account",
"message.delete.account.success": "Successfully deleted account",
"message.delete.account.warning": "Deleting this account will delete all of the instances, volumes and snapshots associated with the account.",
"message.delete.domain.confirm": "Please confirm that you want to delete this domain by entering the name of the domain below.",
"message.delete.domain.warning": "All associated accounts, users, VMs, and sub-domains will be permanently deleted. This action cannot be undone.",
"message.delete.domain.failed": "Delete domain failed",
"message.delete.acl.processing": "Removing ACL rule...",
"message.delete.acl.rule": "Remove ACL rule",
"message.delete.acl.rule.failed": "Failed to remove ACL rule.",
@ -3432,6 +3437,7 @@
"message.error.delete.tungsten.tag": "Removing Tag failed",
"message.error.description": "Please enter description.",
"message.error.discovering.feature": "Exception caught while discovering features.",
"message.error.setting.deployasistemplate": "Settings are read directly from the template",
"message.error.setup.2fa": "2FA setup failed while verifying the code, please retry.",
"message.error.verifying.2fa": "Unable to verify 2FA, please retry.",
"message.error.display.text": "Please enter display text.",

View File

@ -100,7 +100,7 @@
<tooltip-button
:tooltip="$t('label.edit')"
icon="edit-outlined"
:disabled="deployasistemplate === true || item.name.startsWith('extraconfig')"
:disabled="item.name.startsWith('extraconfig')"
v-if="!item.edit"
@onClick="showEditDetail(index)" />
</div>
@ -115,7 +115,7 @@
>
<tooltip-button
:tooltip="$t('label.delete')"
:disabled="deployasistemplate === true || item.name.startsWith('extraconfig')"
:disabled="item.name.startsWith('extraconfig')"
type="primary"
:danger="true"
icon="delete-outlined" />
@ -213,11 +213,16 @@ export default {
this.detailOptions = json.listdetailoptionsresponse.detailoptions.details
})
this.disableSettings = (this.$route.meta.name === 'vm' && resource.state !== 'Stopped')
getAPI('listTemplates', { templatefilter: 'all', id: resource.templateid }).then(json => {
this.deployasistemplate = json.listtemplatesresponse.template[0].deployasis
})
if (this.$route.meta.name === 'vm') {
getAPI('listTemplates', { templatefilter: 'all', id: resource.templateid }).then(json => {
this.deployasistemplate = json.listtemplatesresponse.template[0].deployasis
})
}
},
allowEditOfDetail (name) {
if (this.deployasistemplate) {
return this.resource.alloweddetails && this.resource.alloweddetails.split(',').map(item => item.trim()).includes(name)
}
if (this.resource.readonlydetails) {
if (this.resource.readonlydetails.split(',').map(item => item.trim()).includes(name)) {
return false
@ -320,7 +325,11 @@ export default {
return
}
if (!this.allowEditOfDetail(this.newKey)) {
this.error = this.$t('error.unable.to.proceed')
if (this.deployasistemplate) {
this.error = this.$t('error.unable.to.add.setting') + ' : ' + this.newKey + '. ' + this.$t('message.error.setting.deployasistemplate')
} else {
this.error = this.$t('error.unable.to.add.setting') + ' : ' + this.newKey
}
return
}
this.error = false

View File

@ -0,0 +1,155 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
<template>
<a-modal
:visible="true"
:title="$t('label.action.delete.domain') + ': ' + domain.name"
:okText="$t('label.delete.domain')"
okType="danger"
:confirmLoading="loading"
:ok-button-props="{ disabled: !canDelete }"
@cancel="emitClose"
@ok="emitConfirm">
<a-alert
type="warning"
show-icon
style="margin-bottom: 16px">
<template #message>
<div v-html="$t('message.delete.domain.warning')"></div>
</template>
</a-alert>
<a-spin v-if="loading" />
<a-table
v-else
size="small"
:columns="columns"
:dataSource="accountVmSummary"
:pagination="false"
rowKey="account" />
<div style="margin-top: 16px">
<a-alert style="margin-bottom: 10px">
<template #message>
<div v-html="$t('message.delete.domain.confirm')"></div>
</template>
</a-alert>
<a-input
v-model:value="confirmText"
:placeholder="$t('label.enter.domain.name')" />
</div>
</a-modal>
</template>
<script>
import { api } from '@/api'
export default {
name: 'DomainDeleteConfirm',
props: {
domain: {
type: Object,
required: true
}
},
data () {
return {
loading: false,
confirmText: '',
accountVmSummary: []
}
},
computed: {
canDelete () {
return this.confirmText.trim() === this.domain.name.trim()
},
columns () {
return [
{ title: this.$t('label.account'), dataIndex: 'account' },
{ title: this.$t('label.total') + ' VMs', dataIndex: 'total' },
{ title: this.$t('label.running') + ' VMs', dataIndex: 'running' },
{ title: this.$t('label.stopped') + ' VMs', dataIndex: 'stopped' }
]
}
},
mounted () {
this.fetchDomainImpact()
},
methods: {
emitClose () {
this.$emit('close')
},
emitConfirm () {
if (this.canDelete) {
this.$emit('confirm')
}
},
async fetchDomainImpact () {
this.loading = true
try {
const accResp = await api('listAccounts', {
domainid: this.domain.id,
listall: true
})
const accounts =
accResp.listaccountsresponse &&
accResp.listaccountsresponse.account
? accResp.listaccountsresponse.account
: []
const vmResp = await api('listVirtualMachines', {
domainid: this.domain.id,
listall: true
})
const vms =
vmResp.listvirtualmachinesresponse &&
vmResp.listvirtualmachinesresponse.virtualmachine
? vmResp.listvirtualmachinesresponse.virtualmachine
: []
this.accountVmSummary = accounts.map(account => {
const accountVms = vms.filter(vm => vm.account === account.name)
const running = accountVms.filter(vm => vm.state === 'Running').length
const stopped = accountVms.length - running
return {
account: account.name,
total: accountVms.length,
running,
stopped
}
})
} catch (e) {
this.$notification.error({
message: this.$t('message.request.failed'),
description: e.response?.headers['x-description'] || this.$t('message.request.failed')
})
} finally {
this.loading = false
}
}
}
}
</script>
<style scoped>
</style>

View File

@ -74,6 +74,11 @@
:resource="resource"
:action="action"/>
</div>
<domain-delete-confirm
v-if="showDeleteConfirm"
:domain="deleteDomainResource"
@close="showDeleteConfirm = false"
@confirm="confirmDeleteDomain" />
</div>
</template>
@ -87,6 +92,7 @@ import ActionButton from '@/components/view/ActionButton'
import TreeView from '@/components/view/TreeView'
import DomainActionForm from '@/views/iam/DomainActionForm'
import ResourceView from '@/components/view/ResourceView'
import DomainDeleteConfirm from '@/components/view/DomainDeleteConfirm'
import eventBus from '@/config/eventBus'
export default {
@ -96,7 +102,8 @@ export default {
ActionButton,
TreeView,
DomainActionForm,
ResourceView
ResourceView,
DomainDeleteConfirm
},
mixins: [mixinDevice],
data () {
@ -111,7 +118,9 @@ export default {
action: {},
dataView: false,
domainStore: {},
treeDeletedKey: null
treeDeletedKey: null,
showDeleteConfirm: false,
deleteDomainResource: null
}
},
computed: {
@ -205,7 +214,12 @@ export default {
})
},
execAction (action) {
this.treeDeletedKey = action.api === 'deleteDomain' ? this.resource.key : null
if (action.api === 'deleteDomain') {
this.deleteDomainResource = this.resource
this.showDeleteConfirm = true
return
}
this.treeDeletedKey = null
this.actionData = []
this.action = action
this.action.params = store.getters.apis[this.action.api].params
@ -316,6 +330,42 @@ export default {
closeAction () {
this.showAction = false
},
confirmDeleteDomain () {
const domain = this.deleteDomainResource
const params = { id: domain.id, cleanup: true }
api('deleteDomain', params).then(json => {
const jobId = json.deletedomainresponse.jobid
this.$pollJob({
jobId,
title: this.$t('label.action.delete.domain'),
description: domain.name,
loadingMessage: `${this.$t('label.action.delete.domain')} ${domain.name}`,
successMessage: `${this.$t('label.action.delete.domain')} ${domain.name}`,
catchMessage: this.$t('error.fetching.async.job.result'),
successMethod: () => {
this.$router.replace({ path: '/domain' })
this.resource = {}
this.treeSelected = {}
this.treeDeletedKey = null
this.treeViewKey += 1
this.$nextTick(() => {
this.fetchData()
})
}
})
}).catch(error => {
this.$notification.error({
message: this.$t('message.request.failed'),
description: error.response?.headers['x-description'] || this.$t('message.request.failed')
})
}).finally(() => {
this.showDeleteConfirm = false
this.deleteDomainResource = null
this.treeDeletedKey = null
})
},
forceRerender () {
this.treeViewKey += 1
}

View File

@ -261,6 +261,8 @@ export default {
userdatapolicylist: {},
architectureTypes: {},
originalOstypeid: null
detailsFields: [],
details: {}
}
},
beforeCreate () {
@ -315,17 +317,10 @@ export default {
}
}
}
const resourceDetailsFields = []
if (this.resource.hypervisor === 'KVM') {
resourceDetailsFields.push('rootDiskController')
this.detailsFields.push('rootDiskController')
} else if (this.resource.hypervisor === 'VMware' && !this.resource.deployasis) {
resourceDetailsFields.push(...['rootDiskController', 'nicAdapter', 'keyboard'])
}
for (var detailsField of resourceDetailsFields) {
var detailValue = this.resource?.details?.[detailsField] || null
if (detailValue) {
this.form[detailValue] = fieldValue
}
this.detailsFields.push(...['rootDiskController', 'nicAdapter', 'keyboard'])
}
},
fetchData () {
@ -336,6 +331,7 @@ export default {
this.fetchKeyboardTypes()
this.fetchUserdata()
this.fetchUserdataPolicy()
this.fetchDetails()
},
isValidValueForKey (obj, key) {
if (this.emptyAllowedFields.includes(key) && obj[key] === '') {
@ -380,6 +376,10 @@ export default {
id: 'virtio',
description: 'virtio'
})
controller.push({
id: 'virtio-blk',
description: 'virtio-blk'
})
} else if (hyperVisor === 'VMware') {
controller.push({
id: '',
@ -506,6 +506,25 @@ export default {
this.userdata.loading = false
})
},
fetchDetails () {
const params = {}
params.id = this.resource.id
params.templatefilter = 'all'
api('listTemplates', params).then(response => {
if (response?.listtemplatesresponse?.template?.length > 0) {
this.details = response.listtemplatesresponse.template[0].details
if (this.details) {
for (var detailsField of this.detailsFields) {
var detailValue = this.details?.[detailsField] || null
if (detailValue) {
this.form[detailsField] = detailValue
}
}
}
}
})
},
handleSubmit (e) {
e.preventDefault()
if (this.loading) return
@ -515,10 +534,14 @@ export default {
const params = {
id: this.resource.id
}
const detailsField = ['rootDiskController', 'nicAdapter', 'keyboard']
if (this.details) {
Object.keys(this.details).forEach((detail, index) => {
params['details[0].' + detail] = this.details[detail]
})
}
for (const key in values) {
if (!this.isValidValueForKey(values, key)) continue
if (detailsField.includes(key)) {
if (this.detailsFields.includes(key)) {
params['details[0].' + key] = values[key]
continue
}