This is an automated email from the ASF dual-hosted git repository. sureshanaparti pushed a commit to branch 4.22 in repository https://gitbox.apache.org/repos/asf/cloudstack.git
commit b1f870ae833ed6a6bf32db622a2d0b5d2c8360a1 Merge: 036489b288c 8db7cab7ba4 Author: Suresh Kumar Anaparti <[email protected]> AuthorDate: Thu Jan 22 13:23:21 2026 +0530 Merge branch '4.20' into 4.22 .../org/apache/cloudstack/api/ApiConstants.java | 1 + .../wrapper/LibvirtMigrateCommandWrapper.java | 70 ++++++++++----- .../wrapper/LibvirtStartCommandWrapper.java | 5 +- .../wrapper/LibvirtMigrateCommandWrapperTest.java | 26 +++++- .../version/KubernetesVersionManagerImpl.java | 52 ++++++++++-- .../KubernetesSupportedVersionResponse.java | 12 +++ .../version/KubernetesVersionManagerImplTest.java | 78 ++++++++++++++++- .../version/KubernetesVersionServiceTest.java | 99 +++++++++++++++++----- .../java/com/cloud/storage/StorageManagerImpl.java | 8 +- .../cloud/storage/listener/StoragePoolMonitor.java | 28 ++++-- .../SecondaryStorageManagerImpl.java | 6 +- ui/public/locales/en.json | 1 + ui/public/locales/pt_BR.json | 1 + ui/src/config/section/image.js | 6 +- ui/src/views/auth/ForgotPassword.vue | 2 +- 15 files changed, 329 insertions(+), 66 deletions(-) diff --cc plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapper.java index 81328d6ffb9,fe18a88fe1f..43607edc53a --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapper.java @@@ -287,15 -273,12 +288,15 @@@ public final class LibvirtMigrateComman } catch (final LibvirtException e) { logger.info("Couldn't get VM domain state after " + sleeptime + "ms: " + e.getMessage()); } - if (state != null && state == DomainState.VIR_DOMAIN_RUNNING) { + if (state != null && (state == DomainState.VIR_DOMAIN_RUNNING || state == DomainState.VIR_DOMAIN_PAUSED)) { try { DomainJobInfo job = dm.getJobInfo(); - logger.info(String.format("Aborting migration of VM [%s] with domain job [%s] due to time out after %d seconds.", vmName, job, migrateWait)); + logger.warn("Aborting migration of VM {} with domain job [{}] due to timeout after {} seconds. " + + "Job stats: data processed={} bytes, data remaining={} bytes", vmName, job, migrateWait, job.getDataProcessed(), job.getDataRemaining()); dm.abortJob(); result = String.format("Migration of VM [%s] was cancelled by CloudStack due to time out after %d seconds.", vmName, migrateWait); + commandState = Command.State.FAILED; + libvirtComputingResource.createOrUpdateLogFileForCommand(command, commandState); logger.debug(result); break; } catch (final LibvirtException e) { diff --cc plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapperTest.java index 0407ed8bbfd,05d89cc3d97..aa36fb89765 --- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapperTest.java +++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapperTest.java @@@ -1089,153 -1020,27 +1089,177 @@@ public class LibvirtMigrateCommandWrapp Assert.assertTrue(finalXml.contains(newIsoVolumePath)); } + @Test + public void testMaskVncPwdDomain() { + // Test case 1: Single quotes + String xml1 = "<graphics type='vnc' port='5900' passwd='secret123'/>"; + String expected1 = "<graphics type='vnc' port='5900' passwd='*****'/>"; + assertEquals(expected1, LibvirtMigrateCommandWrapper.maskSensitiveInfoInXML(xml1)); + + // Test case 2: Double quotes + String xml2 = "<graphics type=\"vnc\" port=\"5901\" passwd=\"mypassword\"/>"; + String expected2 = "<graphics type=\"vnc\" port=\"5901\" passwd=\"*****\"/>"; + assertEquals(expected2, LibvirtMigrateCommandWrapper.maskSensitiveInfoInXML(xml2)); + + // Test case 3: Non-VNC graphics (should remain unchanged) + String xml3 = "<graphics type='spice' port='5902' passwd='notvnc'/>"; + assertEquals(xml3, LibvirtMigrateCommandWrapper.maskSensitiveInfoInXML(xml3)); + + // Test case 4: Multiple VNC entries in one string + String xml4 = "<graphics type='vnc' port='5900' passwd='a'/>\n" + + "<graphics type='vnc' port='5901' passwd='b'/>"; + String expected4 = "<graphics type='vnc' port='5900' passwd='*****'/>\n" + + "<graphics type='vnc' port='5901' passwd='*****'/>"; + assertEquals(expected4, LibvirtMigrateCommandWrapper.maskSensitiveInfoInXML(xml4)); + } ++ + @Test + public void updateGpuDevicesIfNeededTestNoGpuDevice() throws Exception { + Mockito.doReturn(virtualMachineTOMock).when(migrateCommandMock).getVirtualMachine(); + Mockito.doReturn(null).when(virtualMachineTOMock).getGpuDevice(); + + String result = libvirtMigrateCmdWrapper.updateGpuDevicesIfNeeded(migrateCommandMock, xmlWithoutGpuDevices, libvirtComputingResourceMock); + + Assert.assertEquals("XML should remain unchanged when no GPU device is present", xmlWithoutGpuDevices, result); + } + + @Test + public void updateGpuDevicesIfNeededTestNoDevicesSection() throws Exception { + List<VgpuTypesInfo> gpuDevices = createTestMixedGpuDevices(); + GPUDeviceTO gpuDeviceTO = Mockito.mock(GPUDeviceTO.class); + Mockito.doReturn(gpuDevices).when(gpuDeviceTO).getGpuDevices(); + + Mockito.doReturn(virtualMachineTOMock).when(migrateCommandMock).getVirtualMachine(); + Mockito.doReturn(gpuDeviceTO).when(virtualMachineTOMock).getGpuDevice(); + + String result = libvirtMigrateCmdWrapper.updateGpuDevicesIfNeeded(migrateCommandMock, xmlNoDevicesSection, libvirtComputingResourceMock); + + Assert.assertEquals("XML should remain unchanged when no devices section is found", xmlNoDevicesSection, result); + } + + @Test + public void updateGpuDevicesIfNeededTestWithPciDevice() throws Exception { + List<VgpuTypesInfo> gpuDevices = createTestPciGpuDevice(); + GPUDeviceTO gpuDeviceTO = Mockito.mock(GPUDeviceTO.class); + Mockito.doReturn(gpuDevices).when(gpuDeviceTO).getGpuDevices(); + + Mockito.doReturn(virtualMachineTOMock).when(migrateCommandMock).getVirtualMachine(); + Mockito.doReturn(gpuDeviceTO).when(virtualMachineTOMock).getGpuDevice(); + + String result = libvirtMigrateCmdWrapper.updateGpuDevicesIfNeeded(migrateCommandMock, xmlWithGpuDevices, libvirtComputingResourceMock); + + // Verify that old GPU devices are removed and new ones are added + Assert.assertFalse("Old PCI device should be removed", result.contains("bus='0x01' slot='0x00'")); + Assert.assertFalse("Old MDEV device should be removed", result.contains("4b20d080-1b54-4048-85b3-a6a62d165c01")); + Assert.assertTrue("New PCI device should be added", result.contains("bus=\"0x02\"")); + Assert.assertTrue("New PCI device should be added", result.contains("slot=\"0x00\"")); + Assert.assertTrue("PCI device should have vfio driver", result.contains("name=\"vfio\"")); + } + + @Test + public void updateGpuDevicesIfNeededTestWithMdevDevice() throws Exception { + List<VgpuTypesInfo> gpuDevices = createTestMdevGpuDevice(); + GPUDeviceTO gpuDeviceTO = Mockito.mock(GPUDeviceTO.class); + Mockito.doReturn(gpuDevices).when(gpuDeviceTO).getGpuDevices(); + + Mockito.doReturn(virtualMachineTOMock).when(migrateCommandMock).getVirtualMachine(); + Mockito.doReturn(gpuDeviceTO).when(virtualMachineTOMock).getGpuDevice(); + + String result = libvirtMigrateCmdWrapper.updateGpuDevicesIfNeeded(migrateCommandMock, xmlWithGpuDevices, libvirtComputingResourceMock); + + // Verify that old GPU devices are removed and new ones are added + Assert.assertFalse("Old PCI device should be removed", result.contains("bus='0x01' slot='0x00'")); + Assert.assertFalse("Old MDEV device should be removed", result.contains("4b20d080-1b54-4048-85b3-a6a62d165c01")); + Assert.assertTrue("New MDEV device should be added", result.contains("6f20d080-1b54-4048-85b3-a6a62d165c01")); + Assert.assertTrue("MDEV device should have display=off", result.contains("display=\"off\"")); + } + + @Test + public void updateGpuDevicesIfNeededTestWithMixedDevices() throws Exception { + List<VgpuTypesInfo> gpuDevices = createTestMixedGpuDevices(); + GPUDeviceTO gpuDeviceTO = Mockito.mock(GPUDeviceTO.class); + Mockito.doReturn(gpuDevices).when(gpuDeviceTO).getGpuDevices(); + + Mockito.doReturn(virtualMachineTOMock).when(migrateCommandMock).getVirtualMachine(); + Mockito.doReturn(gpuDeviceTO).when(virtualMachineTOMock).getGpuDevice(); + + String result = libvirtMigrateCmdWrapper.updateGpuDevicesIfNeeded(migrateCommandMock, xmlWithGpuDevices, libvirtComputingResourceMock); + + // Verify both PCI and MDEV devices are added + Assert.assertTrue("PCI device should be added", result.contains("bus=\"0x02\"")); + Assert.assertTrue("PCI device should be added", result.contains("slot=\"0x00\"")); + Assert.assertTrue("MDEV device should be added", result.contains("6f20d080-1b54-4048-85b3-a6a62d165c01")); + + // Count hostdev elements to ensure we have both + long hostdevCount = result.lines().filter(line -> line.contains("<hostdev")).count(); + Assert.assertEquals("Should have 2 hostdev elements", 2, hostdevCount); + } + + @Test + public void updateGpuDevicesIfNeededTestRemoveAllGpuDevices() throws Exception { + List<VgpuTypesInfo> gpuDevices = new ArrayList<>(); // Empty list + GPUDeviceTO gpuDeviceTO = Mockito.mock(GPUDeviceTO.class); + Mockito.doReturn(gpuDevices).when(gpuDeviceTO).getGpuDevices(); + + Mockito.doReturn(virtualMachineTOMock).when(migrateCommandMock).getVirtualMachine(); + Mockito.doReturn(gpuDeviceTO).when(virtualMachineTOMock).getGpuDevice(); + + String result = libvirtMigrateCmdWrapper.updateGpuDevicesIfNeeded(migrateCommandMock, xmlWithoutGpuDevices, libvirtComputingResourceMock); + + // Verify all GPU devices are removed + Assert.assertFalse("Old PCI device should be removed", result.contains("bus=\"0x01\"")); + Assert.assertFalse("Old PCI device should be removed", result.contains("slot=\"0x00\"")); + Assert.assertFalse("Old MDEV device should be removed", result.contains("4b20d080-1b54-4048-85b3-a6a62d165c01")); + + // Verify no hostdev elements remain + long hostdevCount = result.lines().filter(line -> line.contains("<hostdev")).count(); + Assert.assertEquals("Should have no hostdev elements", 0, hostdevCount); + } + + // Helper methods for creating test GPU devices + private List<VgpuTypesInfo> createTestPciGpuDevice() { + List<VgpuTypesInfo> devices = new ArrayList<>(); + VgpuTypesInfo pciDevice = new VgpuTypesInfo( + GpuDevice.DeviceType.PCI, + "NVIDIA Corporation Tesla T4", + "passthrough", + "02:00.0", // New bus address for destination host + "10de", + "NVIDIA Corporation", + "1eb8", + "Tesla T4" + ); + pciDevice.setDisplay(false); + devices.add(pciDevice); + return devices; + } + + private List<VgpuTypesInfo> createTestMdevGpuDevice() { + List<VgpuTypesInfo> devices = new ArrayList<>(); + VgpuTypesInfo mdevDevice = new VgpuTypesInfo( + GpuDevice.DeviceType.MDEV, + "nvidia-63", + "GRID T4-2Q", + "6f20d080-1b54-4048-85b3-a6a62d165c01", // New UUID for destination host + "10de", + "NVIDIA Corporation", + "1eb8", + "Tesla T4" + ); + mdevDevice.setDisplay(false); + devices.add(mdevDevice); + return devices; + } + + private List<VgpuTypesInfo> createTestMixedGpuDevices() { + List<VgpuTypesInfo> devices = new ArrayList<>(); + + // Add PCI device + devices.addAll(createTestPciGpuDevice()); + + // Add MDEV device + devices.addAll(createTestMdevGpuDevice()); + + return devices; + } } diff --cc plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/version/KubernetesVersionManagerImpl.java index 99f826402ce,8363f6f87e3..aef020335f2 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/version/KubernetesVersionManagerImpl.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/version/KubernetesVersionManagerImpl.java @@@ -347,8 -328,49 +359,34 @@@ public class KubernetesVersionManagerIm return createKubernetesSupportedVersionListResponse(versions, versionsAndCount.second()); } + private void validateImageStoreForZone(Long zoneId, boolean directDownload) { + if (directDownload) { + return; + } + if (zoneId != null) { + List<ImageStoreVO> imageStores = imageStoreDao.listStoresByZoneId(zoneId); + if (CollectionUtils.isEmpty(imageStores)) { + DataCenterVO zone = dataCenterDao.findById(zoneId); + String zoneName = zone != null ? zone.getName() : String.valueOf(zoneId); + throw new InvalidParameterValueException(String.format("Unable to register Kubernetes version ISO. No image store available in zone: %s", zoneName)); + } + } else { + List<DataCenterVO> zones = dataCenterDao.listAllZones(); + List<String> zonesWithoutStorage = new ArrayList<>(); + for (DataCenterVO zone : zones) { + List<ImageStoreVO> imageStores = imageStoreDao.listStoresByZoneId(zone.getId()); + if (CollectionUtils.isEmpty(imageStores)) { + zonesWithoutStorage.add(zone.getName()); + } + } + if (!zonesWithoutStorage.isEmpty()) { + throw new InvalidParameterValueException(String.format("Unable to register Kubernetes version ISO for all zones. The following zones have no image store: %s", String.join(", ", zonesWithoutStorage))); + } + } + } + - @Override - @ActionEvent(eventType = KubernetesVersionEventTypes.EVENT_KUBERNETES_VERSION_ADD, - eventDescription = "Adding Kubernetes supported version") - public KubernetesSupportedVersionResponse addKubernetesSupportedVersion(final AddKubernetesSupportedVersionCmd cmd) { - if (!KubernetesClusterService.KubernetesServiceEnabled.value()) { - throw new CloudRuntimeException("Kubernetes Service plugin is disabled"); - } - String name = cmd.getName(); - final String semanticVersion = cmd.getSemanticVersion(); - final Long zoneId = cmd.getZoneId(); - final String isoUrl = cmd.getUrl(); - final String isoChecksum = cmd.getChecksum(); - final Integer minimumCpu = cmd.getMinimumCpu(); - final Integer minimumRamSize = cmd.getMinimumRamSize(); - final boolean isDirectDownload = cmd.isDirectDownload(); - CPU.CPUArch arch = cmd.getArch(); - + private void validateKubernetesSupportedVersion(Long zoneId, String semanticVersion, Integer minimumCpu, + Integer minimumRamSize, boolean isDirectDownload) { if (minimumCpu == null || minimumCpu < KubernetesClusterService.MIN_KUBERNETES_CLUSTER_NODE_CPU) { throw new InvalidParameterValueException(String.format("Invalid value for %s parameter. Minimum %d vCPUs required.", ApiConstants.MIN_CPU_NUMBER, KubernetesClusterService.MIN_KUBERNETES_CLUSTER_NODE_CPU)); } @@@ -411,33 -414,9 +451,33 @@@ supportedVersionVO = kubernetesSupportedVersionDao.persist(supportedVersionVO); CallContext.current().putContextParameter(KubernetesSupportedVersion.class, supportedVersionVO.getUuid()); - return createKubernetesSupportedVersionResponse(supportedVersionVO); + return createKubernetesSupportedVersionResponse(supportedVersionVO, true); } + @Override + public GetUploadParamsResponse registerKubernetesSupportedVersionForPostUpload(GetUploadParamsForKubernetesSupportedVersionCmd cmd) { + if (!KubernetesClusterService.KubernetesServiceEnabled.value()) { + throw new CloudRuntimeException("Kubernetes Service plugin is disabled"); + } + String name = cmd.getName(); + final String semanticVersion = cmd.getSemanticVersion(); + final Long zoneId = cmd.getZoneId(); + final String isoChecksum = cmd.getChecksum(); + final Integer minimumCpu = cmd.getMinimumCpu(); + final Integer minimumRamSize = cmd.getMinimumRamSize(); + + validateKubernetesSupportedVersion(zoneId, semanticVersion, minimumCpu, minimumRamSize, false); + + GetUploadParamsResponse response = registerKubernetesVersionIsoForUpload(zoneId, name, isoChecksum); + + VMTemplateVO template = templateDao.findByUuid(response.getId().toString()); + KubernetesSupportedVersionVO supportedVersionVO = new KubernetesSupportedVersionVO(name, semanticVersion, template.getId(), zoneId, minimumCpu, minimumRamSize); + supportedVersionVO = kubernetesSupportedVersionDao.persist(supportedVersionVO); + CallContext.current().putContextParameter(KubernetesSupportedVersion.class, supportedVersionVO.getUuid()); + + return response; + } + @Override @ActionEvent(eventType = KubernetesVersionEventTypes.EVENT_KUBERNETES_VERSION_DELETE, eventDescription = "deleting Kubernetes supported version", async = true) diff --cc server/src/main/java/com/cloud/storage/listener/StoragePoolMonitor.java index b432858f2e0,6df3cbaeedf..2f9750edee2 --- a/server/src/main/java/com/cloud/storage/listener/StoragePoolMonitor.java +++ b/server/src/main/java/com/cloud/storage/listener/StoragePoolMonitor.java @@@ -21,12 -20,8 +21,13 @@@ import java.util.List import javax.inject.Inject; +import com.cloud.dc.dao.ClusterDao; +import com.cloud.dc.dao.HostPodDao; +import com.cloud.exception.StorageConflictException; import com.cloud.storage.StorageManager; +import com.cloud.storage.dao.StoragePoolHostDao; +import com.cloud.utils.exception.CloudRuntimeException; + import com.cloud.utils.Profiler; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProvider; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProviderManager; import org.apache.cloudstack.engine.subsystem.api.storage.HypervisorHostListener; diff --cc services/secondary-storage/controller/src/main/java/org/apache/cloudstack/secondarystorage/SecondaryStorageManagerImpl.java index 2ee1dcb37da,c9bcb911000..e26091f677e --- a/services/secondary-storage/controller/src/main/java/org/apache/cloudstack/secondarystorage/SecondaryStorageManagerImpl.java +++ b/services/secondary-storage/controller/src/main/java/org/apache/cloudstack/secondarystorage/SecondaryStorageManagerImpl.java @@@ -1229,22 -1224,11 +1229,24 @@@ public class SecondaryStorageManagerImp if (dc.getDns2() != null) { buf.append(" dns2=").append(dc.getDns2()); } - String nfsVersion = imageStoreDetailsUtil != null ? imageStoreDetailsUtil.getNfsVersion(secStores.get(0).getId()) : null; - buf.append(" nfsVersion=").append(nfsVersion); + String nfsVersion = imageStoreDetailsUtil.getNfsVersion(secStores.get(0).getId()); + if (StringUtils.isNotBlank(nfsVersion)) { + buf.append(" nfsVersion=").append(nfsVersion); + } buf.append(" keystore_password=").append(VirtualMachineGuru.getEncodedString(PasswordGenerator.generateRandomPassword(16))); + + if (SystemVmEnableUserData.valueIn(dc.getId())) { + String userDataUuid = SecondaryStorageVmUserData.valueIn(dc.getId()); + try { + String userData = userDataManager.validateAndGetUserDataForSystemVM(userDataUuid); + if (StringUtils.isNotBlank(userData)) { + buf.append(" userdata=").append(userData); + } + } catch (Exception e) { + logger.warn("Failed to load user data for the ssvm, ignored", e); + } + } + String bootArgs = buf.toString(); if (logger.isDebugEnabled()) { logger.debug(String.format("Boot args for machine profile [%s]: [%s].", profile.toString(), bootArgs)); diff --cc ui/src/config/section/image.js index 7172049d51e,3f8286c5fb1..d93e27c3f22 --- a/ui/src/config/section/image.js +++ b/ui/src/config/section/image.js @@@ -58,11 -58,11 +58,11 @@@ export default return fields }, details: () => { - var fields = ['name', 'id', 'displaytext', 'checksum', 'hypervisor', 'arch', 'format', 'ostypename', 'size', 'physicalsize', 'isready', 'passwordenabled', + var fields = ['name', 'id', 'displaytext', 'checksum', 'hypervisor', 'arch', 'format', 'externalprovisioner', 'ostypename', 'size', 'physicalsize', 'isready', 'passwordenabled', 'crossZones', 'templatetype', 'directdownload', 'deployasis', 'ispublic', 'isfeatured', 'isextractable', 'isdynamicallyscalable', 'crosszones', 'type', - 'account', 'domain', 'created', 'userdatadetails', 'userdatapolicy', 'forcks'] - 'account', 'domain', 'created', 'userdatadetails', 'userdatapolicy', 'url'] ++ 'account', 'domain', 'created', 'userdatadetails', 'userdatapolicy', 'url', 'forcks'] if (['Admin'].includes(store.getters.userInfo.roletype)) { - fields.push('templatetag', 'templatetype', 'url') + fields.push('templatetag', 'templatetype') } return fields }, diff --cc ui/src/views/auth/ForgotPassword.vue index 78af0c3d9f1,1e817e01a6e..a508934070a --- a/ui/src/views/auth/ForgotPassword.vue +++ b/ui/src/views/auth/ForgotPassword.vue @@@ -159,10 -159,10 +159,10 @@@ export default if (!loginParams.domain) { loginParams.domain = '/' } - api('forgotPassword', {}, 'POST', loginParams) + postAPI('forgotPassword', loginParams) .finally(() => { this.$message.success(this.$t('message.forgot.password.success')) - this.$router.push({ path: '/login' }).catch(() => {}) + this.$router.replace({ path: '/user/login' }) }) }).catch(error => { this.formRef.value.scrollToField(error.errorFields[0].name)
