diff --git a/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java b/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java index 87059badbec5..63951dc5983f 100755 --- a/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java +++ b/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java @@ -2465,7 +2465,8 @@ protected void checkIfAllHostsInUse(List sagsToDelete, Long clusterId, L 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); @@ -2479,7 +2480,8 @@ protected void checkIfAllHostsInUse(List sagsToDelete, Long clusterId, L 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); @@ -2493,7 +2495,8 @@ protected void checkIfAllHostsInUse(List sagsToDelete, Long clusterId, L 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 30a021591a5d..d644b036949a 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 void executeUserRequestDefaultOverloadPassesFalseForDeleteHost() throws E 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); + } }