This is an automated email from the ASF dual-hosted git repository. andrijapanicsb pushed a commit to branch fix/skip-stale-destroyed-volumes-local-storage-check in repository https://gitbox.apache.org/repos/asf/cloudstack.git
commit a4298134492cd10f4f29c5e1675cb2faa273e494 Author: Andrija Panic <[email protected]> AuthorDate: Thu May 7 17:47:04 2026 +0200 Skip stale destroyed volumes in local storage checks --- .../main/java/com/cloud/vm/UserVmManagerImpl.java | 20 ++++++++- .../java/com/cloud/vm/UserVmManagerImplTest.java | 52 ++++++++++++++++++++++ 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 60482c431eb..7364b7128c1 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -7660,11 +7660,27 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir protected boolean isAnyVmVolumeUsingLocalStorage(final List<VolumeVO> volumes) { for (VolumeVO vol : volumes) { + if (vol == null || vol.getRemoved() != null || + Volume.State.Destroy.equals(vol.getState()) || + Volume.State.Expunged.equals(vol.getState())) { + logger.debug("Skipping non-active volume while checking local storage usage: {}", vol); + continue; + } DiskOfferingVO diskOffering = _diskOfferingDao.findById(vol.getDiskOfferingId()); - if (diskOffering.isUseLocalStorage()) { + if (diskOffering != null && diskOffering.isUseLocalStorage()) { return true; } - StoragePoolVO storagePool = _storagePoolDao.findById(vol.getPoolId()); + Long poolId = vol.getPoolId(); + if (poolId == null) { + logger.debug("Skipping volume without storage pool while checking local storage usage: {}", vol); + continue; + } + StoragePoolVO storagePool = _storagePoolDao.findById(poolId); + if (storagePool == null || storagePool.getRemoved() != null) { + throw new CloudRuntimeException(String.format( + "Cannot determine local storage usage for active volume %s because storage pool ID %s is missing or removed", + vol, poolId)); + } if (storagePool.isLocal()) { return true; } diff --git a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java index ff67d73c0f4..954384c8c94 100644 --- a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java +++ b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java @@ -1264,6 +1264,58 @@ public class UserVmManagerImplTest { } } + @Test + public void testIsAnyVmVolumeUsingLocalStorageSkipsDestroyedVolumeWithMissingPool() { + VolumeVO volume = Mockito.mock(VolumeVO.class); + Mockito.when(volume.getState()).thenReturn(Volume.State.Destroy); + + Assert.assertFalse(userVmManagerImpl.isAnyVmVolumeUsingLocalStorage(Collections.singletonList(volume))); + Mockito.verify(primaryDataStoreDao, never()).findById(anyLong()); + } + + @Test + public void testIsAnyVmVolumeUsingLocalStorageSkipsRemovedVolume() { + VolumeVO volume = Mockito.mock(VolumeVO.class); + Mockito.when(volume.getRemoved()).thenReturn(new Date()); + + Assert.assertFalse(userVmManagerImpl.isAnyVmVolumeUsingLocalStorage(Collections.singletonList(volume))); + Mockito.verify(primaryDataStoreDao, never()).findById(anyLong()); + } + + @Test + public void testIsAnyVmVolumeUsingLocalStorageFailsForActiveVolumeWithMissingPool() { + VolumeVO volume = Mockito.mock(VolumeVO.class); + Mockito.when(volume.getState()).thenReturn(Volume.State.Ready); + Mockito.when(volume.getDiskOfferingId()).thenReturn(1L); + Mockito.when(volume.getPoolId()).thenReturn(2L); + DiskOfferingVO diskOffering = Mockito.mock(DiskOfferingVO.class); + Mockito.when(diskOfferingDao.findById(1L)).thenReturn(diskOffering); + Mockito.when(diskOffering.isUseLocalStorage()).thenReturn(false); + Mockito.when(primaryDataStoreDao.findById(2L)).thenReturn(null); + + CloudRuntimeException exception = assertThrows(CloudRuntimeException.class, () -> + userVmManagerImpl.isAnyVmVolumeUsingLocalStorage(Collections.singletonList(volume))); + Assert.assertTrue(exception.getMessage().contains("storage pool ID 2 is missing or removed")); + } + + @Test + public void testIsAnyVmVolumeUsingLocalStorageFailsForActiveVolumeWithRemovedPool() { + VolumeVO volume = Mockito.mock(VolumeVO.class); + Mockito.when(volume.getState()).thenReturn(Volume.State.Ready); + Mockito.when(volume.getDiskOfferingId()).thenReturn(1L); + Mockito.when(volume.getPoolId()).thenReturn(2L); + DiskOfferingVO diskOffering = Mockito.mock(DiskOfferingVO.class); + Mockito.when(diskOfferingDao.findById(1L)).thenReturn(diskOffering); + Mockito.when(diskOffering.isUseLocalStorage()).thenReturn(false); + StoragePoolVO storagePool = Mockito.mock(StoragePoolVO.class); + Mockito.when(storagePool.getRemoved()).thenReturn(new Date()); + Mockito.when(primaryDataStoreDao.findById(2L)).thenReturn(storagePool); + + CloudRuntimeException exception = assertThrows(CloudRuntimeException.class, () -> + userVmManagerImpl.isAnyVmVolumeUsingLocalStorage(Collections.singletonList(volume))); + Assert.assertTrue(exception.getMessage().contains("storage pool ID 2 is missing or removed")); + } + private List<VolumeVO> mockVolumesForIsAllVmVolumesOnZoneWideStore(int nullPoolIdVolumes, int nullPoolVolumes, int zoneVolumes, int nonZoneVolumes) { List<VolumeVO> volumes = new ArrayList<>(); for (int i=0; i< nullPoolIdVolumes + nullPoolVolumes + zoneVolumes + nonZoneVolumes; ++i) {
