From 5fb68f8c457533a48ce447911cfeb6868a6ded68 Mon Sep 17 00:00:00 2001 From: Abhisar Sinha <63767682+abh1sar@users.noreply.github.com> Date: Thu, 20 Aug 2026 16:26:13 +0530 Subject: [PATCH 1/3] fix NPE when accessing systemvm templates for unsupported hypervisors in deploySharedFSVM() --- .../main/java/com/cloud/resource/ResourceService.java | 2 +- .../lifecycle/StorageVmSharedFSLifeCycle.java | 11 ++++++++--- .../lifecycle/StorageVmSharedFSLifeCycleTest.java | 4 ++-- .../java/com/cloud/resource/ResourceManagerImpl.java | 4 ++-- .../com/cloud/resource/MockResourceManagerImpl.java | 2 +- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/api/src/main/java/com/cloud/resource/ResourceService.java b/api/src/main/java/com/cloud/resource/ResourceService.java index 3cdf8fc64e99..202d7fe63c49 100644 --- a/api/src/main/java/com/cloud/resource/ResourceService.java +++ b/api/src/main/java/com/cloud/resource/ResourceService.java @@ -91,7 +91,7 @@ public interface ResourceService { DataCenter getZone(Long zoneId); - List getSupportedHypervisorTypes(long zoneId, boolean forVirtualRouter, Long podId); + List getSupportedHypervisorTypes(long zoneId, boolean forSystemVm, Long podId); boolean releaseHostReservation(Long hostId); diff --git a/plugins/storage/sharedfs/storagevm/src/main/java/org/apache/cloudstack/storage/sharedfs/lifecycle/StorageVmSharedFSLifeCycle.java b/plugins/storage/sharedfs/storagevm/src/main/java/org/apache/cloudstack/storage/sharedfs/lifecycle/StorageVmSharedFSLifeCycle.java index ac8d6a58f0cb..59350ed5064a 100644 --- a/plugins/storage/sharedfs/storagevm/src/main/java/org/apache/cloudstack/storage/sharedfs/lifecycle/StorageVmSharedFSLifeCycle.java +++ b/plugins/storage/sharedfs/storagevm/src/main/java/org/apache/cloudstack/storage/sharedfs/lifecycle/StorageVmSharedFSLifeCycle.java @@ -160,7 +160,7 @@ private UserVm deploySharedFSVM(Long zoneId, Account owner, List networkId ServiceOffering serviceOffering = serviceOfferingDao.findById(serviceOfferingId); DataCenter zone = dataCenterDao.findById(zoneId); - List hypervisors = resourceMgr.getSupportedHypervisorTypes(zoneId, false, null); + List hypervisors = resourceMgr.getSupportedHypervisorTypes(zoneId, true, null); if (hypervisors.size() > 0) { Collections.shuffle(hypervisors); } else { @@ -180,8 +180,13 @@ private UserVm deploySharedFSVM(Long zoneId, Account owner, List networkId for (final Iterator iter = hypervisors.iterator(); iter.hasNext();) { final Hypervisor.HypervisorType hypervisor = iter.next(); VMTemplateVO template = templateDao.findSystemVMReadyTemplate(zoneId, hypervisor, preferredArchitecture); - if (template == null && !iter.hasNext()) { - throw new CloudRuntimeException(String.format("Unable to find the systemvm template for %s or it was not downloaded in %s.", hypervisor.toString(), zone.toString())); + if (template == null) { + if (iter.hasNext()) { + continue; + } else { + throw new CloudRuntimeException(String.format("Unable to find the systemvm template for any of the available hypervisors in the Zone: %s", zone.toString())); + } + } LaunchPermissionVO existingPermission = launchPermissionDao.findByTemplateAndAccount(template.getId(), owner.getId()); diff --git a/plugins/storage/sharedfs/storagevm/src/test/java/org/apache/cloudstack/storage/sharedfs/lifecycle/StorageVmSharedFSLifeCycleTest.java b/plugins/storage/sharedfs/storagevm/src/test/java/org/apache/cloudstack/storage/sharedfs/lifecycle/StorageVmSharedFSLifeCycleTest.java index c64e8c05c995..f8a02763e701 100644 --- a/plugins/storage/sharedfs/storagevm/src/test/java/org/apache/cloudstack/storage/sharedfs/lifecycle/StorageVmSharedFSLifeCycleTest.java +++ b/plugins/storage/sharedfs/storagevm/src/test/java/org/apache/cloudstack/storage/sharedfs/lifecycle/StorageVmSharedFSLifeCycleTest.java @@ -229,7 +229,7 @@ private SharedFS prepareDeploySharedFS() throws ResourceUnavailableException, In DataCenterVO zone = mock(DataCenterVO.class); when(dataCenterDao.findById(s_zoneId)).thenReturn(zone); - when(resourceMgr.getSupportedHypervisorTypes(s_zoneId, false, null)).thenReturn(List.of(Hypervisor.HypervisorType.KVM)); + when(resourceMgr.getSupportedHypervisorTypes(s_zoneId, true, null)).thenReturn(List.of(Hypervisor.HypervisorType.KVM)); ServiceOfferingVO serviceOffering = mock(ServiceOfferingVO.class); when(serviceOfferingDao.findById(s_serviceOfferingId)).thenReturn(serviceOffering); @@ -300,7 +300,7 @@ public void testDeploySharedFSTemplateNotFound() throws ResourceUnavailableExcep when(accountMgr.getActiveAccountById(s_ownerId)).thenReturn(null); DataCenterVO zone = mock(DataCenterVO.class); when(dataCenterDao.findById(s_zoneId)).thenReturn(zone); - when(resourceMgr.getSupportedHypervisorTypes(s_zoneId, false, null)).thenReturn(List.of(Hypervisor.HypervisorType.KVM)); + when(resourceMgr.getSupportedHypervisorTypes(s_zoneId, true, null)).thenReturn(List.of(Hypervisor.HypervisorType.KVM)); lifeCycle.deploySharedFS(sharedFS, s_networkId, s_diskOfferingId, s_size, s_minIops, s_maxIops); } diff --git a/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java b/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java index 95b508bca4fe..9e853fafff6b 100755 --- a/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java +++ b/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java @@ -2958,7 +2958,7 @@ public boolean configure(final String name, final Map params) th } @Override - public List getSupportedHypervisorTypes(final long zoneId, final boolean forVirtualRouter, final Long podId) { + public List getSupportedHypervisorTypes(final long zoneId, final boolean forSystemVm, final Long podId) { final List hypervisorTypes = new ArrayList<>(); List clustersForZone; @@ -2970,7 +2970,7 @@ public List getSupportedHypervisorTypes(final long zoneId, final for (final ClusterVO cluster : clustersForZone) { final HypervisorType hType = cluster.getHypervisorType(); - if (!forVirtualRouter || (hType != HypervisorType.BareMetal && hType != HypervisorType.External && hType != HypervisorType.Ovm)) { + if (!forSystemVm || (hType != HypervisorType.BareMetal && hType != HypervisorType.External && hType != HypervisorType.Ovm)) { hypervisorTypes.add(hType); } } diff --git a/server/src/test/java/com/cloud/resource/MockResourceManagerImpl.java b/server/src/test/java/com/cloud/resource/MockResourceManagerImpl.java index 8b62861165f9..01be6db73d88 100755 --- a/server/src/test/java/com/cloud/resource/MockResourceManagerImpl.java +++ b/server/src/test/java/com/cloud/resource/MockResourceManagerImpl.java @@ -205,7 +205,7 @@ public DataCenter getZone(Long zoneId) { * @see com.cloud.resource.ResourceService#getSupportedHypervisorTypes(long, boolean, java.lang.Long) */ @Override - public List getSupportedHypervisorTypes(final long zoneId, final boolean forVirtualRouter, final Long podId) { + public List getSupportedHypervisorTypes(final long zoneId, final boolean forSystemVm, final Long podId) { // TODO Auto-generated method stub return null; } From 099e74a9e8d39aea0d4b44085a0a8c43de471092 Mon Sep 17 00:00:00 2001 From: Abhisar Sinha <63767682+abh1sar@users.noreply.github.com> Date: Thu, 20 Aug 2026 16:34:52 +0530 Subject: [PATCH 2/3] systemvm -> SystemVM Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../storage/sharedfs/lifecycle/StorageVmSharedFSLifeCycle.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/storage/sharedfs/storagevm/src/main/java/org/apache/cloudstack/storage/sharedfs/lifecycle/StorageVmSharedFSLifeCycle.java b/plugins/storage/sharedfs/storagevm/src/main/java/org/apache/cloudstack/storage/sharedfs/lifecycle/StorageVmSharedFSLifeCycle.java index 59350ed5064a..ba010c5f60df 100644 --- a/plugins/storage/sharedfs/storagevm/src/main/java/org/apache/cloudstack/storage/sharedfs/lifecycle/StorageVmSharedFSLifeCycle.java +++ b/plugins/storage/sharedfs/storagevm/src/main/java/org/apache/cloudstack/storage/sharedfs/lifecycle/StorageVmSharedFSLifeCycle.java @@ -184,7 +184,7 @@ private UserVm deploySharedFSVM(Long zoneId, Account owner, List networkId if (iter.hasNext()) { continue; } else { - throw new CloudRuntimeException(String.format("Unable to find the systemvm template for any of the available hypervisors in the Zone: %s", zone.toString())); + throw new CloudRuntimeException(String.format("Unable to find the SystemVM template for any of the available hypervisors in zone %s.", zone.toString())); } } From ac688c7e48328458766eed0d02b651ee43be0f09 Mon Sep 17 00:00:00 2001 From: Abhisar Sinha <63767682+abh1sar@users.noreply.github.com> Date: Thu, 20 Aug 2026 16:45:53 +0530 Subject: [PATCH 3/3] Add unit test for skip-hypervisor-without-template fix in deploySharedFSVM() --- .../StorageVmSharedFSLifeCycleTest.java | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/plugins/storage/sharedfs/storagevm/src/test/java/org/apache/cloudstack/storage/sharedfs/lifecycle/StorageVmSharedFSLifeCycleTest.java b/plugins/storage/sharedfs/storagevm/src/test/java/org/apache/cloudstack/storage/sharedfs/lifecycle/StorageVmSharedFSLifeCycleTest.java index f8a02763e701..31d109e7d66d 100644 --- a/plugins/storage/sharedfs/storagevm/src/test/java/org/apache/cloudstack/storage/sharedfs/lifecycle/StorageVmSharedFSLifeCycleTest.java +++ b/plugins/storage/sharedfs/storagevm/src/test/java/org/apache/cloudstack/storage/sharedfs/lifecycle/StorageVmSharedFSLifeCycleTest.java @@ -53,6 +53,7 @@ import com.cloud.vm.dao.NicDao; import com.cloud.vm.dao.UserVmDao; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import java.util.Optional; import org.apache.cloudstack.api.ApiCommandResourceType; @@ -80,9 +81,12 @@ import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) @@ -305,6 +309,60 @@ public void testDeploySharedFSTemplateNotFound() throws ResourceUnavailableExcep lifeCycle.deploySharedFS(sharedFS, s_networkId, s_diskOfferingId, s_size, s_minIops, s_maxIops); } + @Test + public void testDeploySharedFSSkipsHypervisorWithoutTemplate() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException, IOException, OperationTimedoutException { + // Verifies that when the systemvm template is missing for one of the supported hypervisors, + // deploySharedFSVM() skips it and tries the next one instead of failing with an NPE. + SharedFS sharedFS = mock(SharedFS.class); + when(sharedFS.getDataCenterId()).thenReturn(s_zoneId); + when(sharedFS.getName()).thenReturn(s_name); + when(sharedFS.getServiceOfferingId()).thenReturn(s_serviceOfferingId); + when(sharedFS.getFsType()).thenReturn(SharedFS.FileSystemType.valueOf(s_fsFormat)); + when(sharedFS.getAccountId()).thenReturn(s_ownerId); + + Account owner = mock(Account.class); + when(owner.getId()).thenReturn(s_ownerId); + when(accountMgr.getActiveAccountById(s_ownerId)).thenReturn(owner); + + DataCenterVO zone = mock(DataCenterVO.class); + when(dataCenterDao.findById(s_zoneId)).thenReturn(zone); + + List hypervisors = new ArrayList<>(List.of(Hypervisor.HypervisorType.XenServer, Hypervisor.HypervisorType.KVM)); + when(resourceMgr.getSupportedHypervisorTypes(s_zoneId, true, null)).thenReturn(hypervisors); + + ServiceOfferingVO serviceOffering = mock(ServiceOfferingVO.class); + when(serviceOfferingDao.findById(s_serviceOfferingId)).thenReturn(serviceOffering); + + // The hypervisor list is shuffled before iteration, so instead of pinning which hypervisor is + // tried first, the first lookup (whichever hypervisor that is) returns no template and the + // second lookup returns a valid one, exercising the "skip and try the next hypervisor" fix. + VMTemplateVO template = mock(VMTemplateVO.class); + when(template.getId()).thenReturn(s_templateId); + when(templateDao.findSystemVMReadyTemplate(eq(s_zoneId), any(Hypervisor.HypervisorType.class), eq(ResourceManager.SystemVmPreferredArchitecture.defaultValue()))) + .thenReturn(null, template); + + UserVm vm = mock(UserVm.class); + when(vm.getId()).thenReturn(s_vmId); + when(userVmService.createAdvancedVirtualMachine( + any(DataCenter.class), any(ServiceOffering.class), any(VirtualMachineTemplate.class), anyList(), any(Account.class), anyString(), + anyString(), anyLong(), anyLong(), any(), isNull(), any(Hypervisor.HypervisorType.class), any(BaseCmd.HTTPMethod.class), anyString(), + isNull(), isNull(), anyList(), isNull(), any(Network.IpAddresses.class), isNull(), isNull(), isNull(), + anyMap(), isNull(), isNull(), isNull(), isNull(), + anyBoolean(), anyString(), isNull(), isNull(), isNull())).thenReturn(vm); + + VolumeVO dataVol = mock(VolumeVO.class); + when(dataVol.getId()).thenReturn(s_volumeId); + when(dataVol.getName()).thenReturn("DATA-1"); + when(dataVol.getVolumeType()).thenReturn(Volume.Type.DATADISK); + when(volumeDao.findByInstance(s_vmId)).thenReturn(List.of(dataVol)); + + Pair result = lifeCycle.deploySharedFS(sharedFS, s_networkId, s_diskOfferingId, s_size, s_minIops, s_maxIops); + Assert.assertEquals(Optional.of(s_volumeId), Optional.ofNullable(result.first())); + Assert.assertEquals(Optional.of(s_vmId), Optional.ofNullable(result.second())); + + verify(templateDao, times(2)).findSystemVMReadyTemplate(eq(s_zoneId), any(Hypervisor.HypervisorType.class), eq(ResourceManager.SystemVmPreferredArchitecture.defaultValue())); + } + @Test public void testDeleteSharedFS() throws ResourceUnavailableException { SharedFS sharedFS = mock(SharedFS.class);