Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions engine/schema/src/main/java/com/cloud/vm/dao/UserVmDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,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
Expand Down
31 changes: 27 additions & 4 deletions engine/schema/src/main/java/com/cloud/vm/dao/UserVmDaoImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -134,21 +135,22 @@ void init() {
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();
HostUpSearch.and("host", HostUpSearch.entity().getHostId(), SearchCriteria.Op.EQ);
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);
Expand Down Expand Up @@ -209,6 +211,19 @@ void init() {
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();
Expand Down Expand Up @@ -298,6 +313,14 @@ public List<UserVmVO> listRunningByHostId(long hostId) {
return listBy(sc);
}

@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) {

Expand Down
32 changes: 30 additions & 2 deletions server/src/main/java/com/cloud/server/StatsCollector.java
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ public String toString() {
@Inject
private ClusterDao _clusterDao;
@Inject
private UserVmDao _userVmDao;
protected UserVmDao _userVmDao;
@Inject
private VolumeDao _volsDao;
@Inject
Expand Down Expand Up @@ -291,7 +291,7 @@ public String toString() {
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>();
Expand Down Expand Up @@ -619,6 +619,8 @@ protected void runInContext() {
}
}

cleanUpVirtualMachineStats();

} catch (Throwable t) {
s_logger.error("Error trying to retrieve VM stats", t);
}
Expand Down Expand Up @@ -1485,6 +1487,32 @@ private void storeVirtualMachineStatsInMemory(VmStatsEntry statsForCurrentIterat
}
}

/**
* 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()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VM stats are captured for running VMs only

List<UserVmVO> vms = _userVmDao.listRunningByHostId(host.getId());

removeVirtualMachineStats() removes the vm stat if the VM is stopped/destroyed after adding to the stats, so no need for this cleanup method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review @sureshanaparti!
The cleanUpVirtualMachineStats() method is important to maintain consistency of the metric stats shown by ACS even when there are cases of VMs changing state unexpectedly (e.g., when a VM crashes; or when the VM is stopped directly by the hypervisor interface, etc). In these cases the removeVirtualMachineStats() method is not executed, so the cleanUpVirtualMachineStats() method does its job.

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>
Expand Down
7 changes: 7 additions & 0 deletions server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@
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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -5001,6 +5004,8 @@ public UserVm stopVirtualMachine(long vmId, boolean forced) throws ConcurrentOpe
throw new InvalidParameterValueException("unable to find a virtual machine with id " + vmId);
}

statsCollector.removeVirtualMachineStats(vmId);

_userDao.findById(userId);
boolean status = false;
try {
Expand Down Expand Up @@ -5306,6 +5311,8 @@ public UserVm destroyVm(long vmId, boolean expunge) throws ResourceUnavailableEx
return vm;
}

statsCollector.removeVirtualMachineStats(vmId);

boolean status;
State vmState = vm.getState();

Expand Down
65 changes: 65 additions & 0 deletions server/src/test/java/com/cloud/server/StatsCollectorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@
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;
Expand All @@ -34,6 +36,8 @@
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;
Expand All @@ -44,13 +48,18 @@
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;

@RunWith(PowerMockRunner.class)
@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;
Expand All @@ -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);
Expand Down Expand Up @@ -218,4 +239,48 @@ public void configureAndTestCheckIfDiskStatsAreZero(long bytesRead, long bytesWr
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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
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;
Expand All @@ -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);
Expand Down Expand Up @@ -201,6 +204,7 @@ public void destroyedVMRootVolumeUsageEvent()
// 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);
Expand Down