diff --git a/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java b/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java index d724e5704fb..32a519c79fe 100755 --- a/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java +++ b/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java @@ -2511,7 +2511,8 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager, List hostsInZone = _hostDao.findByDataCenterId(zoneId); Set hostIdsInUseSet = hostIdsUsingStorageAccessGroups.stream().collect(Collectors.toSet()); - boolean allInUseZone = hostsInZone.stream() + // allMatch returns true on empty stream, need to check whether collection is not empty first + boolean allInUseZone = !hostsInZone.isEmpty() && hostsInZone.stream() .map(HostVO::getId) .allMatch(hostIdsInUseSet::contains); @@ -2525,7 +2526,8 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager, List hostsInCluster = _hostDao.findByClusterId(clusterId, Type.Routing); Set hostIdsInUseSet = hostIdsUsingStorageAccessGroups.stream().collect(Collectors.toSet()); - boolean allInUseCluster = hostsInCluster.stream() + // allMatch returns true on empty stream, need to check whether collection is not empty first + boolean allInUseCluster = !hostsInCluster.isEmpty() && hostsInCluster.stream() .map(HostVO::getId) .allMatch(hostIdsInUseSet::contains); @@ -2539,7 +2541,8 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager, List hostsInPod = _hostDao.findByPodId(podId, Type.Routing); Set hostIdsInUseSet = hostIdsUsingStorageAccessGroups.stream().collect(Collectors.toSet()); - boolean allInUsePod = hostsInPod.stream() + // allMatch returns true on empty stream, need to check whether collection is not empty first + boolean allInUsePod = !hostsInPod.isEmpty() && hostsInPod.stream() .map(HostVO::getId) .allMatch(hostIdsInUseSet::contains); diff --git a/server/src/test/java/com/cloud/resource/ResourceManagerImplTest.java b/server/src/test/java/com/cloud/resource/ResourceManagerImplTest.java index 30a021591a5..d644b036949 100644 --- a/server/src/test/java/com/cloud/resource/ResourceManagerImplTest.java +++ b/server/src/test/java/com/cloud/resource/ResourceManagerImplTest.java @@ -1211,4 +1211,96 @@ public class ResourceManagerImplTest { Mockito.verify(resourceManager).doDeleteHost(hostId, false, false); } + + @Test + public void testUpdateClusterStorageAccessGroupsWithEmptyHostsInCluster() { + Long clusterId = 1L; + List newStorageAccessGroups = Arrays.asList("sag1", "sag2"); + + ClusterVO cluster = Mockito.mock(ClusterVO.class); + Mockito.when(cluster.getId()).thenReturn(clusterId); + Mockito.when(cluster.getStorageAccessGroups()).thenReturn("sag3,sag4"); // existing SAGs + Mockito.when(resourceManager.getCluster(clusterId)).thenReturn(cluster); + List emptyHostsList = new ArrayList<>(); + Mockito.when(hostDao.findHypervisorHostInCluster(clusterId)).thenReturn(emptyHostsList); + Mockito.when(hostDao.findByClusterId(clusterId, Host.Type.Routing)).thenReturn(emptyHostsList); + List emptyHostIdsList = new ArrayList<>(); + Mockito.doReturn(emptyHostIdsList).when(resourceManager) + .listOfHostIdsUsingTheStorageAccessGroups(Mockito.anyList(), eq(clusterId), eq(null), eq(null)); + try { + resourceManager.updateClusterStorageAccessGroups(clusterId, newStorageAccessGroups); + } catch (CloudRuntimeException e) { + Assert.fail("updateClusterStorageAccessGroups should not throw CloudRuntimeException when cluster has no hosts. Error: " + e.getMessage()); + } + Mockito.verify(resourceManager).checkIfAllHostsInUse(Mockito.anyList(), eq(clusterId), eq(null), eq(null)); + } + + @Test + public void testUpdateClusterStorageAccessGroupsWithEmptyHostsInZone() { + List sagsToDelete = Arrays.asList("tag1", "tag2"); + Long clusterId = null; + Long podId = null; + Long zoneId = 3L; + + List emptyHostIdsList = new ArrayList<>(); + Mockito.doReturn(emptyHostIdsList).when(resourceManager) + .listOfHostIdsUsingTheStorageAccessGroups(sagsToDelete, clusterId, podId, zoneId); + List emptyHostsInZone = new ArrayList<>(); + Mockito.doReturn(emptyHostsInZone).when(hostDao).findByDataCenterId(zoneId); + + try { + resourceManager.checkIfAllHostsInUse(sagsToDelete, clusterId, podId, zoneId); + } catch (CloudRuntimeException e) { + Assert.fail("checkIfAllHostsInUse should not throw CloudRuntimeException when zone has no hosts. Error: " + e.getMessage()); + } + Mockito.verify(resourceManager).checkIfAllHostsInUse(Mockito.anyList(), eq(null), eq(null), eq(zoneId)); + } + + @Test + public void testUpdateClusterStorageAccessGroupsWithEmptyHostsInPod() { + List sagsToDelete = Arrays.asList("tag1", "tag2"); + Long clusterId = null; + Long podId = 2L; + Long zoneId = null; + + List emptyHostIdsList = new ArrayList<>(); + Mockito.doReturn(emptyHostIdsList).when(resourceManager) + .listOfHostIdsUsingTheStorageAccessGroups(sagsToDelete, clusterId, podId, zoneId); + List emptyHostsInPod = new ArrayList<>(); + Mockito.doReturn(emptyHostsInPod).when(hostDao).findByPodId(podId, Host.Type.Routing); + + try { + resourceManager.checkIfAllHostsInUse(sagsToDelete, clusterId, podId, zoneId); + } catch (CloudRuntimeException e) { + Assert.fail("checkIfAllHostsInUse should not throw CloudRuntimeException when pod has no hosts. Error: " + e.getMessage()); + } + Mockito.verify(resourceManager).checkIfAllHostsInUse(Mockito.anyList(), eq(null), eq(podId), eq(null)); + } + + @Test + public void testCheckIfAllHostsInUseWithEmptyHostsInMultipleLevels() { + List sagsToDelete = Arrays.asList("tag1", "tag2"); + Long clusterId = 1L; + Long podId = 2L; + Long zoneId = 3L; + + List emptyHostIdsList = new ArrayList<>(); + Mockito.doReturn(emptyHostIdsList).when(resourceManager) + .listOfHostIdsUsingTheStorageAccessGroups(sagsToDelete, clusterId, podId, zoneId); + List emptyHostsInZone = new ArrayList<>(); + List emptyHostsInCluster = new ArrayList<>(); + List emptyHostsInPod = new ArrayList<>(); + Mockito.doReturn(emptyHostsInZone).when(hostDao).findByDataCenterId(zoneId); + Mockito.doReturn(emptyHostsInCluster).when(hostDao).findByClusterId(clusterId, Host.Type.Routing); + Mockito.doReturn(emptyHostsInPod).when(hostDao).findByPodId(podId, Host.Type.Routing); + + try { + resourceManager.checkIfAllHostsInUse(sagsToDelete, clusterId, podId, zoneId); + } catch (CloudRuntimeException e) { + Assert.fail("checkIfAllHostsInUse should not throw CloudRuntimeException when all levels have no hosts. Error: " + e.getMessage()); + } + Mockito.verify(hostDao).findByDataCenterId(zoneId); + Mockito.verify(hostDao).findByClusterId(clusterId, Host.Type.Routing); + Mockito.verify(hostDao).findByPodId(podId, Host.Type.Routing); + } } diff --git a/ui/src/views/compute/DeployVnfAppliance.vue b/ui/src/views/compute/DeployVnfAppliance.vue index be154221675..05f804ef4ea 100644 --- a/ui/src/views/compute/DeployVnfAppliance.vue +++ b/ui/src/views/compute/DeployVnfAppliance.vue @@ -272,7 +272,7 @@