This is an automated email from the ASF dual-hosted git repository. Pearl1594 pushed a commit to branch support-multi-vlan-guestnet in repository https://gitbox.apache.org/repos/asf/cloudstack.git
commit 749b2b7f005b7a357e6458f19a5ca2667fb34d52 Author: Pearl1594 <[email protected]> AuthorDate: Tue Oct 6 14:13:09 2026 -0400 Fix DHCP databag merge clobbering a trunk nic's sibling entry on shared VPC-tier VRs --- .../cloud/agent/api/routing/DhcpEntryCommand.java | 12 ++++++++++++ .../virtualnetwork/facade/DhcpEntryConfigItem.java | 2 +- .../resource/virtualnetwork/model/VmDhcpConfig.java | 16 ++++++++++++++-- .../com/cloud/network/router/CommandSetupHelper.java | 12 ++++++++++-- systemvm/debian/opt/cloud/bin/cs_dhcp.py | 20 +++++++++++++------- 5 files changed, 50 insertions(+), 12 deletions(-) diff --git a/core/src/main/java/com/cloud/agent/api/routing/DhcpEntryCommand.java b/core/src/main/java/com/cloud/agent/api/routing/DhcpEntryCommand.java index 67ca447e385..cd7ab226fa7 100644 --- a/core/src/main/java/com/cloud/agent/api/routing/DhcpEntryCommand.java +++ b/core/src/main/java/com/cloud/agent/api/routing/DhcpEntryCommand.java @@ -34,6 +34,10 @@ public class DhcpEntryCommand extends NetworkElementCommand { String ip6Gateway; String duid; private boolean isDefault; + // True only for a nic's associated-network entry (multi-VLAN trunk nics) - distinct from isDefault, + // which reflects whether the nic is the VM's default nic, not whether this entry may coexist with + // other entries sharing the same MAC. See cs_dhcp.py's merge() on the VR for how this is consumed. + private boolean associatedNetwork; boolean executeInSequence = false; boolean remove; Long leaseTime; @@ -154,6 +158,14 @@ public class DhcpEntryCommand extends NetworkElementCommand { this.isDefault = isDefault; } + public boolean isAssociatedNetwork() { + return associatedNetwork; + } + + public void setAssociatedNetwork(boolean associatedNetwork) { + this.associatedNetwork = associatedNetwork; + } + public Long getLeaseTime() { return leaseTime; } diff --git a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/DhcpEntryConfigItem.java b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/DhcpEntryConfigItem.java index 041645046b2..d9faec0443e 100644 --- a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/DhcpEntryConfigItem.java +++ b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/DhcpEntryConfigItem.java @@ -35,7 +35,7 @@ public class DhcpEntryConfigItem extends AbstractConfigItemFacade { final DhcpEntryCommand command = (DhcpEntryCommand) cmd; final VmDhcpConfig vmDhcpConfig = new VmDhcpConfig(command.getVmName(), command.getVmMac(), command.getVmIpAddress(), command.getVmIp6Address(), command.getDuid(), command.getDefaultDns(), - command.getDefaultRouter(), command.getStaticRoutes(), command.isDefault(), command.isRemove(), command.getLeaseTime()); + command.getDefaultRouter(), command.getStaticRoutes(), command.isDefault(), command.isRemove(), command.getLeaseTime(), command.isAssociatedNetwork()); return generateConfigItems(vmDhcpConfig); } diff --git a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/VmDhcpConfig.java b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/VmDhcpConfig.java index 1c43cd1823b..d51c05b0a70 100644 --- a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/VmDhcpConfig.java +++ b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/VmDhcpConfig.java @@ -29,6 +29,9 @@ public class VmDhcpConfig extends ConfigBase { private String defaultGateway; private String staticRoutes; private boolean defaultEntry; + // True only for a nic's associated-network entry (multi-VLAN trunk nics) - lets the VR-side merge + // keep this entry from overwriting, or being overwritten by, another entry sharing the same MAC. + private boolean associatedNetwork; private Long leaseTime; // Indicate if the entry should be removed when set to true @@ -40,11 +43,11 @@ public class VmDhcpConfig extends ConfigBase { public VmDhcpConfig(String hostName, String macAddress, String ipv4Address, String ipv6Address, String ipv6Duid, String dnsAddresses, String defaultGateway, String staticRoutes, boolean defaultEntry, boolean remove) { - this(hostName, macAddress, ipv4Address, ipv6Address, ipv6Duid, dnsAddresses, defaultGateway, staticRoutes, defaultEntry, remove, null); + this(hostName, macAddress, ipv4Address, ipv6Address, ipv6Duid, dnsAddresses, defaultGateway, staticRoutes, defaultEntry, remove, null, false); } public VmDhcpConfig(String hostName, String macAddress, String ipv4Address, String ipv6Address, String ipv6Duid, String dnsAddresses, String defaultGateway, - String staticRoutes, boolean defaultEntry, boolean remove, Long leaseTime) { + String staticRoutes, boolean defaultEntry, boolean remove, Long leaseTime, boolean associatedNetwork) { super(VM_DHCP); this.hostName = hostName; this.macAddress = macAddress; @@ -57,6 +60,7 @@ public class VmDhcpConfig extends ConfigBase { this.defaultEntry = defaultEntry; this.remove = remove; this.leaseTime = leaseTime; + this.associatedNetwork = associatedNetwork; } public String getHostName() { @@ -147,4 +151,12 @@ public class VmDhcpConfig extends ConfigBase { this.leaseTime = leaseTime; } + public boolean isAssociatedNetwork() { + return associatedNetwork; + } + + public void setAssociatedNetwork(boolean associatedNetwork) { + this.associatedNetwork = associatedNetwork; + } + } diff --git a/server/src/main/java/com/cloud/network/router/CommandSetupHelper.java b/server/src/main/java/com/cloud/network/router/CommandSetupHelper.java index d30b3968aca..2fb10b622b6 100644 --- a/server/src/main/java/com/cloud/network/router/CommandSetupHelper.java +++ b/server/src/main/java/com/cloud/network/router/CommandSetupHelper.java @@ -285,14 +285,21 @@ public class CommandSetupHelper { public void createDhcpEntryCommand(final VirtualRouter router, final UserVm vm, final NicVO nic, boolean remove, final Commands cmds) { createDhcpEntryCommand(router, vm, nic.getMacAddress(), nic.getIPv4Address(), nic.getIPv6Address(), nic.getIPv4Gateway(), nic.getIPv6Gateway(), - nic.getNetworkId(), nic.isDefaultNic(), remove, cmds); + nic.getNetworkId(), nic.isDefaultNic(), remove, false, cmds); } // Same as createDhcpEntryCommand(router, vm, NicVO, ...), but for a nic's association with a network other // than its primary (multi-VLAN trunk nics) - takes the addressing explicitly, since a nic's own DB row only - // ever carries its primary network's IP/gateway. + // ever carries its primary network's IP/gateway. Always called with isAssociatedNetwork=true: this is the + // one overload every trunk-association DHCP push goes through, directly (never via the NicVO overload above). public void createDhcpEntryCommand(final VirtualRouter router, final UserVm vm, final String macAddress, final String ip4Address, final String ip6Address, final String ip4Gateway, final String ip6Gateway, final long networkId, final boolean isDefaultNic, boolean remove, final Commands cmds) { + createDhcpEntryCommand(router, vm, macAddress, ip4Address, ip6Address, ip4Gateway, ip6Gateway, networkId, isDefaultNic, remove, true, cmds); + } + + private void createDhcpEntryCommand(final VirtualRouter router, final UserVm vm, final String macAddress, final String ip4Address, final String ip6Address, + final String ip4Gateway, final String ip6Gateway, final long networkId, final boolean isDefaultNic, boolean remove, final boolean isAssociatedNetwork, + final Commands cmds) { final DhcpEntryCommand dhcpCommand = new DhcpEntryCommand(macAddress, ip4Address, vm.getHostName(), ip6Address, _networkModel.getExecuteInSeqNtwkElmtCmd()); @@ -308,6 +315,7 @@ public class CommandSetupHelper { dhcpCommand.setDefaultDns(ipaddress); dhcpCommand.setDuid(NetUtils.getDuidLL(macAddress)); dhcpCommand.setDefault(isDefaultNic); + dhcpCommand.setAssociatedNetwork(isAssociatedNetwork); dhcpCommand.setRemove(remove); // Set DHCP lease timeout from zone-scoped config (0 = infinite) diff --git a/systemvm/debian/opt/cloud/bin/cs_dhcp.py b/systemvm/debian/opt/cloud/bin/cs_dhcp.py index cd6574fd926..cfebd125438 100755 --- a/systemvm/debian/opt/cloud/bin/cs_dhcp.py +++ b/systemvm/debian/opt/cloud/bin/cs_dhcp.py @@ -26,14 +26,20 @@ def merge(dbag, data): if data['ipv4_address'] in dbag: del dbag[data['ipv4_address']] else: - remove_keys = set() - for key, entry in dbag.items(): - if key != 'id' and entry['mac_address'] == data['mac_address']: - remove_keys.add(key) - break + # A multi-VLAN trunk nic's one MAC can legitimately hold several simultaneous entries - its + # primary network's, plus one per associated network. Only ever replace an existing entry for + # this MAC when the incoming entry is itself not an associated-network entry, and only replace + # an existing entry that is also not one - otherwise an association's push would delete the + # nic's own primary entry (or a sibling association's), and vice versa. + if not data.get('associated_network', False): + remove_keys = set() + for key, entry in dbag.items(): + if key != 'id' and entry['mac_address'] == data['mac_address'] and not entry.get('associated_network', False): + remove_keys.add(key) + break - for remove_key in remove_keys: - del dbag[remove_key] + for remove_key in remove_keys: + del dbag[remove_key] dbag[data['ipv4_address']] = data
