Skip to content

Commit 22bd9f1

Browse files
committed
Allow exclusion of destination addresses from usage on KVM shared networks
1 parent a54ee1d commit 22bd9f1

14 files changed

Lines changed: 1316 additions & 17 deletions

File tree

‎debian/control‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Description: CloudStack server library
2424

2525
Package: cloudstack-agent
2626
Architecture: all
27-
Depends: ${python:Depends}, ${python3:Depends}, openjdk-17-jre-headless | java17-runtime-headless | java17-runtime | zulu-17 | openjdk-21-jre-headless | java21-runtime-headless | java21-runtime | zulu-21, cloudstack-common (= ${source:Version}), lsb-base (>= 9), openssh-client, qemu-kvm (>= 2.5) | qemu-system-x86 (>= 5.2), libvirt-bin (>= 1.3) | libvirt-daemon-system (>= 3.0), iproute2, ebtables, vlan, ipset, python3-libvirt, ethtool, iptables, cryptsetup, rng-tools, rsync, ovmf, swtpm, lsb-release, ufw, apparmor, cpu-checker, libvirt-daemon-driver-storage-rbd, sysstat, python3-libnbd, socat
27+
Depends: ${python:Depends}, ${python3:Depends}, openjdk-17-jre-headless | java17-runtime-headless | java17-runtime | zulu-17 | openjdk-21-jre-headless | java21-runtime-headless | java21-runtime | zulu-21, cloudstack-common (= ${source:Version}), lsb-base (>= 9), openssh-client, qemu-kvm (>= 2.5) | qemu-system-x86 (>= 5.2), libvirt-bin (>= 1.3) | libvirt-daemon-system (>= 3.0), iproute2, ebtables, vlan, ipset, nftables, python3-libvirt, ethtool, iptables, cryptsetup, rng-tools, rsync, ovmf, swtpm, lsb-release, ufw, apparmor, cpu-checker, libvirt-daemon-driver-storage-rbd, sysstat, python3-libnbd, socat
2828
Recommends: init-system-helpers
2929
Conflicts: cloud-agent, cloud-agent-libs, cloud-agent-deps, cloud-agent-scripts
3030
Description: CloudStack agent

‎engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,9 +145,10 @@ public interface NetworkOrchestrationService {
145145
"Default data transfer rate in megabits per second allowed in User vm's default network.", true, ConfigKey.Scope.Zone);
146146

147147
ConfigKey<String> NetworkUsageExemptCidrs = new ConfigKey<>(String.class, "network.usage.exempt.cidrs", "Usage", "",
148-
"Comma-separated IPv4 or IPv6 addresses or CIDRs. Traffic between guest networks and these addresses doesn't count " +
149-
"towards virtual router network usage. Virtual routers only count IPv4 traffic, so they ignore IPv6 entries. " +
150-
"Routers pick up a change the next time they start or restart.",
148+
"Comma-separated IPv4 or IPv6 addresses or CIDRs. Traffic between guest VMs and these addresses doesn't count " +
149+
"towards network usage. Virtual routers only count IPv4 traffic, so they ignore IPv6 entries, and pick up a change " +
150+
"the next time they start or restart. On shared networks, KVM hosts exempt both IPv4 and IPv6 and pick up a change " +
151+
"straight away.",
151152
true, ConfigKey.Scope.Zone, null, null, null, null, null, ConfigKey.Kind.CSV, null);
152153

153154
/**

‎engine/components-api/src/main/java/com/cloud/agent/AgentManager.java‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,12 @@ enum TapAgentsAction {
175175

176176
void propagateChangeToAgents(Map<String, String> params);
177177

178+
/**
179+
* Sends network.usage.exempt.cidrs to the KVM hosts in one zone, or in every zone when zoneId is null,
180+
* using the value for each host's zone.
181+
*/
182+
void propagateUsageExemptCidrsToAgents(Long zoneId);
183+
178184
boolean transferDirectAgentsFromMS(String fromMsUuid, long fromMsId, long timeoutDurationInMs, boolean excludeHostsInMaintenance);
179185

