From bb72490b72b1c122973aaa3d399bb7a5da2a57d1 Mon Sep 17 00:00:00 2001 From: Shawn Edwards Date: Mon, 12 Jan 2026 11:22:13 -0600 Subject: [PATCH] Enhance DHCP functionality by adding lease time support across various components. Updated DhcpEntryCommand to include lease time, modified VmDhcpConfig to handle lease time, and adjusted related scripts and configurations to accommodate this new parameter. This allows for configurable DHCP lease durations, improving flexibility in network management. --- .../agent/api/routing/DhcpEntryCommand.java | 9 ++++++++ .../facade/DhcpEntryConfigItem.java | 2 +- .../virtualnetwork/model/VmDhcpConfig.java | 15 +++++++++++++ scripts/network/exdhcp/dhcpd_edithosts.py | 21 ++++++++++++------- scripts/network/exdhcp/dnsmasq_edithosts.sh | 9 +++++++- .../java/com/cloud/configuration/Config.java | 9 ++++++++ .../network/router/CommandSetupHelper.java | 5 +++++ systemvm/debian/opt/cloud/bin/cs/CsDhcp.py | 6 ++++-- systemvm/debian/opt/cloud/bin/edithosts.sh | 17 ++++++++++----- 9 files changed, 76 insertions(+), 17 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 7fb65fe15cf9..67ca447e3853 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 @@ -36,6 +36,7 @@ public class DhcpEntryCommand extends NetworkElementCommand { private boolean isDefault; boolean executeInSequence = false; boolean remove; + Long leaseTime; public boolean isRemove() { return remove; @@ -152,4 +153,12 @@ public boolean isDefault() { public void setDefault(boolean isDefault) { this.isDefault = isDefault; } + + public Long getLeaseTime() { + return leaseTime; + } + + public void setLeaseTime(Long leaseTime) { + this.leaseTime = 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 9c5b657bb4e4..041645046b23 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 List generateConfig(final NetworkElementCommand cmd) { 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.getDefaultRouter(), command.getStaticRoutes(), command.isDefault(), command.isRemove(), command.getLeaseTime()); 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 d9cb8b0b2645..1c43cd1823b9 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,7 @@ public class VmDhcpConfig extends ConfigBase { private String defaultGateway; private String staticRoutes; private boolean defaultEntry; + private Long leaseTime; // Indicate if the entry should be removed when set to true private boolean remove; @@ -39,6 +40,11 @@ public VmDhcpConfig() { 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); + } + + public VmDhcpConfig(String hostName, String macAddress, String ipv4Address, String ipv6Address, String ipv6Duid, String dnsAddresses, String defaultGateway, + String staticRoutes, boolean defaultEntry, boolean remove, Long leaseTime) { super(VM_DHCP); this.hostName = hostName; this.macAddress = macAddress; @@ -50,6 +56,7 @@ public VmDhcpConfig(String hostName, String macAddress, String ipv4Address, Stri this.staticRoutes = staticRoutes; this.defaultEntry = defaultEntry; this.remove = remove; + this.leaseTime = leaseTime; } public String getHostName() { @@ -132,4 +139,12 @@ public void setDefaultEntry(boolean defaultEntry) { this.defaultEntry = defaultEntry; } + public Long getLeaseTime() { + return leaseTime; + } + + public void setLeaseTime(Long leaseTime) { + this.leaseTime = leaseTime; + } + } diff --git a/scripts/network/exdhcp/dhcpd_edithosts.py b/scripts/network/exdhcp/dhcpd_edithosts.py index 4df996dc0321..a0deae63a50b 100644 --- a/scripts/network/exdhcp/dhcpd_edithosts.py +++ b/scripts/network/exdhcp/dhcpd_edithosts.py @@ -17,18 +17,18 @@ # under the License. -# Usage: dhcpd_edithosts.py mac ip hostname dns gateway nextserver +# Usage: dhcpd_edithosts.py mac ip hostname dns gateway nextserver [leasetime] import sys, os from os.path import exists from time import sleep from os import remove -usage = '''dhcpd_edithosts.py mac ip hostname dns gateway nextserver''' +usage = '''dhcpd_edithosts.py mac ip hostname dns gateway nextserver [leasetime]''' conf_path = "/etc/dhcpd.conf" file_lock = "/etc/dhcpd.conf_locked" sleep_max = 20 -host_entry = 'host %s { hardware ethernet %s; fixed-address %s; option domain-name-servers %s; option domain-name "%s"; option routers %s; default-lease-time infinite; max-lease-time infinite; min-lease-time infinite; filename "pxelinux.0";}' -host_entry1 = 'host %s { hardware ethernet %s; fixed-address %s; option domain-name-servers %s; option domain-name "%s"; option routers %s; default-lease-time infinite; max-lease-time infinite; min-lease-time infinite; next-server %s; filename "pxelinux.0";}' +host_entry = 'host %s { hardware ethernet %s; fixed-address %s; option domain-name-servers %s; option domain-name "%s"; option routers %s; default-lease-time %s; max-lease-time %s; min-lease-time %s; filename "pxelinux.0";}' +host_entry1 = 'host %s { hardware ethernet %s; fixed-address %s; option domain-name-servers %s; option domain-name "%s"; option routers %s; default-lease-time %s; max-lease-time %s; min-lease-time %s; next-server %s; filename "pxelinux.0";}' def lock(): if exists(file_lock): count = 0 @@ -59,10 +59,14 @@ def unlock(): print "Cannot remove file lock at %s" % file_lock return False -def insert_host_entry(mac, ip, hostname, dns, gateway, next_server): +def insert_host_entry(mac, ip, hostname, dns, gateway, next_server, lease_time="infinite"): if lock() == False: return 1 + # Convert 0 to 'infinite' for lease time + if lease_time == "0": + lease_time = "infinite" + cmd = 'sed -i /"fixed-address %s"/d %s' % (ip, conf_path) ret = os.system(cmd) if ret != 0: @@ -78,9 +82,9 @@ def insert_host_entry(mac, ip, hostname, dns, gateway, next_server): return 1 if next_server != "null": - entry = host_entry1 % (hostname, mac, ip, dns, "cloudnine.internal", gateway, next_server) + entry = host_entry1 % (hostname, mac, ip, dns, "cloudnine.internal", gateway, lease_time, lease_time, lease_time, next_server) else: - entry = host_entry % (hostname, mac, ip, dns, "cloudnine.internal", gateway) + entry = host_entry % (hostname, mac, ip, dns, "cloudnine.internal", gateway, lease_time, lease_time, lease_time) cmd = '''echo '%s' >> %s''' % (entry, conf_path) ret = os.system(cmd) if ret != 0: @@ -111,6 +115,7 @@ def insert_host_entry(mac, ip, hostname, dns, gateway, next_server): dns = sys.argv[4] gateway = sys.argv[5] next_server = sys.argv[6] + lease_time = sys.argv[7] if len(sys.argv) > 7 else "infinite" if exists(conf_path) == False: conf_path = "/etc/dhcp/dhcpd.conf" @@ -118,5 +123,5 @@ def insert_host_entry(mac, ip, hostname, dns, gateway, next_server): print "Cannot find dhcpd.conf" sys.exit(1) - ret = insert_host_entry(mac, ip, hostname, dns, gateway, next_server) + ret = insert_host_entry(mac, ip, hostname, dns, gateway, next_server, lease_time) sys.exit(ret) diff --git a/scripts/network/exdhcp/dnsmasq_edithosts.sh b/scripts/network/exdhcp/dnsmasq_edithosts.sh index f0744a9a822b..522250d8622b 100755 --- a/scripts/network/exdhcp/dnsmasq_edithosts.sh +++ b/scripts/network/exdhcp/dnsmasq_edithosts.sh @@ -21,6 +21,7 @@ # $1 : the mac address # $2 : the associated ip address # $3 : the hostname +# $4 : the lease time (optional, defaults to 'infinite') wait_for_dnsmasq () { local _pid=$(pidof dnsmasq) @@ -41,11 +42,17 @@ no_dhcp_release=$? [ ! -f /etc/dhcphosts.txt ] && touch /etc/dhcphosts.txt [ ! -f /var/lib/misc/dnsmasq.leases ] && touch /var/lib/misc/dnsmasq.leases +# Set lease time, default to 'infinite', convert 0 to 'infinite' +lease_time=${4:-infinite} +if [ "$lease_time" = "0" ]; then + lease_time=infinite +fi + sed -i /$1/d /etc/dhcphosts.txt sed -i /$2,/d /etc/dhcphosts.txt sed -i /$3,/d /etc/dhcphosts.txt -echo "$1,$2,$3,infinite" >>/etc/dhcphosts.txt +echo "$1,$2,$3,$lease_time" >>/etc/dhcphosts.txt #release previous dhcp lease if present if [ $no_dhcp_release -eq 0 ] diff --git a/server/src/main/java/com/cloud/configuration/Config.java b/server/src/main/java/com/cloud/configuration/Config.java index abae4d3996cb..518c2168e9c2 100644 --- a/server/src/main/java/com/cloud/configuration/Config.java +++ b/server/src/main/java/com/cloud/configuration/Config.java @@ -351,6 +351,15 @@ public enum Config { ConfigKey.Kind.CSV, null), + DhcpLeaseTimeout( + "Network", + ManagementServer.class, + Integer.class, + "dhcp.lease.timeout", + "0", + "DHCP lease time in seconds for VMs. Use 0 for infinite lease time (default). A non-zero value sets the lease duration in seconds.", + null), + //VPN RemoteAccessVpnPskLength( "Network", 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 b915027e9ab3..78a2be99be5f 100644 --- a/server/src/main/java/com/cloud/network/router/CommandSetupHelper.java +++ b/server/src/main/java/com/cloud/network/router/CommandSetupHelper.java @@ -296,6 +296,11 @@ public void createDhcpEntryCommand(final VirtualRouter router, final UserVm vm, dhcpCommand.setDefault(nic.isDefaultNic()); dhcpCommand.setRemove(remove); + // Set DHCP lease timeout from global config (0 = infinite) + String leaseTimeStr = _configDao.getValue(Config.DhcpLeaseTimeout.key()); + Long leaseTime = (leaseTimeStr != null) ? Long.parseLong(leaseTimeStr) : 0L; + dhcpCommand.setLeaseTime(leaseTime); + dhcpCommand.setAccessDetail(NetworkElementCommand.ROUTER_IP, _routerControlHelper.getRouterControlIp(router.getId())); dhcpCommand.setAccessDetail(NetworkElementCommand.ROUTER_NAME, router.getInstanceName()); dhcpCommand.setAccessDetail(NetworkElementCommand.ROUTER_GUEST_IP, _routerControlHelper.getRouterIpInNetwork(nic.getNetworkId(), router.getId())); diff --git a/systemvm/debian/opt/cloud/bin/cs/CsDhcp.py b/systemvm/debian/opt/cloud/bin/cs/CsDhcp.py index e15714af212f..9c048f40540e 100755 --- a/systemvm/debian/opt/cloud/bin/cs/CsDhcp.py +++ b/systemvm/debian/opt/cloud/bin/cs/CsDhcp.py @@ -199,12 +199,14 @@ def write_hosts(self): def add(self, entry): self.add_host(entry['ipv4_address'], entry['host_name']) - # Lease time set to "infinite" since we properly control all DHCP/DNS config via CloudStack. + # Lease time is configurable via CloudStack global config dhcp.lease.timeout + # 0 = infinite (default), otherwise the value represents seconds # Infinite time helps avoid some edge cases which could cause DHCPNAK being sent to VMs since # (RHEL) system lose routes when they receive DHCPNAK. # When VM is expunged, its active lease and DHCP/DNS config is properly removed from related files in VR, # so the infinite duration of lease does not cause any issues or garbage. - lease = 'infinite' + lease_time = entry.get('lease_time', 0) + lease = 'infinite' if lease_time == 0 else str(lease_time) if entry['default_entry']: self.dhcp_hosts.add("%s,%s,%s,%s" % (entry['mac_address'], diff --git a/systemvm/debian/opt/cloud/bin/edithosts.sh b/systemvm/debian/opt/cloud/bin/edithosts.sh index 6f66331c88e3..976f009c2b12 100755 --- a/systemvm/debian/opt/cloud/bin/edithosts.sh +++ b/systemvm/debian/opt/cloud/bin/edithosts.sh @@ -21,7 +21,7 @@ # edithosts.sh -- edit the dhcphosts file on the routing domain usage() { - printf "Usage: %s: -m -4 -6 -h -d -n -s -u [-N]\n" $(basename $0) >&2 + printf "Usage: %s: -m -4 -6 -h -d -n -s -u -l [-N]\n" $(basename $0) >&2 } mac= @@ -33,8 +33,9 @@ dns= routes= duid= nondefault= +lease_time=infinite -while getopts 'm:4:h:d:n:s:6:u:N' OPTION +while getopts 'm:4:h:d:n:s:6:u:l:N' OPTION do case $OPTION in m) mac="$OPTARG" @@ -53,6 +54,8 @@ do ;; s) routes="$OPTARG" ;; + l) lease_time="$OPTARG" + ;; N) nondefault=1 ;; ?) usage @@ -124,17 +127,21 @@ fi sed -i /$host,/d $DHCP_HOSTS #put in the new entry +# If lease_time is 0, use 'infinite', otherwise use the value +if [ "$lease_time" = "0" ]; then + lease_time=infinite +fi if [ $ipv4 ] then - echo "$mac,$ipv4,$host,infinite" >>$DHCP_HOSTS + echo "$mac,$ipv4,$host,$lease_time" >>$DHCP_HOSTS fi if [ $ipv6 ] then if [ $nondefault ] then - echo "id:$duid,set:nondefault6,[$ipv6],$host,infinite" >>$DHCP_HOSTS + echo "id:$duid,set:nondefault6,[$ipv6],$host,$lease_time" >>$DHCP_HOSTS else - echo "id:$duid,[$ipv6],$host,infinite" >>$DHCP_HOSTS + echo "id:$duid,[$ipv6],$host,$lease_time" >>$DHCP_HOSTS fi fi