This is an automated email from the ASF dual-hosted git repository. gutoveronezi pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/main by this push: new 28385be Fix metrics stats for VMs not running (#5633) 28385be is described below commit 28385be60998852f1419c5a77d3e0bba6e069807 Author: José Flauzino <jose.wilson...@gmail.com> AuthorDate: Mon Dec 6 11:06:10 2021 -0300 Fix metrics stats for VMs not running (#5633) * Fix metrics stats for VMs that are not running * Improves the way to get vmIdsToRemoveStats * Improves test Co-authored-by: José Flauzino <j...@scclouds.com.br> --- .../src/main/java/com/cloud/vm/dao/UserVmDao.java | 6 ++ .../main/java/com/cloud/vm/dao/UserVmDaoImpl.java | 31 +++++++++-- .../main/java/com/cloud/server/StatsCollector.java | 32 ++++++++++- .../main/java/com/cloud/vm/UserVmManagerImpl.java | 7 +++ .../java/com/cloud/server/StatsCollectorTest.java | 65 ++++++++++++++++++++++ .../AccountManagerImplVolumeDeleteEventTest.java | 4 ++ 6 files changed, 139 insertions(+), 6 deletions(-) diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/UserVmDao.java b/engine/schema/src/main/java/com/cloud/vm/dao/UserVmDao.java index 55d8fa0..c53936f 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/UserVmDao.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/UserVmDao.java @@ -61,6 +61,12 @@ public interface UserVmDao extends GenericDao<UserVmVO, Long> { public List<UserVmVO> listRunningByHostId(long hostId); /** + * List all running VMs. + * @return the list of all VM instances that are running. + */ + public List<UserVmVO> listAllRunning(); + + /** * List user vm instances with virtualized networking (i.e. not direct attached networking) for the given account and datacenter * @param accountId will search for vm instances belonging to this account * @return the list of vm instances owned by the account in the given data center that have virtualized networking (not direct attached networking) diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/UserVmDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/UserVmDaoImpl.java index a903c33..9cfa7a7 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/UserVmDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/UserVmDaoImpl.java @@ -67,6 +67,7 @@ public class UserVmDaoImpl extends GenericDaoBase<UserVmVO, Long> implements Use protected SearchBuilder<UserVmVO> LastHostSearch; protected SearchBuilder<UserVmVO> HostUpSearch; protected SearchBuilder<UserVmVO> HostRunningSearch; + protected SearchBuilder<UserVmVO> RunningSearch; protected SearchBuilder<UserVmVO> StateChangeSearch; protected SearchBuilder<UserVmVO> AccountHostSearch; @@ -134,9 +135,8 @@ public class UserVmDaoImpl extends GenericDaoBase<UserVmVO, Long> implements Use HostSearch.and("host", HostSearch.entity().getHostId(), SearchCriteria.Op.EQ); HostSearch.done(); - LastHostSearch = createSearchBuilder(); + LastHostSearch = createSearchBuilderWithStateCriteria(SearchCriteria.Op.EQ); LastHostSearch.and("lastHost", LastHostSearch.entity().getLastHostId(), SearchCriteria.Op.EQ); - LastHostSearch.and("state", LastHostSearch.entity().getState(), SearchCriteria.Op.EQ); LastHostSearch.done(); HostUpSearch = createSearchBuilder(); @@ -144,11 +144,13 @@ public class UserVmDaoImpl extends GenericDaoBase<UserVmVO, Long> implements Use HostUpSearch.and("states", HostUpSearch.entity().getState(), SearchCriteria.Op.NIN); HostUpSearch.done(); - HostRunningSearch = createSearchBuilder(); + HostRunningSearch = createSearchBuilderWithStateCriteria(SearchCriteria.Op.EQ); HostRunningSearch.and("host", HostRunningSearch.entity().getHostId(), SearchCriteria.Op.EQ); - HostRunningSearch.and("state", HostRunningSearch.entity().getState(), SearchCriteria.Op.EQ); HostRunningSearch.done(); + RunningSearch = createSearchBuilderWithStateCriteria(SearchCriteria.Op.EQ); + RunningSearch.done(); + AccountPodSearch = createSearchBuilder(); AccountPodSearch.and("account", AccountPodSearch.entity().getAccountId(), SearchCriteria.Op.EQ); AccountPodSearch.and("pod", AccountPodSearch.entity().getPodIdToDeployIn(), SearchCriteria.Op.EQ); @@ -209,6 +211,19 @@ public class UserVmDaoImpl extends GenericDaoBase<UserVmVO, Long> implements Use assert _updateTimeAttr != null : "Couldn't get this updateTime attribute"; } + /** + * Creates an {@link com.cloud.vm.UserVmVO UserVmVO} search builder with a + * {@link com.cloud.utils.db.SearchCriteria.Op SearchCriteria.Op} condition + * to the 'state' criteria already included. + * @param searchCriteria the {@link com.cloud.utils.db.SearchCriteria.Op SearchCriteria.Op} to 'state' criteria. + * @return the {@link com.cloud.vm.UserVmVO UserVmVO} search builder. + */ + protected SearchBuilder<UserVmVO> createSearchBuilderWithStateCriteria(SearchCriteria.Op searchCriteria) { + SearchBuilder<UserVmVO> genericSearchBuilderWithStateCriteria = createSearchBuilder(); + genericSearchBuilderWithStateCriteria.and("state", genericSearchBuilderWithStateCriteria.entity().getState(), searchCriteria); + return genericSearchBuilderWithStateCriteria; + } + @Override public List<UserVmVO> listByAccountAndPod(long accountId, long podId) { SearchCriteria<UserVmVO> sc = AccountPodSearch.create(); @@ -299,6 +314,14 @@ public class UserVmDaoImpl extends GenericDaoBase<UserVmVO, Long> implements Use } @Override + public List<UserVmVO> listAllRunning() { + SearchCriteria<UserVmVO> sc = RunningSearch.create(); + sc.setParameters("state", State.Running); + + return listBy(sc); + } + + @Override public List<UserVmVO> listVirtualNetworkInstancesByAcctAndNetwork(long accountId, long networkId) { SearchCriteria<UserVmVO> sc = AccountDataCenterVirtualSearch.create(); diff --git a/server/src/main/java/com/cloud/server/StatsCollector.java b/server/src/main/java/com/cloud/server/StatsCollector.java index c14f88c..9b193c0 100644 --- a/server/src/main/java/com/cloud/server/StatsCollector.java +++ b/server/src/main/java/com/cloud/server/StatsCollector.java @@ -238,7 +238,7 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc @Inject private ClusterDao _clusterDao; @Inject - private UserVmDao _userVmDao; + protected UserVmDao _userVmDao; @Inject private VolumeDao _volsDao; @Inject @@ -291,7 +291,7 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc private ImageStoreDetailsUtil imageStoreDetailsUtil; private ConcurrentHashMap<Long, HostStats> _hostStats = new ConcurrentHashMap<Long, HostStats>(); - private final ConcurrentHashMap<Long, VmStats> _VmStats = new ConcurrentHashMap<Long, VmStats>(); + protected ConcurrentHashMap<Long, VmStats> _VmStats = new ConcurrentHashMap<Long, VmStats>(); private final Map<String, VolumeStats> _volumeStats = new ConcurrentHashMap<String, VolumeStats>(); private ConcurrentHashMap<Long, StorageStats> _storageStats = new ConcurrentHashMap<Long, StorageStats>(); private ConcurrentHashMap<Long, StorageStats> _storagePoolStats = new ConcurrentHashMap<Long, StorageStats>(); @@ -619,6 +619,8 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc } } + cleanUpVirtualMachineStats(); + } catch (Throwable t) { s_logger.error("Error trying to retrieve VM stats", t); } @@ -1486,6 +1488,32 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc } /** + * Removes stats for a given virtual machine. + * @param vmId ID of the virtual machine to remove stats. + */ + public void removeVirtualMachineStats(Long vmId) { + s_logger.debug(String.format("Removing stats from VM with ID: %s .",vmId)); + _VmStats.remove(vmId); + } + + /** + * Removes stats of virtual machines that are not running from memory. + */ + protected void cleanUpVirtualMachineStats() { + List<Long> allRunningVmIds = new ArrayList<Long>(); + for (UserVmVO vm : _userVmDao.listAllRunning()) { + allRunningVmIds.add(vm.getId()); + } + + List<Long> vmIdsToRemoveStats = new ArrayList<Long>(_VmStats.keySet()); + vmIdsToRemoveStats.removeAll(allRunningVmIds); + + for (Long vmId : vmIdsToRemoveStats) { + removeVirtualMachineStats(vmId); + } + } + + /** * Sends host metrics to a configured InfluxDB host. The metrics respects the following specification.</br> * <b>Tags:</b>vm_id, uuid, instance_name, data_center_id, host_id</br> * <b>Fields:</b>memory_total_kb, memory_internal_free_kbs, memory_target_kbs, cpu_utilization, cpus, network_write_kb, disk_read_iops, disk_read_kbs, disk_write_iops, disk_write_kbs diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index da6b55b..07d1f2b 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -269,6 +269,7 @@ import com.cloud.resource.ResourceManager; import com.cloud.resource.ResourceState; import com.cloud.server.ManagementService; import com.cloud.server.ResourceTag; +import com.cloud.server.StatsCollector; import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.service.dao.ServiceOfferingDetailsDao; @@ -552,6 +553,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir @Autowired @Qualifier("networkHelper") protected NetworkHelper nwHelper; + @Inject + private StatsCollector statsCollector; private ScheduledExecutorService _executor = null; private ScheduledExecutorService _vmIpFetchExecutor = null; @@ -5001,6 +5004,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir throw new InvalidParameterValueException("unable to find a virtual machine with id " + vmId); } + statsCollector.removeVirtualMachineStats(vmId); + _userDao.findById(userId); boolean status = false; try { @@ -5306,6 +5311,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir return vm; } + statsCollector.removeVirtualMachineStats(vmId); + boolean status; State vmState = vm.getState(); diff --git a/server/src/test/java/com/cloud/server/StatsCollectorTest.java b/server/src/test/java/com/cloud/server/StatsCollectorTest.java index 70416e8..4ee2099 100644 --- a/server/src/test/java/com/cloud/server/StatsCollectorTest.java +++ b/server/src/test/java/com/cloud/server/StatsCollectorTest.java @@ -21,9 +21,11 @@ package com.cloud.server; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import org.influxdb.InfluxDB; @@ -34,6 +36,8 @@ import org.influxdb.dto.Point; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; import org.mockito.Mockito; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; @@ -44,6 +48,9 @@ import com.cloud.agent.api.VmDiskStatsEntry; import com.cloud.server.StatsCollector.ExternalStatsProtocol; import com.cloud.user.VmDiskStatisticsVO; import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.UserVmVO; +import com.cloud.vm.VmStats; +import com.cloud.vm.dao.UserVmDao; import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; @@ -51,6 +58,8 @@ import com.tngtech.java.junit.dataprovider.DataProviderRunner; @PowerMockRunnerDelegate(DataProviderRunner.class) @PrepareForTest({InfluxDBFactory.class, BatchPoints.class}) public class StatsCollectorTest { + + @InjectMocks private StatsCollector statsCollector = Mockito.spy(new StatsCollector()); private static final int GRAPHITE_DEFAULT_PORT = 2003; @@ -60,6 +69,18 @@ public class StatsCollectorTest { private static final String DEFAULT_DATABASE_NAME = "cloudstack"; + @Mock + ConcurrentHashMap<Long, VmStats> vmStatsMock; + + @Mock + VmStats singleVmStatsMock; + + @Mock + UserVmDao userVmDaoMock; + + @Mock + UserVmVO userVmVOMock; + @Test public void createInfluxDbConnectionTest() { configureAndTestCreateInfluxDbConnection(true); @@ -218,4 +239,48 @@ public class StatsCollectorTest { boolean result = statsCollector.areAllDiskStatsZero(vmDiskStatsEntry); Assert.assertEquals(expected, result); } + + @Test + public void removeVirtualMachineStatsTestRemoveOneVmStats() { + Mockito.doReturn(new Object()).when(vmStatsMock).remove(Mockito.anyLong()); + + statsCollector.removeVirtualMachineStats(1l); + + Mockito.verify(vmStatsMock, Mockito.times(1)).remove(Mockito.anyLong()); + } + + @Test + public void cleanUpVirtualMachineStatsTestDoNothing() { + Mockito.doReturn(new ArrayList<>()).when(userVmDaoMock).listAllRunning(); + Mockito.doReturn(new ConcurrentHashMap<Long, VmStats>(new HashMap<>()).keySet()) + .when(vmStatsMock).keySet(); + + statsCollector.cleanUpVirtualMachineStats(); + + Mockito.verify(statsCollector, Mockito.never()).removeVirtualMachineStats(Mockito.anyLong()); + } + + @Test + public void cleanUpVirtualMachineStatsTestRemoveOneVmStats() { + Mockito.doReturn(new ArrayList<>()).when(userVmDaoMock).listAllRunning(); + Mockito.doReturn(1l).when(userVmVOMock).getId(); + Mockito.doReturn(new ConcurrentHashMap<Long, VmStats>(Map.of(1l, singleVmStatsMock)).keySet()) + .when(vmStatsMock).keySet(); + + statsCollector.cleanUpVirtualMachineStats(); + + Mockito.verify(vmStatsMock, Mockito.times(1)).remove(Mockito.anyLong()); + } + + @Test + public void cleanUpVirtualMachineStatsTestRemoveOnlyOneVmStats() { + Mockito.doReturn(1l).when(userVmVOMock).getId(); + Mockito.doReturn(Arrays.asList(userVmVOMock)).when(userVmDaoMock).listAllRunning(); + Mockito.doReturn(new ConcurrentHashMap<Long, VmStats>(Map.of(1l, singleVmStatsMock, 2l, singleVmStatsMock)).keySet()) + .when(vmStatsMock).keySet(); + + statsCollector.cleanUpVirtualMachineStats(); + + Mockito.verify(vmStatsMock, Mockito.times(1)).remove(Mockito.anyLong()); + } } diff --git a/server/src/test/java/com/cloud/user/AccountManagerImplVolumeDeleteEventTest.java b/server/src/test/java/com/cloud/user/AccountManagerImplVolumeDeleteEventTest.java index ce0e796..37c48ad 100644 --- a/server/src/test/java/com/cloud/user/AccountManagerImplVolumeDeleteEventTest.java +++ b/server/src/test/java/com/cloud/user/AccountManagerImplVolumeDeleteEventTest.java @@ -54,6 +54,7 @@ import com.cloud.event.UsageEventVO; import com.cloud.exception.AgentUnavailableException; import com.cloud.exception.CloudException; import com.cloud.exception.ConcurrentOperationException; +import com.cloud.server.StatsCollector; import com.cloud.service.ServiceOfferingVO; import com.cloud.storage.Volume.Type; import com.cloud.storage.VolumeVO; @@ -75,6 +76,8 @@ public class AccountManagerImplVolumeDeleteEventTest extends AccountManagetImplT @Mock private UserVmManager userVmManager; + @Mock + private StatsCollector statsCollectorMock; Map<String, Object> oldFields = new HashMap<>(); UserVmVO vm = mock(UserVmVO.class); @@ -201,6 +204,7 @@ public class AccountManagerImplVolumeDeleteEventTest extends AccountManagetImplT // volume. public void runningVMRootVolumeUsageEvent() throws SecurityException, IllegalArgumentException, ReflectiveOperationException, AgentUnavailableException, ConcurrentOperationException, CloudException { + Mockito.doNothing().when(statsCollectorMock).removeVirtualMachineStats(Mockito.anyLong()); Mockito.lenient().when(_vmMgr.destroyVm(nullable(Long.class), nullable(Boolean.class))).thenReturn(vm); List<UsageEventVO> emittedEvents = deleteUserAccountRootVolumeUsageEvents(false); UsageEventVO event = emittedEvents.get(0);