This is an automated email from the ASF dual-hosted git repository. weizhouapache pushed a commit to branch 4.22 in repository https://gitbox.apache.org/repos/asf/cloudstack.git
commit af8691c6a95ffa67e7da37f4aa9b8c7da1bf83db Author: Wei Zhou <[email protected]> AuthorDate: Thu Sep 10 11:21:27 2026 +0200 Routed: get vm network statistics on Routed network (#13105) * Routed: get vm network statistics on Routed network * Potential fix for pull request finding * Create method isNetworkEligibleForNetworkStats and add unit tests --- .../main/java/com/cloud/server/StatsCollector.java | 39 ++++++++-- .../java/com/cloud/server/StatsCollectorTest.java | 88 ++++++++++++++++++++++ 2 files changed, 122 insertions(+), 5 deletions(-) diff --git a/server/src/main/java/com/cloud/server/StatsCollector.java b/server/src/main/java/com/cloud/server/StatsCollector.java index 917e05a3c98..3eec27d4f54 100644 --- a/server/src/main/java/com/cloud/server/StatsCollector.java +++ b/server/src/main/java/com/cloud/server/StatsCollector.java @@ -57,6 +57,7 @@ import org.apache.cloudstack.framework.config.Configurable; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.managed.context.ManagedContextRunnable; import org.apache.cloudstack.management.ManagementServerHost; +import org.apache.cloudstack.network.RoutedIpv4Manager; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.cloudstack.utils.bytescale.ByteScaleUtils; @@ -113,6 +114,8 @@ import com.cloud.host.dao.HostDao; import com.cloud.hypervisor.Hypervisor; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.network.as.AutoScaleManager; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkVO; import com.cloud.org.Cluster; import com.cloud.resource.ResourceManager; import com.cloud.resource.ResourceState; @@ -203,6 +206,11 @@ import com.sun.management.OperatingSystemMXBean; @Component public class StatsCollector extends ManagerBase implements ComponentMethodInterceptable, Configurable, DbStatsCollection { + @Inject + private NetworkDao networkDao; + @Inject + private RoutedIpv4Manager routedIpv4Manager; + public static enum ExternalStatsProtocol { NONE("none"), GRAPHITE("graphite"), INFLUXDB("influxdb"); String _type; @@ -268,9 +276,9 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc private static final ConfigKey<Integer> vmDiskStatsIntervalMin = new ConfigKey<>("Advanced", Integer.class, "vm.disk.stats.interval.min", "300", "Minimal interval (in seconds) to report vm disk statistics. If vm.disk.stats.interval is smaller than this, use this to report vm disk statistics.", false); private static final ConfigKey<Integer> vmNetworkStatsInterval = new ConfigKey<>("Advanced", Integer.class, "vm.network.stats.interval", "0", - "Interval (in seconds) to report vm network statistics (for Shared networks). Vm network statistics will be disabled if this is set to 0 or less than 0.", false); + "Interval (in seconds) to report vm network statistics (for Shared and Routed networks). Vm network statistics will be disabled if this is set to 0 or less than 0.", false); private static final ConfigKey<Integer> vmNetworkStatsIntervalMin = new ConfigKey<>("Advanced", Integer.class, "vm.network.stats.interval.min", "300", - "Minimal Interval (in seconds) to report vm network statistics (for Shared networks). If vm.network.stats.interval is smaller than this, use this to report vm network statistics.", + "Minimal Interval (in seconds) to report vm network statistics (for Shared and Routed networks). If vm.network.stats.interval is smaller than this, use this to report vm network statistics.", false); private static final ConfigKey<Integer> StatsTimeout = new ConfigKey<>("Advanced", Integer.class, "stats.timeout", "60000", "The timeout for stats call in milli seconds.", true, @@ -1601,9 +1609,9 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc SearchCriteria<NicVO> sc_nic = _nicDao.createSearchCriteria(); sc_nic.addAnd("macAddress", SearchCriteria.Op.EQ, vmNetworkStatEntry.getMacAddress()); NicVO nic = _nicDao.search(sc_nic, null).get(0); - List<VlanVO> vlan = _vlanDao.listVlansByNetworkId(nic.getNetworkId()); - if (vlan == null || vlan.size() == 0 || vlan.get(0).getVlanType() != VlanType.DirectAttached) - continue; // only get network statistics for DirectAttached network (shared networks in Basic zone and Advanced zone with/without SG) + if (!isNetworkEligibleForNetworkStats(nic.getNetworkId())) { + continue; // only get network statistics for Shared or Routed network + } UserStatisticsVO previousvmNetworkStats = _userStatsDao.findBy(userVm.getAccountId(), userVm.getDataCenterId(), nic.getNetworkId(), nic.getIPv4Address(), vmId, "UserVm"); if (previousvmNetworkStats == null) { @@ -2159,6 +2167,27 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc return true; } + /** + * Returns {@code true} if the given network is eligible for VM network statistics collection. + * Only Shared (DirectAttached) networks and Routed networks qualify. + * + * @param networkId the network id to evaluate + * @return {@code true} when the network is routed or direct-attached, {@code false} otherwise + */ + protected boolean isNetworkEligibleForNetworkStats(Long networkId) { + if (networkId == null) { + return false; + } + List<VlanVO> vlans = _vlanDao.listVlansByNetworkId(networkId); + boolean isDirectAttachedNetwork = CollectionUtils.isNotEmpty(vlans) + && vlans.get(0).getVlanType() == VlanType.DirectAttached; + if (isDirectAttachedNetwork) { + return true; + } + NetworkVO networkVO = networkDao.findById(networkId); + return networkVO != null && routedIpv4Manager.isRoutedNetwork(networkVO); + } + /** * Returns true if all the VmDiskStatsEntry are Zeros (Bytes read, Bytes write, IO read, and IO write must be all equals to zero) */ diff --git a/server/src/test/java/com/cloud/server/StatsCollectorTest.java b/server/src/test/java/com/cloud/server/StatsCollectorTest.java index 46449a9cb6e..cb00d1652c9 100644 --- a/server/src/test/java/com/cloud/server/StatsCollectorTest.java +++ b/server/src/test/java/com/cloud/server/StatsCollectorTest.java @@ -26,6 +26,7 @@ import java.net.URISyntaxException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -38,6 +39,7 @@ import java.util.concurrent.TimeUnit; import com.cloud.utils.DateUtil; import com.google.gson.JsonSyntaxException; import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.network.RoutedIpv4Manager; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.commons.collections.CollectionUtils; import org.influxdb.InfluxDB; @@ -64,7 +66,12 @@ import com.cloud.agent.api.GetStorageStatsAnswer; import com.cloud.agent.api.GetStorageStatsCommand; import com.cloud.agent.api.VmDiskStatsEntry; import com.cloud.agent.api.VmStatsEntry; +import com.cloud.dc.Vlan.VlanType; +import com.cloud.dc.VlanVO; +import com.cloud.dc.dao.VlanDao; import com.cloud.hypervisor.Hypervisor; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkVO; import com.cloud.server.StatsCollector.ExternalStatsProtocol; import com.cloud.storage.StorageStats; import com.cloud.storage.VolumeStatsVO; @@ -118,6 +125,15 @@ public class StatsCollectorTest { @Mock private StoragePoolVO mockPool; + @Mock + private RoutedIpv4Manager routedIpv4Manager; + + @Mock + private NetworkDao networkDao; + + @Mock + private VlanDao vlanDao; + private static Gson gson = new Gson(); private Gson msStatsGson; @@ -744,6 +760,78 @@ public class StatsCollectorTest { */ } + // ----------------------------------------------------------------------- + // Tests for isNetworkEligibleForNetworkStats + // ----------------------------------------------------------------------- + + private VlanVO buildVlan(VlanType type) { + VlanVO vlan = Mockito.mock(VlanVO.class); + Mockito.when(vlan.getVlanType()).thenReturn(type); + return vlan; + } + + @Test + public void isNetworkEligibleForNetworkStats_RoutedNetwork_ReturnsTrue() { + Long networkId = 1L; + NetworkVO networkVO = Mockito.mock(NetworkVO.class); + Mockito.when(networkDao.findById(networkId)).thenReturn(networkVO); + Mockito.when(vlanDao.listVlansByNetworkId(networkId)).thenReturn(Collections.emptyList()); + Mockito.when(routedIpv4Manager.isRoutedNetwork(networkVO)).thenReturn(true); + + Assert.assertTrue(statsCollector.isNetworkEligibleForNetworkStats(networkId)); + } + + @Test + public void isNetworkEligibleForNetworkStats_DirectAttachedNetwork_ReturnsTrue() { + Long networkId = 1L; + NetworkVO networkVO = Mockito.mock(NetworkVO.class); + Mockito.when(networkDao.findById(networkId)).thenReturn(networkVO); + Mockito.when(routedIpv4Manager.isRoutedNetwork(networkVO)).thenReturn(false); + List<VlanVO> vlans = Collections.singletonList(buildVlan(VlanType.DirectAttached)); + Mockito.when(vlanDao.listVlansByNetworkId(networkId)).thenReturn(vlans); + + Assert.assertTrue(statsCollector.isNetworkEligibleForNetworkStats(networkId)); + } + + @Test + public void isNetworkEligibleForNetworkStats_NeitherRoutedNorDirectAttached_ReturnsFalse() { + Long networkId = 1L; + NetworkVO networkVO = Mockito.mock(NetworkVO.class); + Mockito.when(networkDao.findById(networkId)).thenReturn(networkVO); + Mockito.when(routedIpv4Manager.isRoutedNetwork(networkVO)).thenReturn(false); + List<VlanVO> vlans = Collections.singletonList(buildVlan(VlanType.VirtualNetwork)); + Mockito.when(vlanDao.listVlansByNetworkId(networkId)).thenReturn(vlans); + + Assert.assertFalse(statsCollector.isNetworkEligibleForNetworkStats(networkId)); + } + + @Test + public void isNetworkEligibleForNetworkStats_NullNetworkAndEmptyVlans_ReturnsFalse() { + Long networkId = 1L; + Mockito.when(networkDao.findById(networkId)).thenReturn(null); + Mockito.when(vlanDao.listVlansByNetworkId(networkId)).thenReturn(Collections.emptyList()); + + Assert.assertFalse(statsCollector.isNetworkEligibleForNetworkStats(networkId)); + } + + @Test + public void isNetworkEligibleForNetworkStats_NullNetworkButDirectAttached_ReturnsTrue() { + Long networkId = 1L; + Mockito.when(networkDao.findById(networkId)).thenReturn(null); + List<VlanVO> vlans = Collections.singletonList(buildVlan(VlanType.DirectAttached)); + Mockito.when(vlanDao.listVlansByNetworkId(networkId)).thenReturn(vlans); + + Assert.assertTrue(statsCollector.isNetworkEligibleForNetworkStats(networkId)); + Mockito.verify(routedIpv4Manager, Mockito.never()).isRoutedNetwork(Mockito.any()); + } + + @Test + public void isNetworkEligibleForNetworkStats_NullNetworkId_ReturnsFalse() { + Assert.assertFalse(statsCollector.isNetworkEligibleForNetworkStats(null)); + Mockito.verify(networkDao, Mockito.never()).findById(Mockito.anyLong()); + Mockito.verify(vlanDao, Mockito.never()).listVlansByNetworkId(Mockito.anyLong()); + } + private static class TestClass { private String str; private int num;
