Merge branch '4.20'

This commit is contained in:
Daan Hoogland 2025-10-07 15:12:53 +02:00
commit 5f63e8493c
21 changed files with 218 additions and 145 deletions

View File

@ -27,6 +27,7 @@ public class ApiConstants {
public static final String ACTIVATION_RULE = "activationrule";
public static final String ACTIVITY = "activity";
public static final String ADAPTER_TYPE = "adaptertype";
public static final String ADDITONAL_CONFIG_ENABLED = "additionalconfigenabled";
public static final String ADDRESS = "address";
public static final String ALGORITHM = "algorithm";
public static final String ALIAS = "alias";

View File

@ -75,6 +75,7 @@ public class ListCapabilitiesCmd extends BaseCmd {
response.setInstanceLeaseEnabled((Boolean) capabilities.get(ApiConstants.INSTANCE_LEASE_ENABLED));
response.setExtensionsPath((String)capabilities.get(ApiConstants.EXTENSIONS_PATH));
response.setDynamicScalingEnabled((Boolean) capabilities.get(ApiConstants.DYNAMIC_SCALING_ENABLED));
response.setAdditionalConfigEnabled((Boolean) capabilities.get(ApiConstants.ADDITONAL_CONFIG_ENABLED));
response.setObjectName("capability");
response.setResponseName(getCommandName());
this.setResponseObject(response);

View File

@ -149,6 +149,10 @@ public class CapabilitiesResponse extends BaseResponse {
@Param(description = "true if dynamically scaling for instances is enabled", since = "4.21.0")
private Boolean dynamicScalingEnabled;
@SerializedName(ApiConstants.ADDITONAL_CONFIG_ENABLED)
@Param(description = "true if additional configurations or extraconfig can be passed to Instances", since = "4.20.2")
private Boolean additionalConfigEnabled;
public void setSecurityGroupsEnabled(boolean securityGroupsEnabled) {
this.securityGroupsEnabled = securityGroupsEnabled;
}
@ -272,4 +276,8 @@ public class CapabilitiesResponse extends BaseResponse {
public void setDynamicScalingEnabled(Boolean dynamicScalingEnabled) {
this.dynamicScalingEnabled = dynamicScalingEnabled;
}
public void setAdditionalConfigEnabled(Boolean additionalConfigEnabled) {
this.additionalConfigEnabled = additionalConfigEnabled;
}
}

View File

@ -116,17 +116,17 @@ public class VMInstanceDaoImpl extends GenericDaoBase<VMInstanceVO, Long> implem
protected Attribute _updateTimeAttr;
private static final String ORDER_CLUSTERS_NUMBER_OF_VMS_FOR_ACCOUNT_PART1 = "SELECT host.cluster_id, SUM(IF(vm.state='Running' AND vm.account_id = ?, 1, 0)) " +
private static final String ORDER_CLUSTERS_NUMBER_OF_VMS_FOR_ACCOUNT_PART1 = "SELECT host.cluster_id, SUM(IF(vm.state IN ('Running', 'Starting') AND vm.account_id = ?, 1, 0)) " +
"FROM `cloud`.`host` host LEFT JOIN `cloud`.`vm_instance` vm ON host.id = vm.host_id WHERE ";
private static final String ORDER_CLUSTERS_NUMBER_OF_VMS_FOR_ACCOUNT_PART2 = " AND host.type = 'Routing' AND host.removed is null GROUP BY host.cluster_id " +
"ORDER BY 2 ASC ";
private static final String ORDER_PODS_NUMBER_OF_VMS_FOR_ACCOUNT = "SELECT pod.id, SUM(IF(vm.state='Running' AND vm.account_id = ?, 1, 0)) FROM `cloud`.`" +
private static final String ORDER_PODS_NUMBER_OF_VMS_FOR_ACCOUNT = "SELECT pod.id, SUM(IF(vm.state IN ('Running', 'Starting') AND vm.account_id = ?, 1, 0)) FROM `cloud`.`" +
"host_pod_ref` pod LEFT JOIN `cloud`.`vm_instance` vm ON pod.id = vm.pod_id WHERE pod.data_center_id = ? AND pod.removed is null "
+ " GROUP BY pod.id ORDER BY 2 ASC ";
private static final String ORDER_HOSTS_NUMBER_OF_VMS_FOR_ACCOUNT =
"SELECT host.id, SUM(IF(vm.state='Running' AND vm.account_id = ?, 1, 0)) FROM `cloud`.`host` host LEFT JOIN `cloud`.`vm_instance` vm ON host.id = vm.host_id " +
"SELECT host.id, SUM(IF(vm.state IN ('Running', 'Starting') AND vm.account_id = ?, 1, 0)) FROM `cloud`.`host` host LEFT JOIN `cloud`.`vm_instance` vm ON host.id = vm.host_id " +
"WHERE host.data_center_id = ? AND host.type = 'Routing' AND host.removed is null ";
private static final String ORDER_HOSTS_NUMBER_OF_VMS_FOR_ACCOUNT_PART2 = " GROUP BY host.id ORDER BY 2 ASC ";

View File

@ -190,7 +190,9 @@ public class PrimaryDataStoreHelper {
pool.setScope(scope.getScopeType());
pool.setUsedBytes(existingInfo.getCapacityBytes() - existingInfo.getAvailableBytes());
pool.setCapacityBytes(existingInfo.getCapacityBytes());
pool.setStatus(StoragePoolStatus.Up);
if (pool.getStatus() != StoragePoolStatus.Disabled) {
pool.setStatus(StoragePoolStatus.Up);
}
this.dataStoreDao.update(pool.getId(), pool);
this.storageMgr.createCapacityEntry(pool, Capacity.CAPACITY_TYPE_LOCAL_STORAGE, pool.getUsedBytes());
return dataStoreMgr.getDataStore(pool.getId(), DataStoreRole.Primary);

View File

@ -5838,11 +5838,20 @@ public class VmwareResource extends ServerResourceBase implements StoragePoolRes
if (toolsStatus == VirtualMachineToolsStatus.TOOLS_NOT_INSTALLED) {
details += "Vmware tools not installed.";
} else {
ip = guestInfo.getIpAddress();
if (ip != null) {
result = true;
var normalizedMac = cmd.getMacAddress().replaceAll("-", ":");
for(var guestInfoNic : guestInfo.getNet()) {
var normalizedNicMac = guestInfoNic.getMacAddress().replaceAll("-", ":");
if (!result && normalizedNicMac.equalsIgnoreCase(normalizedMac)) {
result = true;
details = null;
for (var ipAddr : guestInfoNic.getIpAddress()) {
if (NetUtils.isValidIp4(ipAddr) && (cmd.getVmNetworkCidr() == null || NetUtils.isIpWithInCidrRange(ipAddr, cmd.getVmNetworkCidr()))) {
details = ipAddr;
}
}
break;
}
}
details = ip;
}
} else {
details += "VM " + vmName + " no longer exists on vSphere host: " + hyperHost.getHyperHostName();

View File

@ -430,6 +430,9 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
logger.warn("Unable to find the network with ID: {} passed for the Kubernetes cluster", networkId);
return false;
}
if (isDirectAccess(network)) {
return true;
}
networkOffering = networkOfferingDao.findById(network.getNetworkOfferingId());
if (networkOffering == null) {
logger.warn("Unable to find the network offering of the network: {} ({}) to be used for provisioning Kubernetes cluster", network.getName(), network.getUuid());
@ -1870,7 +1873,7 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
try {
Role role = getProjectKubernetesAccountRole();
UserAccount userAccount = accountService.createUserAccount(accountName,
UuidUtils.first(UUID.randomUUID().toString()), PROJECT_KUBERNETES_ACCOUNT_FIRST_NAME,
UUID.randomUUID().toString(), PROJECT_KUBERNETES_ACCOUNT_FIRST_NAME,
PROJECT_KUBERNETES_ACCOUNT_LAST_NAME, null, null, accountName, Account.Type.NORMAL, role.getId(),
project.getDomainId(), null, null, null, null, User.Source.NATIVE);
projectManager.assignAccountToProject(project, userAccount.getAccountId(), ProjectAccount.Role.Regular,

View File

@ -504,7 +504,7 @@ public class CloudStackPrimaryDataStoreLifeCycleImpl extends BasePrimaryDataStor
@Override
public boolean attachHost(DataStore store, HostScope scope, StoragePoolInfo existingInfo) {
DataStore dataStore = dataStoreHelper.attachHost(store, scope, existingInfo);
if(existingInfo.getCapacityBytes() == 0){
if (existingInfo.getCapacityBytes() == 0) {
try {
storageMgr.connectHostToSharedPool(hostDao.findById(scope.getScopeId()), dataStore.getId());
} catch (StorageUnavailableException ex) {

View File

@ -5,6 +5,12 @@ All notable changes to Linstor CloudStack plugin will be documented in this file
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2025-10-03]
### Changed
- Revert qcow2 snapshot now use sparse/discard options to convert on thin devices.
## [2025-08-05]
### Fixed

View File

@ -26,24 +26,39 @@ import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager;
import com.cloud.resource.CommandWrapper;
import com.cloud.resource.ResourceWrapper;
import com.cloud.storage.Storage;
import com.cloud.utils.script.Script;
import org.apache.cloudstack.storage.command.CopyCmdAnswer;
import org.apache.cloudstack.storage.datastore.util.LinstorUtil;
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
import org.apache.cloudstack.storage.to.VolumeObjectTO;
import org.apache.cloudstack.utils.qemu.QemuImg;
import org.apache.cloudstack.utils.qemu.QemuImgException;
import org.apache.cloudstack.utils.qemu.QemuImgFile;
import org.joda.time.Duration;
import org.libvirt.LibvirtException;
@ResourceWrapper(handles = LinstorRevertBackupSnapshotCommand.class)
public final class LinstorRevertBackupSnapshotCommandWrapper
extends CommandWrapper<LinstorRevertBackupSnapshotCommand, CopyCmdAnswer, LibvirtComputingResource>
{
private void convertQCow2ToRAW(final String srcPath, final String dstPath, int waitMilliSeconds)
private void convertQCow2ToRAW(
KVMStoragePool pool, final String srcPath, final String dstUuid, int waitMilliSeconds)
throws LibvirtException, QemuImgException
{
final String dstPath = pool.getPhysicalDisk(dstUuid).getPath();
final QemuImgFile srcQemuFile = new QemuImgFile(
srcPath, QemuImg.PhysicalDiskFormat.QCOW2);
final QemuImg qemu = new QemuImg(waitMilliSeconds);
boolean zeroedDevice = LinstorUtil.resourceSupportZeroBlocks(pool, LinstorUtil.RSC_PREFIX + dstUuid);
if (zeroedDevice)
{
// blockdiscard the device to ensure the device is filled with zeroes
Script blkDiscardScript = new Script("blkdiscard", Duration.millis(waitMilliSeconds));
blkDiscardScript.add("-f");
blkDiscardScript.add(dstPath);
blkDiscardScript.execute();
}
final QemuImg qemu = new QemuImg(waitMilliSeconds, zeroedDevice, true);
final QemuImgFile dstFile = new QemuImgFile(dstPath, QemuImg.PhysicalDiskFormat.RAW);
qemu.convert(srcQemuFile, dstFile);
}
@ -70,8 +85,9 @@ public final class LinstorRevertBackupSnapshotCommandWrapper
srcDataStore.getUrl() + File.separator + srcFile.getParent());
convertQCow2ToRAW(
linstorPool,
secondaryPool.getLocalPath() + File.separator + srcFile.getName(),
linstorPool.getPhysicalDisk(dst.getPath()).getPath(),
dst.getPath(),
cmd.getWaitInMillSeconds());
final VolumeObjectTO dstVolume = new VolumeObjectTO();

View File

@ -30,7 +30,6 @@ import javax.annotation.Nonnull;
import com.cloud.storage.Storage;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.script.Script;
import org.apache.cloudstack.storage.datastore.util.LinstorUtil;
import org.apache.cloudstack.utils.qemu.QemuImg;
import org.apache.cloudstack.utils.qemu.QemuImgException;
@ -57,7 +56,6 @@ import com.linbit.linstor.api.model.ResourceGroupSpawn;
import com.linbit.linstor.api.model.ResourceMakeAvailable;
import com.linbit.linstor.api.model.ResourceWithVolumes;
import com.linbit.linstor.api.model.StoragePool;
import com.linbit.linstor.api.model.Volume;
import com.linbit.linstor.api.model.VolumeDefinition;
import java.io.File;
@ -573,40 +571,6 @@ public class LinstorStorageAdaptor implements StorageAdaptor {
return copyPhysicalDisk(disk, name, destPool, timeout, null, null, null);
}
/**
* Checks if all diskful resource are on a zeroed block device.
* @param destPool Linstor pool to use
* @param resName Linstor resource name
* @return true if all resources are on a provider with zeroed blocks.
*/
private boolean resourceSupportZeroBlocks(KVMStoragePool destPool, String resName) {
final DevelopersApi api = getLinstorAPI(destPool);
try {
List<ResourceWithVolumes> resWithVols = api.viewResources(
Collections.emptyList(),
Collections.singletonList(resName),
Collections.emptyList(),
Collections.emptyList(),
null,
null);
if (resWithVols != null) {
return resWithVols.stream()
.allMatch(res -> {
Volume vol0 = res.getVolumes().get(0);
return vol0 != null && (vol0.getProviderKind() == ProviderKind.LVM_THIN ||
vol0.getProviderKind() == ProviderKind.ZFS ||
vol0.getProviderKind() == ProviderKind.ZFS_THIN ||
vol0.getProviderKind() == ProviderKind.DISKLESS);
} );
}
} catch (ApiException apiExc) {
logger.error(apiExc.getMessage());
}
return false;
}
/**
* Checks if the given disk is the SystemVM template, by checking its properties file in the same directory.
* The initial systemvm template resource isn't created on the management server, but
@ -677,7 +641,7 @@ public class LinstorStorageAdaptor implements StorageAdaptor {
destFile.setFormat(dstDisk.getFormat());
destFile.setSize(disk.getVirtualSize());
boolean zeroedDevice = resourceSupportZeroBlocks(destPools, getLinstorRscName(name));
boolean zeroedDevice = LinstorUtil.resourceSupportZeroBlocks(destPools, getLinstorRscName(name));
try {
final QemuImg qemu = new QemuImg(timeout, zeroedDevice, true);
qemu.convert(srcFile, destFile);

View File

@ -42,6 +42,7 @@ import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import com.cloud.hypervisor.kvm.storage.KVMStoragePool;
import com.cloud.utils.Pair;
import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.logging.log4j.Logger;
@ -431,4 +432,37 @@ public class LinstorUtil {
public static boolean isRscDiskless(ResourceWithVolumes rsc) {
return rsc.getFlags() != null && rsc.getFlags().contains(ApiConsts.FLAG_DISKLESS);
}
/**
* Checks if all diskful resource are on a zeroed block device.
* @param pool Linstor pool to use
* @param resName Linstor resource name
* @return true if all resources are on a provider with zeroed blocks.
*/
public static boolean resourceSupportZeroBlocks(KVMStoragePool pool, String resName) {
final DevelopersApi api = getLinstorAPI(pool.getSourceHost());
try {
List<ResourceWithVolumes> resWithVols = api.viewResources(
Collections.emptyList(),
Collections.singletonList(resName),
Collections.emptyList(),
Collections.emptyList(),
null,
null);
if (resWithVols != null) {
return resWithVols.stream()
.allMatch(res -> {
Volume vol0 = res.getVolumes().get(0);
return vol0 != null && (vol0.getProviderKind() == ProviderKind.LVM_THIN ||
vol0.getProviderKind() == ProviderKind.ZFS ||
vol0.getProviderKind() == ProviderKind.ZFS_THIN ||
vol0.getProviderKind() == ProviderKind.DISKLESS);
} );
}
} catch (ApiException apiExc) {
LOGGER.error(apiExc.getMessage());
}
return false;
}
}

View File

@ -31,7 +31,6 @@ import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.TimeZone;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
@ -765,14 +764,12 @@ import com.cloud.network.dao.IPAddressDao;
import com.cloud.network.dao.IPAddressVO;
import com.cloud.network.dao.LoadBalancerDao;
import com.cloud.network.dao.LoadBalancerVO;
import com.cloud.network.dao.NetrisProviderDao;
import com.cloud.network.dao.NetworkAccountDao;
import com.cloud.network.dao.NetworkAccountVO;
import com.cloud.network.dao.NetworkDao;
import com.cloud.network.dao.NetworkDomainDao;
import com.cloud.network.dao.NetworkDomainVO;
import com.cloud.network.dao.NetworkVO;
import com.cloud.network.dao.NsxProviderDao;
import com.cloud.network.vpc.dao.VpcDao;
import com.cloud.offering.ServiceOffering;
import com.cloud.org.Cluster;
@ -843,7 +840,6 @@ import com.cloud.utils.db.SearchCriteria;
import com.cloud.utils.db.Transaction;
import com.cloud.utils.db.TransactionCallbackNoReturn;
import com.cloud.utils.db.TransactionStatus;
import com.cloud.utils.db.UUIDManager;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.fsm.StateMachine2;
import com.cloud.utils.net.MacAddress;
@ -1034,8 +1030,6 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
@Inject
DomainRouterDao routerDao;
@Inject
public UUIDManager uuidMgr;
@Inject
protected UserDataDao userDataDao;
@Inject
protected VMTemplateDao templateDao;
@ -1057,10 +1051,6 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
@Inject
ResourceLimitService resourceLimitService;
@Inject
NsxProviderDao nsxProviderDao;
@Inject
NetrisProviderDao netrisProviderDao;
@Inject
ExtensionsManager extensionsManager;
private LockControllerListener _lockControllerListener;
@ -1070,8 +1060,6 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
private Map<String, String> _configs;
private Map<String, Boolean> _availableIdsMap;
private List<UserAuthenticator> _userAuthenticators;
private List<UserTwoFactorAuthenticator> _userTwoFactorAuthenticators;
private List<UserAuthenticator> _userPasswordEncoders;
@ -1155,12 +1143,6 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
_alertExecutor.scheduleAtFixedRate(new AlertPurgeTask(), alertPurgeInterval, alertPurgeInterval, TimeUnit.SECONDS);
}
final String[] availableIds = TimeZone.getAvailableIDs();
_availableIdsMap = new HashMap<>(availableIds.length);
for (final String id : availableIds) {
_availableIdsMap.put(id, true);
}
supportedHypervisors.add(HypervisorType.KVM);
supportedHypervisors.add(HypervisorType.XenServer);
@ -1221,15 +1203,6 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
throw new InvalidParameterValueException("privatePort is an invalid value");
}
// logger.debug("Checking if " + privateIp +
// " is a valid private IP address. Guest IP address is: " +
// _configs.get("guest.ip.network"));
//
// if (!NetUtils.isValidPrivateIp(privateIp,
// _configs.get("guest.ip.network"))) {
// throw new
// InvalidParameterValueException("Invalid private ip address");
// }
if (!NetUtils.isValidProto(proto)) {
throw new InvalidParameterValueException("Invalid protocol");
}
@ -1308,7 +1281,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
final Object name = cmd.getClusterName();
final Object podId = cmd.getPodId();
Long zoneId = cmd.getZoneId();
final Object hypervisorType = cmd.getHypervisorType();
final String hypervisorType = cmd.getHypervisorType();
final Object clusterType = cmd.getClusterType();
final Object allocationState = cmd.getAllocationState();
final String keyword = cmd.getKeyword();
@ -1353,8 +1326,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
}
if (hypervisorType != null) {
String hypervisorStr = (String) hypervisorType;
String hypervisorSearch = HypervisorType.getType(hypervisorStr).toString();
String hypervisorSearch = HypervisorType.getType(hypervisorType).toString();
sc.setParameters("hypervisorType", hypervisorSearch);
}
@ -1491,7 +1463,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
protected boolean zoneWideVolumeRequiresStorageMotion(PrimaryDataStore volumeDataStore,
final Host sourceHost, final Host destinationHost) {
if (volumeDataStore.isManaged() && sourceHost.getClusterId() != destinationHost.getClusterId()) {
if (volumeDataStore.isManaged() && !Objects.equals(sourceHost.getClusterId(), destinationHost.getClusterId())) {
PrimaryDataStoreDriver driver = (PrimaryDataStoreDriver)volumeDataStore.getDriver();
// Depends on the storage driver. For some storages simply
// changing volume access to host should work: grant access on destination
@ -1560,11 +1532,11 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
validateVgpuProfileForVmMigration(vmProfile);
final Type hostType = srcHost.getType();
Pair<List<HostVO>, Integer> allHostsPair = null;
List<HostVO> allHosts = null;
Pair<List<HostVO>, Integer> allHostsPair;
List<HostVO> allHosts;
List<HostVO> filteredHosts = null;
final Map<Host, Boolean> requiresStorageMotion = new HashMap<>();
DataCenterDeployment plan = null;
DataCenterDeployment plan;
if (canMigrateWithStorage) {
Long podId = !VirtualMachine.Type.User.equals(vm.getType()) ? srcHost.getPodId() : null;
allHostsPair = searchForServers(startIndex, pageSize, null, hostType, null, srcHost.getDataCenterId(), podId, null, null, keyword,
@ -1776,7 +1748,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
// Volume must be attached to an instance for live migration.
List<? extends StoragePool> allPools = new ArrayList<>();
List<? extends StoragePool> suitablePools = new ArrayList<>();
List<StoragePool> suitablePools = new ArrayList<>();
// Volume must be in Ready state to be migrated.
if (!Volume.State.Ready.equals(volume.getState())) {
@ -1839,7 +1811,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
suitablePools = findAllSuitableStoragePoolsForDetachedVolume(volume, diskOfferingId, allPools);
}
removeDataStoreClusterParents((List<StoragePool>) allPools);
removeDataStoreClusterParents((List<StoragePool>) suitablePools);
removeDataStoreClusterParents(suitablePools);
return new Pair<>(allPools, suitablePools);
}
@ -1922,7 +1894,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
* Looks for all suitable storage pools to allocate the given volume.
* We take into account the service offering of the VM and volume to find suitable storage pools. It is also excluded from the search the current storage pool used by the volume.
* We use {@link StoragePoolAllocator} to look for possible storage pools to allocate the given volume. We will look for possible local storage poosl even if the volume is using a shared storage disk offering.
*
* <p>
* Side note: the idea behind this method is to provide power for administrators of manually overriding deployments defined by CloudStack.
*/
private List<StoragePool> findAllSuitableStoragePoolsForVm(final VolumeVO volume, Long diskOfferingId, Long newSize, Long newMinIops, Long newMaxIops, VMInstanceVO vm, Host vmHost, ExcludeList avoid, Cluster srcCluster, HypervisorType hypervisorType, boolean bypassStorageTypeCheck, String keyword) {
@ -2004,7 +1976,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
sb.and("hypervisorVersion", sb.entity().getHypervisorVersion(), SearchCriteria.Op.GTEQ);
final String haTag = _haMgr.getHaTag();
SearchBuilder<HostTagVO> hostTagSearch = null;
SearchBuilder<HostTagVO> hostTagSearch;
if (haHosts != null && StringUtils.isNotEmpty(haTag)) {
hostTagSearch = _hostTagsDao.createSearchBuilder();
if ((Boolean)haHosts) {
@ -2468,7 +2440,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
}
boolean isAllocatedTemp = isAllocated;
VlanType vlanType = null;
VlanType vlanType;
if (forVirtualNetwork != null) {
vlanType = forVirtualNetwork ? VlanType.VirtualNetwork : VlanType.DirectAttached;
} else {
@ -2541,7 +2513,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
Boolean isRecursive = cmd.isRecursive();
final List<Long> permittedAccounts = new ArrayList<>();
ListProjectResourcesCriteria listProjectResourcesCriteria = null;
Boolean isAllocatedOrReserved = false;
boolean isAllocatedOrReserved = false;
if (isAllocated || IpAddress.State.Reserved.name().equalsIgnoreCase(state)) {
isAllocatedOrReserved = true;
}
@ -2590,8 +2562,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
if (zoneId == null) {
zoneId = guestNetwork.getDataCenterId();
} else if (zoneId != guestNetwork.getDataCenterId()) {
InvalidParameterValueException ex = new InvalidParameterValueException("Please specify a valid associated network id in the specified zone.");
throw ex;
throw new InvalidParameterValueException("Please specify a valid associated network id in the specified zone.");
}
owner = _accountDao.findById(guestNetwork.getAccountId());
}
@ -2633,7 +2604,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
}
}
if (freeAddrIds.size() > 0) {
if (!freeAddrIds.isEmpty()) {
final SearchBuilder<IPAddressVO> sb2 = _publicIpAddressDao.createSearchBuilder();
buildParameters(sb2, cmd, false);
sb2.and("ids", sb2.entity().getId(), SearchCriteria.Op.IN);
@ -2687,8 +2658,8 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
if (tags != null && !tags.isEmpty()) {
final SearchBuilder<ResourceTagVO> tagSearch = _resourceTagDao.createSearchBuilder();
for (int count = 0; count < tags.size(); count++) {
tagSearch.or().op("key" + String.valueOf(count), tagSearch.entity().getKey(), SearchCriteria.Op.EQ);
tagSearch.and("value" + String.valueOf(count), tagSearch.entity().getValue(), SearchCriteria.Op.EQ);
tagSearch.or().op("key" + count, tagSearch.entity().getKey(), SearchCriteria.Op.EQ);
tagSearch.and("value" + count, tagSearch.entity().getValue(), SearchCriteria.Op.EQ);
tagSearch.cp();
}
tagSearch.and("resourceType", tagSearch.entity().getResourceType(), SearchCriteria.Op.EQ);
@ -2717,7 +2688,6 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
final Object keyword = cmd.getKeyword();
final Long physicalNetworkId = cmd.getPhysicalNetworkId();
final Long sourceNetworkId = cmd.getNetworkId();
final Long vpcId = cmd.getVpcId();
Long zone = cmd.getZoneId();
final String address = cmd.getIpAddress();
final Long vlan = cmd.getVlanId();
@ -2736,8 +2706,8 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
int count = 0;
sc.setJoinParameters("tagSearch", "resourceType", ResourceObjectType.PublicIpAddress.toString());
for (final String key : tags.keySet()) {
sc.setJoinParameters("tagSearch", "key" + String.valueOf(count), key);
sc.setJoinParameters("tagSearch", "value" + String.valueOf(count), tags.get(key));
sc.setJoinParameters("tagSearch", "key" + count, key);
sc.setJoinParameters("tagSearch", "value" + count, tags.get(key));
count++;
}
}
@ -3018,7 +2988,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
if (duplicate != null) {
if (!cmd.isForced()) {
throw new InvalidParameterValueException(
"Mapping from hypervisor : " + hypervisorType.toString() + ", version : " + hypervisorVersion + " and guest OS : " + guestOs.getDisplayName() + " already exists!");
"Mapping from hypervisor : " + hypervisorType + ", version : " + hypervisorVersion + " and guest OS : " + guestOs.getDisplayName() + " already exists!");
}
if (Boolean.TRUE.equals(cmd.getOsMappingCheckEnabled())) {
@ -3110,7 +3080,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
logger.debug("GuestOSDetails");
final GuestOSVO guestOsVo = new GuestOSVO();
guestOsVo.setCategoryId(categoryId.longValue());
guestOsVo.setCategoryId(categoryId);
guestOsVo.setDisplayName(displayName);
guestOsVo.setName(name);
guestOsVo.setIsUserDefined(true);
@ -3123,8 +3093,8 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
}
private void persistGuestOsDetails(Map<String, String> details, long guestOsPersistedId) {
for (Object key : details.keySet()) {
_guestOsDetailsDao.addDetail(guestOsPersistedId, (String)key, details.get(key), false);
for (String key : details.keySet()) {
_guestOsDetailsDao.addDetail(guestOsPersistedId, key, details.get(key), false);
}
}
@ -3369,7 +3339,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
logger.trace("Trying to retrieve VNC port from agent about VM " + vm.getHostName());
}
GetVncPortAnswer answer = null;
GetVncPortAnswer answer;
if (vm.getState() == State.Migrating && vm.getLastHostId() != null) {
answer = (GetVncPortAnswer)_agentMgr.easySend(vm.getLastHostId(), new GetVncPortCommand(vm.getId(), vm.getInstanceName()));
} else {
@ -3543,9 +3513,9 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
});
Integer pageSize = null;
int pageSize;
try {
pageSize = Integer.valueOf(cmd.getPageSizeVal().toString());
pageSize = Integer.parseInt(cmd.getPageSizeVal().toString());
} catch (final IllegalArgumentException e) {
throw new InvalidParameterValueException("pageSize " + cmd.getPageSizeVal() + " is out of Integer range is not supported for this call");
}
@ -4324,7 +4294,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
final Calendar purgeCal = Calendar.getInstance();
purgeCal.add(Calendar.DAY_OF_YEAR, -_purgeDelay);
final Date purgeTime = purgeCal.getTime();
logger.debug("Deleting events older than: " + purgeTime.toString());
logger.debug("Deleting events older than: " + purgeTime);
final List<EventVO> oldEvents = _eventDao.listOlderEvents(purgeTime);
logger.debug("Found " + oldEvents.size() + " events to be purged");
for (final EventVO event : oldEvents) {
@ -4358,7 +4328,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
final Calendar purgeCal = Calendar.getInstance();
purgeCal.add(Calendar.DAY_OF_YEAR, -_alertPurgeDelay);
final Date purgeTime = purgeCal.getTime();
logger.debug("Deleting alerts older than: " + purgeTime.toString());
logger.debug("Deleting alerts older than: " + purgeTime);
final List<AlertVO> oldAlerts = _alertDao.listOlderAlerts(purgeTime);
logger.debug("Found " + oldAlerts.size() + " events to be purged");
for (final AlertVO alert : oldAlerts) {
@ -4677,8 +4647,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
// get the user obj to get their secret key
user = _accountMgr.getActiveUser(userId);
final String secretKey = user.getSecretKey();
final String input = cloudIdentifier;
signature = signRequest(input, secretKey);
signature = signRequest(cloudIdentifier, secretKey);
} catch (final Exception e) {
logger.warn("Exception whilst creating a signature:" + e);
}
@ -4697,8 +4666,8 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
final Account caller = getCaller();
final boolean isCallerAdmin = _accountService.isAdmin(caller.getId());
boolean securityGroupsEnabled = false;
boolean elasticLoadBalancerEnabled = false;
boolean KVMSnapshotEnabled = false;
boolean elasticLoadBalancerEnabled;
boolean KVMSnapshotEnabled;
String supportELB = "false";
final List<NetworkVO> networks = networkDao.listSecurityGroupEnabledNetworks();
if (networks != null && !networks.isEmpty()) {
@ -4737,7 +4706,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
// check if region-wide secondary storage is used
boolean regionSecondaryEnabled = false;
final List<ImageStoreVO> imgStores = _imgStoreDao.findRegionImageStores();
if (imgStores != null && imgStores.size() > 0) {
if (imgStores != null && !imgStores.isEmpty()) {
regionSecondaryEnabled = true;
}
@ -4780,6 +4749,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
if (isCallerAdmin) {
capabilities.put(ApiConstants.EXTENSIONS_PATH, extensionsManager.getExtensionsPath());
}
capabilities.put(ApiConstants.ADDITONAL_CONFIG_ENABLED, UserVmManager.EnableAdditionalVmConfig.valueIn(caller.getId()));
return capabilities;
}
@ -4801,7 +4771,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
final String groupName = cmd.getGroupName();
// Verify input parameters
final InstanceGroupVO group = _vmGroupDao.findById(groupId.longValue());
final InstanceGroupVO group = _vmGroupDao.findById(groupId);
if (group == null) {
final InvalidParameterValueException ex = new InvalidParameterValueException("unable to find a vm group with specified groupId");
ex.addProxyObject(groupId.toString(), "groupId");
@ -4828,7 +4798,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
public String getVersion() {
final Class<?> c = ManagementServer.class;
final String fullVersion = c.getPackage().getImplementationVersion();
if (fullVersion != null && fullVersion.length() > 0) {
if (fullVersion != null && !fullVersion.isEmpty()) {
return fullVersion;
}
@ -4884,7 +4854,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
}
} else {
try {
logger.debug(String.format("Trying to validate the root certificate format"));
logger.debug("Trying to validate the root certificate format");
CertificateHelper.buildCertificate(certificate);
} catch (CertificateException e) {
String errorMsg = String.format("Failed to pass certificate validation check with error: Certificate validation failed due to exception: %s", e.getMessage());
@ -4901,7 +4871,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
final String[] hypervisors = hypers.split(",");
if (zoneId != null) {
if (zoneId.longValue() == -1L) {
if (zoneId == -1L) {
final List<DataCenterVO> zones = _dcDao.listAll();
for (final String hypervisor : hypervisors) {
@ -5235,8 +5205,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
* @return
*/
private String getFingerprint(final String publicKey) {
final String fingerprint = SSHKeysHelper.getPublicKeyFingerprint(publicKey);
return fingerprint;
return SSHKeysHelper.getPublicKeyFingerprint(publicKey);
}
/**
@ -5260,8 +5229,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
protected Account getOwner(final RegisterSSHKeyPairCmd cmd) {
final Account caller = getCaller();
final Account owner = _accountMgr.finalizeOwner(caller, cmd.getAccountName(), cmd.getDomainId(), cmd.getProjectId());
return owner;
return _accountMgr.finalizeOwner(caller, cmd.getAccountName(), cmd.getDomainId(), cmd.getProjectId());
}
/**
@ -5277,8 +5245,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
* @return
*/
protected Account getCaller() {
final Account caller = CallContext.current().getCallingAccount();
return caller;
return CallContext.current().getCallingAccount();
}
private SSHKeyPair createAndSaveSSHKeyPair(final String name, final String fingerprint, final String publicKey, final String privateKey, final Account owner) {
@ -5448,9 +5415,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
eventTypes[i++] = field.get(eventObj).toString();
}
return eventTypes;
} catch (final IllegalArgumentException e) {
logger.error("Error while listing Event Types", e);
} catch (final IllegalAccessException e) {
} catch (final IllegalArgumentException | IllegalAccessException e) {
logger.error("Error while listing Event Types", e);
}
return null;
@ -5585,8 +5550,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
}
final boolean result = _userVmMgr.upgradeVirtualMachine(cmd.getId(), cmd.getServiceOfferingId(), cmd.getDetails());
if (result) {
final VirtualMachine vm = _vmInstanceDao.findById(cmd.getId());
return vm;
return _vmInstanceDao.findById(cmd.getId());
} else {
throw new CloudRuntimeException("Failed to upgrade System VM");
}
@ -5800,7 +5764,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
ManagementServerJoinVO managementServer = managementServerJoinDao.findById(id);
if (managementServer == null) {
throw new InvalidParameterValueException(String.format("Unable to find a Management Server with ID equal to [%s].", managementServer.getUuid()));
throw new InvalidParameterValueException(String.format("Unable to find a Management Server with ID equal to [%s].", id));
}
if (!ManagementServerHost.State.Down.equals(managementServer.getState())) {

View File

@ -2754,7 +2754,10 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
logger.debug("Creating user: " + userName + ", accountId: " + accountId + " timezone:" + timezone);
}
passwordPolicy.verifyIfPasswordCompliesWithPasswordPolicies(password, userName, getAccount(accountId).getDomainId());
Account callingAccount = getCurrentCallingAccount();
if (callingAccount.getId() != Account.ACCOUNT_ID_SYSTEM) {
passwordPolicy.verifyIfPasswordCompliesWithPasswordPolicies(password, userName, getAccount(accountId).getDomainId());
}
String encodedPassword = null;
for (UserAuthenticator authenticator : _userPasswordEncoders) {

View File

@ -83,6 +83,15 @@ public interface UserVmManager extends UserVmService {
"If set to true, tags specified in `resource.limit.host.tags` are also included in vm.strict.host.tags.",
true);
ConfigKey<Boolean> EnableAdditionalVmConfig = new ConfigKey<>(
"Advanced",
Boolean.class,
"enable.additional.vm.configuration",
"false",
"allow additional arbitrary configuration to vm",
true,
ConfigKey.Scope.Account);
static final int MAX_USER_DATA_LENGTH_BYTES = 2048;
public static final String CKS_NODE = "cksnode";

View File

@ -689,9 +689,6 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
private static final ConfigKey<Boolean> AllowDeployVmIfGivenHostFails = new ConfigKey<Boolean>("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<Boolean> EnableAdditionalVmConfig = new ConfigKey<>("Advanced", Boolean.class,
"enable.additional.vm.configuration", "false", "allow additional arbitrary configuration to vm", true, ConfigKey.Scope.Account);
private static final ConfigKey<String> KvmAdditionalConfigAllowList = new ConfigKey<>(String.class,
"allow.additional.vm.configuration.list.kvm", "Advanced", "", "Comma separated list of allowed additional configuration options.", true, ConfigKey.Scope.Account, null, null, EnableAdditionalVmConfig.key(), null, null, ConfigKey.Kind.CSV, null);
@ -775,7 +772,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
String networkCidr;
String macAddress;
public VmIpAddrFetchThread(long vmId, long nicId, String instanceName, boolean windows, Long hostId, String networkCidr, String macAddress) {
public VmIpAddrFetchThread(long vmId, String vmUuid, long nicId, String instanceName, boolean windows, Long hostId, String networkCidr, String macAddress) {
this.vmId = vmId;
this.vmUuid = vmUuid;
this.nicId = nicId;
@ -797,8 +794,13 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
Answer answer = _agentMgr.send(hostId, cmd);
if (answer.getResult()) {
String vmIp = answer.getDetails();
if (NetUtils.isValidIp4(vmIp)) {
if (vmIp == null) {
// we got a valid response and the NIC does not have an IP assigned, as such we will update the database with null
if (nic.getIPv4Address() != null) {
nic.setIPv4Address(null);
_nicDao.update(nicId, nic);
}
} else if (NetUtils.isValidIp4(vmIp)) {
// set this vm ip addr in vm nic.
if (nic != null) {
nic.setIPv4Address(vmIp);
@ -813,12 +815,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
}
}
} else {
//previously vm has ip and nic table has ip address. After vm restart or stop/start
//if vm doesnot get the ip then set the ip in nic table to null
if (nic.getIPv4Address() != null) {
nic.setIPv4Address(null);
_nicDao.update(nicId, nic);
}
// since no changes are being done, we should not decrement IP usage
decrementCount = false;
if (answer.getDetails() != null) {
logger.debug("Failed to get vm ip for Vm [id: {}, uuid: {}, name: {}], details: {}",
vmId, vmUuid, vmName, answer.getDetails());
@ -2726,7 +2724,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
VirtualMachineProfile vmProfile = new VirtualMachineProfileImpl(userVm);
VirtualMachine vm = vmProfile.getVirtualMachine();
boolean isWindows = _guestOSCategoryDao.findById(_guestOSDao.findById(vm.getGuestOSId()).getCategoryId()).getName().equalsIgnoreCase("Windows");
_vmIpFetchThreadExecutor.execute(new VmIpAddrFetchThread(vmId, nicId, vmInstance.getInstanceName(),
_vmIpFetchThreadExecutor.execute(new VmIpAddrFetchThread(vmId, vmInstance.getUuid(), nicId, vmInstance.getInstanceName(),
isWindows, vm.getHostId(), network.getCidr(), nicVo.getMacAddress()));
}
@ -6604,7 +6603,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
protected void persistExtraConfigVmware(String decodedUrl, UserVm vm) {
boolean isValidConfig = isValidKeyValuePair(decodedUrl);
if (isValidConfig) {
String[] extraConfigs = decodedUrl.split("\\r?\\n");
String[] extraConfigs = decodedUrl.split("\\r?\\n+");
for (String cfg : extraConfigs) {
// Validate cfg against unsupported operations set by admin here
String[] allowedKeyList = VmwareAdditionalConfigAllowList.value().split(",");
@ -6632,7 +6631,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
protected void persistExtraConfigXenServer(String decodedUrl, UserVm vm) {
boolean isValidConfig = isValidKeyValuePair(decodedUrl);
if (isValidConfig) {
String[] extraConfigs = decodedUrl.split("\\r?\\n");
String[] extraConfigs = decodedUrl.split("\\r?\\n+");
int i = 1;
String extraConfigKey = ApiConstants.EXTRA_CONFIG + "-";
for (String cfg : extraConfigs) {
@ -6712,8 +6711,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
// validate config against denied cfg commands
validateKvmExtraConfig(decodedUrl, vm.getAccountId());
String[] extraConfigs = decodedUrl.split("\n\n");
int i = 1;
for (String cfg : extraConfigs) {
int i = 1;
String[] cfgParts = cfg.split("\n");
String extraConfigKey = ApiConstants.EXTRA_CONFIG;
String extraConfigValue;

View File

@ -55,6 +55,17 @@ public interface UserPasswordResetManager {
"Use auth in the SMTP server for sending emails for resetting password for ACS users",
false, ConfigKey.Scope.Global);
ConfigKey<Boolean> UserPasswordResetSMTPUseStartTLS = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED,
Boolean.class, "user.password.reset.smtp.useStartTLS", "false",
"If set to true and if we enable security via user.password.reset.smtp.useAuth, this will enable StartTLS to secure the connection.",
true,
ConfigKey.Scope.Global);
ConfigKey<String> UserPasswordResetSMTPEnabledSecurityProtocols = new ConfigKey<String>(ConfigKey.CATEGORY_ADVANCED,
String.class, "user.password.reset.smtp.enabledSecurityProtocols", "",
"White-space separated security protocols; ex: \"TLSv1 TLSv1.1\". Supported protocols: SSLv2Hello, SSLv3, TLSv1, TLSv1.1 and TLSv1.2",
true, ConfigKey.Kind.WhitespaceSeparatedListWithOptions, "SSLv2Hello,SSLv3,TLSv1,TLSv1.1,TLSv1.2");
ConfigKey<String> UserPasswordResetSMTPUsername = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED,
String.class, "user.password.reset.smtp.username", null,
"Username for SMTP server for sending emails for resetting password for ACS users",

View File

@ -92,6 +92,8 @@ public class UserPasswordResetManagerImpl extends ManagerBase implements UserPas
UserPasswordResetSMTPHost,
UserPasswordResetSMTPPort,
UserPasswordResetSMTPUseAuth,
UserPasswordResetSMTPUseStartTLS,
UserPasswordResetSMTPEnabledSecurityProtocols,
UserPasswordResetSMTPUsername,
UserPasswordResetSMTPPassword,
UserPasswordResetDomainURL,
@ -106,6 +108,8 @@ public class UserPasswordResetManagerImpl extends ManagerBase implements UserPas
Boolean useAuth = UserPasswordResetSMTPUseAuth.value();
String username = UserPasswordResetSMTPUsername.value();
String password = UserPasswordResetSMTPPassword.value();
Boolean useStartTLS = UserPasswordResetSMTPUseStartTLS.value();
String enabledSecurityProtocols = UserPasswordResetSMTPEnabledSecurityProtocols.value();
if (!StringUtils.isEmpty(smtpHost) && smtpPort != null && smtpPort > 0) {
String namespace = "password.reset.smtp";
@ -117,6 +121,8 @@ public class UserPasswordResetManagerImpl extends ManagerBase implements UserPas
configs.put(getKey(namespace, SMTPMailSender.CONFIG_USE_AUTH), useAuth.toString());
configs.put(getKey(namespace, SMTPMailSender.CONFIG_USERNAME), username);
configs.put(getKey(namespace, SMTPMailSender.CONFIG_PASSWORD), password);
configs.put(getKey(namespace, SMTPMailSender.CONFIG_USE_STARTTLS), useStartTLS.toString());
configs.put(getKey(namespace, SMTPMailSender.CONFIG_ENABLED_SECURITY_PROTOCOLS), enabledSecurityProtocols);
mailSender = new SMTPMailSender(configs, namespace);
}

View File

@ -1051,6 +1051,8 @@
"label.externalid": "External Id",
"label.externalloadbalanceripaddress": "External load balancer IP address.",
"label.extra": "Extra arguments",
"label.extraconfig": "Additional Configuration",
"label.extraconfig.tooltip": "Additional configuration parameters (extraconfig) to pass to the instance in plain text",
"label.f5": "F5",
"label.f5.ip.loadbalancer": "F5 BIG-IP load balancer.",
"label.failed": "Failed",

View File

@ -762,6 +762,12 @@
</div>
</a-card>
</a-form-item>
<a-form-item v-if="extraConfigEnabled" name="extraconfig" ref="extraconfig">
<template #label>
<tooltip-label :title="$t('label.extraconfig')" :tooltip="$t('label.extraconfig.tooltip')"/>
</template>
<a-textarea v-model:value="form.extraconfig"/>
</a-form-item>
<a-form-item :label="$t('label.affinity.groups')">
<affinity-group-selection
:items="options.affinityGroups"
@ -1510,6 +1516,9 @@ export default {
dynamicScalingVmConfigValue () {
return this.$store.getters.features.dynamicscalingenabled
},
extraConfigEnabled () {
return this.$store.getters.features.additionalconfigenabled
},
isCustomizedDiskIOPS () {
return this.diskSelected?.iscustomizediops || false
},
@ -2323,6 +2332,9 @@ export default {
if (isUserdataAllowed && values.userdata && values.userdata.length > 0) {
deployVmData.userdata = this.$toBase64AndURIEncoded(values.userdata)
}
if (values.extraconfig && values.extraconfig.length > 0) {
deployVmData.extraconfig = encodeURIComponent(values.extraconfig)
}
// step 2: select template/iso
if (this.imageType === 'templateid') {
deployVmData.templateid = values.templateid

View File

@ -91,6 +91,12 @@
<a-textarea v-model:value="form.userdata">
</a-textarea>
</a-form-item>
<a-form-item v-if="extraConfigEnabled">
<template #label>
<tooltip-label :title="$t('label.extraconfig')" :tooltip="$t('label.extraconfig.tooltip')"/>
</template>
<a-textarea v-model:value="form.extraconfig"/>
</a-form-item>
<a-form-item ref="securitygroupids" name="securitygroupids" :label="$t('label.security.groups')" v-if="securityGroupsEnabled">
<a-select
mode="multiple"
@ -204,6 +210,19 @@ export default {
}
}
},
computed: {
extraConfigEnabled () {
return this.$store.getters.features.additionalconfigenabled
},
combinedExtraConfig () {
if (!this.extraConfigEnabled || !this.resource.details) return ''
const configs = Object.keys(this.resource.details)
.filter(key => key.startsWith('extraconfig-'))
.map(key => this.resource.details[key] || '')
.filter(val => val.trim())
return configs.join('\n\n')
}
},
beforeCreate () {
this.apiParams = this.$getApiParams('updateVirtualMachine')
},
@ -225,6 +244,7 @@ export default {
haenable: this.resource.haenable,
leaseduration: this.resource.leaseduration,
leaseexpiryaction: this.resource.leaseexpiryaction
extraconfig: this.combinedExtraConfig
})
this.rules = reactive({
leaseduration: [this.naturalNumberRule]
@ -403,6 +423,9 @@ export default {
params.leaseexpiryaction = values.leaseexpiryaction
}
}
if (values.extraconfig && values.extraconfig.length > 0) {
params.extraconfig = encodeURIComponent(values.extraconfig)
}
this.loading = true
postAPI('updateVirtualMachine', params).then(json => {