180186
int getHostSshPort(HostVO host);

‎engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.nio.channels.ClosedChannelException;
2424
import java.util.ArrayList;
2525
import java.util.Arrays;
26+
import java.util.Collections;
2627
import java.util.Date;
2728
import java.util.HashMap;
2829
import java.util.List;
@@ -2288,6 +2289,7 @@ public void processConnect(final Host host, final StartupCommand cmd, final bool
22882289
params.put(Config.MigrateWait.toString(), _configDao.getValue(Config.MigrateWait.toString()));
22892290
params.put(NetworkOrchestrationService.TUNGSTEN_ENABLED.key(), String.valueOf(NetworkOrchestrationService.TUNGSTEN_ENABLED.valueIn(host.getDataCenterId())));
22902291
params.put(ReconcileCommandService.ReconcileCommandsEnabled.key(), String.valueOf(_reconcileCommandsEnabled));
2292+
params.put(NetworkOrchestrationService.NetworkUsageExemptCidrs.key(), getUsageExemptCidrsParam(host.getDataCenterId()));
22912293

22922294
try {
22932295
SetHostParamsCommand cmds = new SetHostParamsCommand(params);
@@ -2366,6 +2368,31 @@ public void propagateChangeToAgents(Map<String, String> params) {
23662368
}
23672369
}
23682370

2371+
@Override
2372+
public void propagateUsageExemptCidrsToAgents(Long zoneId) {
2373+
final Map<Long, List<Long>> hostsPerZone = getHostsPerZone();
2374+
if (hostsPerZone == null) {
2375+
return;
2376+
}
2377+
logger.debug("Sending {} to the agents", NetworkOrchestrationService.NetworkUsageExemptCidrs.key());
2378+
for (Map.Entry<Long, List<Long>> zone : hostsPerZone.entrySet()) {
2379+
if (zoneId != null && !zoneId.equals(zone.getKey())) {
2380+
continue;
2381+
}
2382+
final Map<String, String> params = new HashMap<>();
2383+
params.put(NetworkOrchestrationService.NetworkUsageExemptCidrs.key(), getUsageExemptCidrsParam(zone.getKey()));
2384+
sendCommandToAgents(Collections.singletonMap(zone.getKey(), zone.getValue()), params);
2385+
}
2386+
}
2387+
2388+
/**
2389+
* The zone's exempt list, tidied and joined with commas. An empty string tells the host the list is empty.
2390+
*/
2391+
protected String getUsageExemptCidrsParam(long zoneId) {
2392+
return String.join(",", NetworkOrchestrationService.parseUsageExemptCidrs(
2393+
NetworkOrchestrationService.NetworkUsageExemptCidrs.valueIn(zoneId)));
2394+
}
2395+
23692396
@Override
23702397
public boolean transferDirectAgentsFromMS(String fromMsUuid, long fromMsId, long timeoutDurationInMs, boolean excludeHostsInMaintenance) {
23712398
return true;

‎engine/orchestration/src/test/java/com/cloud/agent/manager/AgentManagerImplTest.java‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.cloud.agent.api.Answer;
2121
import com.cloud.agent.api.ReadyAnswer;
2222
import com.cloud.agent.api.ReadyCommand;
23+
import com.cloud.agent.api.SetHostParamsCommand;
2324
import com.cloud.agent.api.StartupCommand;
2425
import com.cloud.agent.api.StartupRoutingCommand;
2526
import com.cloud.exception.ConnectionException;
@@ -37,7 +38,10 @@
3738
import org.mockito.Mockito;
3839

3940
import java.util.ArrayList;
41+
import java.util.Arrays;
42+
import java.util.Collections;
4043
import java.util.HashMap;
44+
import java.util.List;
4145
import java.util.Map;
4246

4347
public class AgentManagerImplTest {
@@ -172,4 +176,33 @@ public void testGetHostSshPortWithKVMHostCustomPort() {
172176
int hostSshPort = mgr.getHostSshPort(host);
173177
Assert.assertEquals(3922, hostSshPort);
174178
}
179+
180+
private void mockUsageExemptPush() {
181+
Map<Long, List<Long>> hostsPerZone = new HashMap<>();
182+
hostsPerZone.put(1L, Arrays.asList(11L, 12L));
183+
hostsPerZone.put(2L, Collections.singletonList(21L));
184+
Mockito.doReturn(hostsPerZone).when(mgr).getHostsPerZone();
185+
Mockito.doReturn("192.0.2.5/32").when(mgr).getUsageExemptCidrsParam(Mockito.anyLong());
186+
Answer answer = Mockito.mock(Answer.class);
187+
Mockito.when(answer.getResult()).thenReturn(true);
188+
Mockito.doReturn(answer).when(mgr).easySend(Mockito.anyLong(), Mockito.any(SetHostParamsCommand.class));
189+
}
190+
191+
@Test
192+
public void propagateUsageExemptCidrsToAgentsTestOneZone() {
193+
mockUsageExemptPush();
194+
mgr.propagateUsageExemptCidrsToAgents(1L);
195+
Mockito.verify(mgr).easySend(Mockito.eq(11L), Mockito.any(SetHostParamsCommand.class));
196+
Mockito.verify(mgr).easySend(Mockito.eq(12L), Mockito.any(SetHostParamsCommand.class));
197+
Mockito.verify(mgr, Mockito.never()).easySend(Mockito.eq(21L), Mockito.any(SetHostParamsCommand.class));
198+
}
199+
200+
@Test
201+
public void propagateUsageExemptCidrsToAgentsTestAllZones() {
202+
mockUsageExemptPush();
203+
mgr.propagateUsageExemptCidrsToAgents(null);
204+
Mockito.verify(mgr, Mockito.times(3)).easySend(Mockito.anyLong(), Mockito.any(SetHostParamsCommand.class));
205+
Mockito.verify(mgr).getUsageExemptCidrsParam(1L);
206+
Mockito.verify(mgr).getUsageExemptCidrsParam(2L);
207+
}
175208
}

‎packaging/el8/cloud.spec‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ Requires: ethtool
114114
Requires: (net-tools or net-tools-deprecated)
115115
Requires: iproute
116116
Requires: ipset
117+
Requires: nftables
117118
Requires: perl
118119
Requires: rsync
119120
Requires: cifs-utils

‎packaging/suse15/cloud.spec‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ Requires: ethtool
114114
Requires: (net-tools or net-tools-deprecated)
115115
Requires: iproute
116116
Requires: ipset
117+
Requires: nftables
117118
Requires: perl
118119
Requires: rsync
119120
Requires: cifs-utils

‎plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java‎

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
623623
protected MemStat memStat = new MemStat(dom0MinMem, dom0OvercommitMem);
624624
private final LibvirtUtilitiesHelper libvirtUtilitiesHelper = new LibvirtUtilitiesHelper();
625625
private LibvirtDomainListener libvirtDomainListener;
626+
private final UsageExemptCounters usageExemptCounters = new UsageExemptCounters();
626627

627628
protected Boolean enableManuallySettingCpuTopologyOnKvmVm = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.ENABLE_MANUALLY_SETTING_CPU_TOPOLOGY_ON_KVM_VM);
628629

@@ -1790,10 +1791,19 @@ public boolean configureHostParams(final Map<String, String> params) {
17901791
isTungstenEnabled = Boolean.parseBoolean(params.get(NetworkOrchestrationService.TUNGSTEN_ENABLED.key()));
17911792
}
17921793

1793-
if (params.get(ReconcileCommandService.ReconcileCommandsEnabled.key()) != null) {
1794-
isReconcileCommandsEnabled = Boolean.parseBoolean(params.get(ReconcileCommandService.ReconcileCommandsEnabled.key()));
1794+
final String usageExemptCidrs = params.get(NetworkOrchestrationService.NetworkUsageExemptCidrs.key());
1795+
if (usageExemptCidrs != null && hypervisorType == HypervisorType.KVM) {
1796+
usageExemptCounters.setTapOwners(this::getTapOwners);
1797+
usageExemptCounters.setCidrs(NetworkOrchestrationService.parseUsageExemptCidrs(usageExemptCidrs));
17951798
}
1796-
if (isReconcileCommandsEnabled) {
1799+
1800+
final String reconcileCommandsEnabled = params.get(ReconcileCommandService.ReconcileCommandsEnabled.key());
1801+
if (reconcileCommandsEnabled != null) {
1802+
isReconcileCommandsEnabled = Boolean.parseBoolean(reconcileCommandsEnabled);
1803+
}
1804+
// Only on connect, which is the only time the server sends this setting. Settings pushed while the agent is
1805+
// running must leave the logs of commands still in progress alone.
1806+
if (isReconcileCommandsEnabled && reconcileCommandsEnabled != null) {
17971807
File commandsLogPath = new File(COMMANDS_LOG_PATH);
17981808
if (!commandsLogPath.exists()) {
17991809
commandsLogPath.mkdirs();
@@ -5197,6 +5207,38 @@ private int executeBashScriptAndRetrieveExitValue(final String script) {
51975207
return command.getExitValue();
51985208
}
51995209

5210+
public UsageExemptCounters getUsageExemptCounters() {
5211+
return usageExemptCounters;
5212+
}
5213+
5214+
/**
5215+
* Maps each running VM's NIC device name to the VM's name, so the usage exemption counters can skip system VMs.
5216+
* Returns null if libvirt can't be asked.
5217+
*/
5218+
protected Map<String, String> getTapOwners() {
5219+
try {
5220+
final Connect conn = LibvirtConnection.getConnection();
5221+
final Map<String, String> owners = new HashMap<>();
5222+
for (final int id : conn.listDomains()) {
5223+
final Domain dm = conn.domainLookupByID(id);
5224+
try {
5225+
final String vmName = dm.getName();
5226+
for (final InterfaceDef nic : getInterfaces(conn, vmName)) {
5227+
if (nic.getDevName() != null) {
5228+
owners.put(nic.getDevName(), vmName);
5229+
}
5230+
}
5231+
} finally {
5232+
dm.free();
5233+
}
5234+
}
5235+
return owners;
5236+
} catch (final LibvirtException e) {
5237+
LOGGER.debug("Failed to list the running VMs' NICs for the usage exemption counters", e);
5238+
return null;
5239+
}
5240+
}
5241+
52005242
public List<VmNetworkStatsEntry> getVmNetworkStat(Connect conn, String vmName) throws LibvirtException {
52015243
Domain dm = null;
52025244
try {
@@ -5209,7 +5251,8 @@ public List<VmNetworkStatsEntry> getVmNetworkStat(Connect conn, String vmName) t
52095251
for (InterfaceDef nic : nics) {
52105252
DomainInterfaceStats nicStats = dm.interfaceStats(nic.getDevName());
52115253
String macAddress = nic.getMacAddress();
5212-
VmNetworkStatsEntry stat = new VmNetworkStatsEntry(vmName, macAddress, nicStats.tx_bytes, nicStats.rx_bytes);
5254+
long[] usage = usageExemptCounters.subtractExempt(nic.getDevName(), nicStats.tx_bytes, nicStats.rx_bytes);
5255+
VmNetworkStatsEntry stat = new VmNetworkStatsEntry(vmName, macAddress, usage[0], usage[1]);
52135256
stats.add(stat);
52145257
}
52155258

0 commit comments

Comments
 (0)