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/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java index 947cbd8e6182..23bd7aa13b48 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java @@ -91,6 +91,10 @@ public interface NetworkOrchestrationService { ConfigKey NetworkThrottlingRate = new ConfigKey<>("Network", Integer.class, NetworkThrottlingRateCK, "200", "Default data transfer rate in megabits per second allowed in network.", true, ConfigKey.Scope.Zone); + ConfigKey DhcpLeaseTimeout = new ConfigKey<>("Network", 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.", + true, ConfigKey.Scope.Zone, "0-"); + ConfigKey PromiscuousMode = new ConfigKey<>("Advanced", Boolean.class, "network.promiscuous.mode", "false", "Whether to allow or deny promiscuous mode on NICs for applicable network elements such as for vswitch/dvswitch portgroups.", true); 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/network/router/CommandSetupHelper.java b/server/src/main/java/com/cloud/network/router/CommandSetupHelper.java index b915027e9ab3..39775cc08afb 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,10 @@ public void createDhcpEntryCommand(final VirtualRouter router, final UserVm vm, dhcpCommand.setDefault(nic.isDefaultNic()); dhcpCommand.setRemove(remove); + // Set DHCP lease timeout from zone-scoped config (0 = infinite) + Long leaseTime = (long) NetworkOrchestrationService.DhcpLeaseTimeout.valueIn(router.getDataCenterId()); + 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/server/src/test/java/com/cloud/network/router/CommandSetupHelperTest.java b/server/src/test/java/com/cloud/network/router/CommandSetupHelperTest.java index bdd905841f92..5abc4ee255f6 100644 --- a/server/src/test/java/com/cloud/network/router/CommandSetupHelperTest.java +++ b/server/src/test/java/com/cloud/network/router/CommandSetupHelperTest.java @@ -46,6 +46,8 @@ import com.cloud.vm.NicVO; import com.cloud.vm.VirtualMachine; import com.cloud.vm.dao.NicDao; +import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; +import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.network.BgpPeerVO; import org.apache.cloudstack.network.dao.BgpPeerDetailsDao; import org.junit.Assert; @@ -271,4 +273,52 @@ public void testCreateBgpPeersCommandsForVpc() { Assert.assertTrue(cmd instanceof SetBgpPeersCommand); Assert.assertEquals(4, ((SetBgpPeersCommand) cmd).getBpgPeers().length); } + + @Test + public void testDhcpLeaseTimeoutDefaultValue() { + // Test that the default value is 0 (infinite) + Integer defaultValue = NetworkOrchestrationService.DhcpLeaseTimeout.value(); + Assert.assertEquals("Default DHCP lease timeout should be 0 (infinite)", 0, defaultValue.intValue()); + } + + @Test + public void testDhcpLeaseTimeoutAcceptsZero() { + // Test that 0 value is accepted (infinite lease) + ConfigKey configKey = NetworkOrchestrationService.DhcpLeaseTimeout; + Assert.assertNotNull("ConfigKey should not be null", configKey); + Assert.assertEquals("ConfigKey default should be 0", "0", configKey.defaultValue()); + } + + @Test + public void testDhcpLeaseTimeoutAcceptsPositiveValues() { + // Test that positive values are accepted + ConfigKey configKey = NetworkOrchestrationService.DhcpLeaseTimeout; + Assert.assertNotNull("ConfigKey should not be null", configKey); + // Verify the validator allows positive values + String validator = configKey.range(); + Assert.assertNotNull("Validator should not be null", validator); + Assert.assertEquals("Validator should be '0-' (>= 0)", "0-", validator); + } + + @Test + public void testDhcpLeaseTimeoutValidatorRejectsNegative() { + // Test that the validator is configured to reject negative values + ConfigKey configKey = NetworkOrchestrationService.DhcpLeaseTimeout; + String validator = configKey.range(); + Assert.assertEquals("Validator should enforce minimum of 0", "0-", validator); + } + + @Test + public void testDhcpLeaseTimeoutHasZoneScope() { + // Test that the ConfigKey has Zone scope + ConfigKey configKey = NetworkOrchestrationService.DhcpLeaseTimeout; + Assert.assertEquals("ConfigKey should have Zone scope", ConfigKey.Scope.Zone, configKey.scope()); + } + + @Test + public void testDhcpLeaseTimeoutIsDynamic() { + // Test that the ConfigKey is dynamic (can be updated at runtime) + ConfigKey configKey = NetworkOrchestrationService.DhcpLeaseTimeout; + Assert.assertTrue("ConfigKey should be dynamic", configKey.isDynamic()); + } } diff --git a/server/src/test/java/com/cloud/network/router/DhcpLeaseTimeoutIntegrationTest.java b/server/src/test/java/com/cloud/network/router/DhcpLeaseTimeoutIntegrationTest.java new file mode 100644 index 000000000000..7de699aa4a12 --- /dev/null +++ b/server/src/test/java/com/cloud/network/router/DhcpLeaseTimeoutIntegrationTest.java @@ -0,0 +1,190 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.network.router; + +import com.cloud.agent.api.routing.DhcpEntryCommand; +import com.cloud.agent.manager.Commands; +import com.cloud.dc.DataCenterVO; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.network.NetworkModel; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkDetailsDao; +import com.cloud.offerings.dao.NetworkOfferingDao; +import com.cloud.offerings.dao.NetworkOfferingDetailsDao; +import com.cloud.user.UserVmVO; +import com.cloud.vm.NicVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.dao.NicDao; +import com.cloud.vm.dao.UserVmDao; +import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.when; + +/** + * Integration tests for DHCP lease timeout functionality. + * Tests the end-to-end flow from ConfigKey through DhcpEntryCommand creation. + */ +@RunWith(MockitoJUnitRunner.class) +public class DhcpLeaseTimeoutIntegrationTest { + + @InjectMocks + protected CommandSetupHelper commandSetupHelper = new CommandSetupHelper(); + + @Mock + NicDao nicDao; + @Mock + NetworkDao networkDao; + @Mock + NetworkModel networkModel; + @Mock + NetworkOfferingDao networkOfferingDao; + @Mock + NetworkOfferingDetailsDao networkOfferingDetailsDao; + @Mock + NetworkDetailsDao networkDetailsDao; + @Mock + RouterControlHelper routerControlHelper; + @Mock + DataCenterDao dcDao; + @Mock + UserVmDao userVmDao; + + private VirtualRouter mockRouter; + private UserVmVO mockVm; + private NicVO mockNic; + private DataCenterVO mockDc; + + @Before + public void setUp() { + ReflectionTestUtils.setField(commandSetupHelper, "_nicDao", nicDao); + ReflectionTestUtils.setField(commandSetupHelper, "_networkDao", networkDao); + ReflectionTestUtils.setField(commandSetupHelper, "_networkModel", networkModel); + ReflectionTestUtils.setField(commandSetupHelper, "_networkOfferingDao", networkOfferingDao); + ReflectionTestUtils.setField(commandSetupHelper, "networkOfferingDetailsDao", networkOfferingDetailsDao); + ReflectionTestUtils.setField(commandSetupHelper, "networkDetailsDao", networkDetailsDao); + ReflectionTestUtils.setField(commandSetupHelper, "_routerControlHelper", routerControlHelper); + ReflectionTestUtils.setField(commandSetupHelper, "_dcDao", dcDao); + + // Create common mocks + mockRouter = Mockito.mock(VirtualRouter.class); + mockVm = Mockito.mock(UserVmVO.class); + mockNic = Mockito.mock(NicVO.class); + mockDc = Mockito.mock(DataCenterVO.class); + + // Setup default mock behaviors + when(mockRouter.getId()).thenReturn(100L); + when(mockRouter.getInstanceName()).thenReturn("r-100-VM"); + when(mockRouter.getDataCenterId()).thenReturn(1L); + + when(mockVm.getId()).thenReturn(200L); + when(mockVm.getHostName()).thenReturn("test-vm"); + + when(mockNic.getId()).thenReturn(300L); + when(mockNic.getMacAddress()).thenReturn("02:00:0a:0b:0c:0d"); + when(mockNic.getIPv4Address()).thenReturn("10.1.1.10"); + when(mockNic.getIPv6Address()).thenReturn(null); + when(mockNic.getNetworkId()).thenReturn(400L); + when(mockNic.isDefaultNic()).thenReturn(true); + + when(dcDao.findById(anyLong())).thenReturn(mockDc); + when(routerControlHelper.getRouterControlIp(anyLong())).thenReturn("10.1.1.1"); + when(routerControlHelper.getRouterIpInNetwork(anyLong(), anyLong())).thenReturn("10.1.1.1"); + when(networkModel.getExecuteInSeqNtwkElmtCmd()).thenReturn(false); + } + + @Test + public void testDhcpEntryCommandContainsLeaseTime() { + // Test that DhcpEntryCommand includes the lease time from ConfigKey + Commands cmds = new Commands(null); + commandSetupHelper.createDhcpEntryCommand(mockRouter, mockVm, mockNic, false, cmds); + + Assert.assertEquals("Should have one DHCP command", 1, cmds.size()); + DhcpEntryCommand dhcpCmd = (DhcpEntryCommand) cmds.toCommands()[0]; + Assert.assertNotNull("DHCP command should not be null", dhcpCmd); + Assert.assertNotNull("Lease time should not be null", dhcpCmd.getLeaseTime()); + + // Default value should be 0 (infinite) + Assert.assertEquals("Default lease time should be 0", Long.valueOf(0L), dhcpCmd.getLeaseTime()); + } + + @Test + public void testDhcpEntryCommandUsesZoneScopedValue() { + // Test that the command uses zone-scoped configuration + Long zoneId = mockRouter.getDataCenterId(); + Integer expectedLeaseTime = NetworkOrchestrationService.DhcpLeaseTimeout.valueIn(zoneId); + + Commands cmds = new Commands(null); + commandSetupHelper.createDhcpEntryCommand(mockRouter, mockVm, mockNic, false, cmds); + + DhcpEntryCommand dhcpCmd = (DhcpEntryCommand) cmds.toCommands()[0]; + Assert.assertEquals("Lease time should match zone-scoped config", + expectedLeaseTime.longValue(), dhcpCmd.getLeaseTime().longValue()); + } + + @Test + public void testInfiniteLeaseWithZeroValue() { + // Test that 0 value represents infinite lease + ConfigKey configKey = NetworkOrchestrationService.DhcpLeaseTimeout; + Assert.assertEquals("Default value should be 0 for infinite lease", "0", configKey.defaultValue()); + + Commands cmds = new Commands(null); + commandSetupHelper.createDhcpEntryCommand(mockRouter, mockVm, mockNic, false, cmds); + + DhcpEntryCommand dhcpCmd = (DhcpEntryCommand) cmds.toCommands()[0]; + Assert.assertEquals("Lease time 0 represents infinite lease", Long.valueOf(0L), dhcpCmd.getLeaseTime()); + } + + @Test + public void testDhcpCommandForNonDefaultNic() { + // Test DHCP command creation for non-default NIC + when(mockNic.isDefaultNic()).thenReturn(false); + + Commands cmds = new Commands(null); + commandSetupHelper.createDhcpEntryCommand(mockRouter, mockVm, mockNic, false, cmds); + + DhcpEntryCommand dhcpCmd = (DhcpEntryCommand) cmds.toCommands()[0]; + Assert.assertNotNull("DHCP command should be created for non-default NIC", dhcpCmd); + Assert.assertNotNull("Lease time should be set even for non-default NIC", dhcpCmd.getLeaseTime()); + Assert.assertFalse("Command should reflect non-default NIC", dhcpCmd.isDefault()); + } + + @Test + public void testDhcpCommandWithRemoveFlag() { + // Test DHCP command with remove flag set + Commands cmds = new Commands(null); + commandSetupHelper.createDhcpEntryCommand(mockRouter, mockVm, mockNic, true, cmds); + + DhcpEntryCommand dhcpCmd = (DhcpEntryCommand) cmds.toCommands()[0]; + Assert.assertNotNull("DHCP command should be created even with remove flag", dhcpCmd); + Assert.assertTrue("Remove flag should be set", dhcpCmd.isRemove()); + // Lease time should still be included even for removal + Assert.assertNotNull("Lease time should be present even for removal", dhcpCmd.getLeaseTime()); + } +} 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