From 30cb8c7a82005e334f939956d8db6546fa02b5c0 Mon Sep 17 00:00:00 2001 From: Nicolas Vazquez Date: Tue, 30 Sep 2025 04:01:07 -0300 Subject: [PATCH 1/9] Fix importing unmanaged instances due to incorrect internal name (#11753) --- .../java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java index 30cf4ad76a7..e5eb29a798d 100644 --- a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java @@ -1128,7 +1128,7 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { } String internalCSName = unmanagedInstance.getInternalCSName(); - if (StringUtils.isEmpty(instanceNameInternal)) { + if (StringUtils.isEmpty(internalCSName)) { internalCSName = instanceNameInternal; } Map allDetails = new HashMap<>(details); From 70af55e84897ab23b8f37f7ade04a520d99e1d8a Mon Sep 17 00:00:00 2001 From: Abhisar Sinha <63767682+abh1sar@users.noreply.github.com> Date: Tue, 30 Sep 2025 12:50:44 +0530 Subject: [PATCH 2/9] UI support for extraconfig in deploy and update instance (#11719) --- .../apache/cloudstack/api/ApiConstants.java | 1 + .../user/config/ListCapabilitiesCmd.java | 1 + .../api/response/CapabilitiesResponse.java | 8 ++++++ .../cloud/server/ManagementServerImpl.java | 2 ++ .../main/java/com/cloud/vm/UserVmManager.java | 9 +++++++ .../java/com/cloud/vm/UserVmManagerImpl.java | 9 +++---- ui/public/locales/en.json | 2 ++ ui/src/views/compute/DeployVM.vue | 12 +++++++++ ui/src/views/compute/EditVM.vue | 25 ++++++++++++++++++- 9 files changed, 62 insertions(+), 7 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index 89c9a194e3f..4abc0d13d74 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -26,6 +26,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"; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/config/ListCapabilitiesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/config/ListCapabilitiesCmd.java index bd3f39a09aa..7553ccffa7d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/config/ListCapabilitiesCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/config/ListCapabilitiesCmd.java @@ -73,6 +73,7 @@ public class ListCapabilitiesCmd extends BaseCmd { response.setSharedFsVmMinCpuCount((Integer)capabilities.get(ApiConstants.SHAREDFSVM_MIN_CPU_COUNT)); response.setSharedFsVmMinRamSize((Integer)capabilities.get(ApiConstants.SHAREDFSVM_MIN_RAM_SIZE)); 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); diff --git a/api/src/main/java/org/apache/cloudstack/api/response/CapabilitiesResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/CapabilitiesResponse.java index ff2e33b1389..affa130d4b0 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/CapabilitiesResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/CapabilitiesResponse.java @@ -140,6 +140,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; } @@ -255,4 +259,8 @@ public class CapabilitiesResponse extends BaseResponse { public void setDynamicScalingEnabled(Boolean dynamicScalingEnabled) { this.dynamicScalingEnabled = dynamicScalingEnabled; } + + public void setAdditionalConfigEnabled(Boolean additionalConfigEnabled) { + this.additionalConfigEnabled = additionalConfigEnabled; + } } diff --git a/server/src/main/java/com/cloud/server/ManagementServerImpl.java b/server/src/main/java/com/cloud/server/ManagementServerImpl.java index 271372bf656..56e8a56f2e2 100644 --- a/server/src/main/java/com/cloud/server/ManagementServerImpl.java +++ b/server/src/main/java/com/cloud/server/ManagementServerImpl.java @@ -4535,6 +4535,8 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe } capabilities.put(ApiConstants.SHAREDFSVM_MIN_CPU_COUNT, fsVmMinCpu); capabilities.put(ApiConstants.SHAREDFSVM_MIN_RAM_SIZE, fsVmMinRam); + capabilities.put(ApiConstants.ADDITONAL_CONFIG_ENABLED, UserVmManager.EnableAdditionalVmConfig.valueIn(caller.getId())); + return capabilities; } diff --git a/server/src/main/java/com/cloud/vm/UserVmManager.java b/server/src/main/java/com/cloud/vm/UserVmManager.java index f2a8a672d42..21ac6e3eb36 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManager.java +++ b/server/src/main/java/com/cloud/vm/UserVmManager.java @@ -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 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"; diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 5b3284c2c1e..a67484b6dd6 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -670,9 +670,6 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir private static final ConfigKey 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 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 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); @@ -6280,7 +6277,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(","); @@ -6308,7 +6305,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) { @@ -6388,8 +6385,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; diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 79ea9bb9d8f..d18c0945d3b 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -975,6 +975,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", diff --git a/ui/src/views/compute/DeployVM.vue b/ui/src/views/compute/DeployVM.vue index a604fe68fe4..82a54ed8314 100644 --- a/ui/src/views/compute/DeployVM.vue +++ b/ui/src/views/compute/DeployVM.vue @@ -724,6 +724,12 @@ + + + + 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.tabKey === 'templateid') { deployVmData.templateid = values.templateid diff --git a/ui/src/views/compute/EditVM.vue b/ui/src/views/compute/EditVM.vue index d5e75fcc658..9e60175f2a9 100644 --- a/ui/src/views/compute/EditVM.vue +++ b/ui/src/views/compute/EditVM.vue @@ -91,6 +91,12 @@ + + + + key.startsWith('extraconfig-')) + .map(key => this.resource.details[key] || '') + .filter(val => val.trim()) + return configs.join('\n\n') + } + }, beforeCreate () { this.apiParams = this.$getApiParams('updateVirtualMachine') }, @@ -185,7 +204,8 @@ export default { deleteprotection: this.resource.deleteprotection, group: this.resource.group, userdata: '', - haenable: this.resource.haenable + haenable: this.resource.haenable, + extraconfig: this.combinedExtraConfig }) this.rules = reactive({}) }, @@ -342,6 +362,9 @@ export default { if (values.userdata && values.userdata.length > 0) { params.userdata = this.$toBase64AndURIEncoded(values.userdata) } + if (values.extraconfig && values.extraconfig.length > 0) { + params.extraconfig = encodeURIComponent(values.extraconfig) + } this.loading = true api('updateVirtualMachine', {}, 'POST', params).then(json => { From c631d6a480dfc89475c14bead84ae309c78d3d81 Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Wed, 1 Oct 2025 08:47:58 +0200 Subject: [PATCH 3/9] CKS: generate a random UUID as password of CKS user in project (#11639) --- .../kubernetes/cluster/KubernetesClusterManagerImpl.java | 2 +- server/src/main/java/com/cloud/user/AccountManagerImpl.java | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java index 9b3e487680d..5a171296826 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java @@ -1551,7 +1551,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, diff --git a/server/src/main/java/com/cloud/user/AccountManagerImpl.java b/server/src/main/java/com/cloud/user/AccountManagerImpl.java index 04a64fbfc8c..2f6392ffaad 100644 --- a/server/src/main/java/com/cloud/user/AccountManagerImpl.java +++ b/server/src/main/java/com/cloud/user/AccountManagerImpl.java @@ -2747,7 +2747,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) { From ca7138b3bdf73dafce147d0a730d41db621a7e9b Mon Sep 17 00:00:00 2001 From: Manoj Kumar Date: Thu, 2 Oct 2025 11:43:48 +0530 Subject: [PATCH 4/9] server: Consider Instance in Starting state as well for allocation algorithm (#11751) * Consider Instance in Starting state as well for allocation algorithm * use IN instead of OR statement --- .../src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java index ef10af63bae..e6405fd34db 100755 --- a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java @@ -116,17 +116,17 @@ public class VMInstanceDaoImpl extends GenericDaoBase 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 "; From e12813de497e8e94907d602c30842b70841f3bb0 Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Fri, 3 Oct 2025 11:05:43 +0200 Subject: [PATCH 5/9] CKS: fix CKS creation on an existing Shared and Routed network (#11735) --- .../cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java index 5a171296826..3b6052fb1b9 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java @@ -381,6 +381,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()); From 8e4dc0a66d23e1d5d857cd7341003ac111e00a3a Mon Sep 17 00:00:00 2001 From: Alexandru Bagu Date: Sat, 4 Oct 2025 12:49:26 +0300 Subject: [PATCH 6/9] VMware: match nic mac for ip address fetch (#10641) --- .../vmware/resource/VmwareResource.java | 17 ++++++++++---- .../java/com/cloud/vm/UserVmManagerImpl.java | 22 ++++++++++--------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java index 481dbba922e..d8e7d170369 100644 --- a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java +++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java @@ -5837,11 +5837,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(); diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index a67484b6dd6..7cf722db797 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -753,7 +753,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; @@ -775,8 +775,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); @@ -791,12 +796,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()); @@ -2693,7 +2694,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())); } From a208db54ea84a8e3eda406990671c49efc90ef42 Mon Sep 17 00:00:00 2001 From: Rene Peinthor Date: Mon, 6 Oct 2025 09:10:53 +0200 Subject: [PATCH 7/9] linstor: use sparse/discard qemu-img convert on thin devices (#11787) --- plugins/storage/volume/linstor/CHANGELOG.md | 6 +++ ...torRevertBackupSnapshotCommandWrapper.java | 21 ++++++++-- .../kvm/storage/LinstorStorageAdaptor.java | 38 +------------------ .../storage/datastore/util/LinstorUtil.java | 34 +++++++++++++++++ 4 files changed, 59 insertions(+), 40 deletions(-) diff --git a/plugins/storage/volume/linstor/CHANGELOG.md b/plugins/storage/volume/linstor/CHANGELOG.md index c0991a9aa2b..7da3516955d 100644 --- a/plugins/storage/volume/linstor/CHANGELOG.md +++ b/plugins/storage/volume/linstor/CHANGELOG.md @@ -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 diff --git a/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LinstorRevertBackupSnapshotCommandWrapper.java b/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LinstorRevertBackupSnapshotCommandWrapper.java index 511b5a40ca8..98b8bf0bb78 100644 --- a/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LinstorRevertBackupSnapshotCommandWrapper.java +++ b/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LinstorRevertBackupSnapshotCommandWrapper.java @@ -26,13 +26,16 @@ 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.apache.log4j.Logger; +import org.joda.time.Duration; import org.libvirt.LibvirtException; @ResourceWrapper(handles = LinstorRevertBackupSnapshotCommand.class) @@ -41,12 +44,23 @@ public final class LinstorRevertBackupSnapshotCommandWrapper { private static final Logger s_logger = Logger.getLogger(LinstorRevertBackupSnapshotCommandWrapper.class); - 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); } @@ -73,8 +87,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(); diff --git a/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java b/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java index 4210008f1c0..c269878c808 100644 --- a/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java +++ b/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java @@ -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; @@ -56,7 +55,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; @@ -570,40 +568,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 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) { - s_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 @@ -674,7 +638,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); diff --git a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java index 60d06590006..9a6151efafc 100644 --- a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java +++ b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java @@ -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.log4j.Logger; @@ -430,4 +431,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 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) { + s_logger.error(apiExc.getMessage()); + } + return false; + } } From 963a67b81677fa85ef06dc7c6c2aaa165c85d9df Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Tue, 7 Oct 2025 06:49:57 +0200 Subject: [PATCH 8/9] server: add user.password.reset.smtp.useStartTLS and enabledSecurityProtocols for password reset (#11228) --- .../cloudstack/user/UserPasswordResetManager.java | 11 +++++++++++ .../cloudstack/user/UserPasswordResetManagerImpl.java | 6 ++++++ 2 files changed, 17 insertions(+) diff --git a/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManager.java b/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManager.java index a42faf2835a..929f11013b0 100644 --- a/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManager.java +++ b/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManager.java @@ -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 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 UserPasswordResetSMTPEnabledSecurityProtocols = new ConfigKey(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 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", diff --git a/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManagerImpl.java b/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManagerImpl.java index 6574489c827..798b6287e7e 100644 --- a/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManagerImpl.java @@ -93,6 +93,8 @@ public class UserPasswordResetManagerImpl extends ManagerBase implements UserPas UserPasswordResetSMTPHost, UserPasswordResetSMTPPort, UserPasswordResetSMTPUseAuth, + UserPasswordResetSMTPUseStartTLS, + UserPasswordResetSMTPEnabledSecurityProtocols, UserPasswordResetSMTPUsername, UserPasswordResetSMTPPassword, PasswordResetMailTemplate @@ -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); } From 823cb00a0a0923225597e3e7220ab0d67091a794 Mon Sep 17 00:00:00 2001 From: Suresh Kumar Anaparti Date: Tue, 7 Oct 2025 14:51:47 +0530 Subject: [PATCH 9/9] server: do not enable the disabled local storage(s) on host connection during mgmt server / agent start (#11722) --- .../storage/volume/datastore/PrimaryDataStoreHelper.java | 4 +++- .../lifecycle/CloudStackPrimaryDataStoreLifeCycleImpl.java | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/volume/datastore/PrimaryDataStoreHelper.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/volume/datastore/PrimaryDataStoreHelper.java index 7f28224a316..de87444ef44 100644 --- a/engine/storage/src/main/java/org/apache/cloudstack/storage/volume/datastore/PrimaryDataStoreHelper.java +++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/volume/datastore/PrimaryDataStoreHelper.java @@ -159,7 +159,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); diff --git a/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/CloudStackPrimaryDataStoreLifeCycleImpl.java b/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/CloudStackPrimaryDataStoreLifeCycleImpl.java index 351d59f6b03..e5ff0ab5873 100644 --- a/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/CloudStackPrimaryDataStoreLifeCycleImpl.java +++ b/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/CloudStackPrimaryDataStoreLifeCycleImpl.java @@ -500,7 +500,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) {