This is an automated email from the ASF dual-hosted git repository.
weizhouapache pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/main by this push:
new de3656c8193 Enhance DHCP functionality by adding lease timeout support
(#12410)
de3656c8193 is described below
commit de3656c8193c5cf96f4bf39a22b0f285a8e88265
Author: Shawn Edwards <[email protected]>
AuthorDate: Thu Jul 9 14:57:14 2026 -0500
Enhance DHCP functionality by adding lease timeout support (#12410)
* 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.
* Move dhcp lease timeout to ConfigKey, set it to 'zone' scope, and add
some tests.
* Refactor DHCP lease timeout tests to pass code standards/remove white
space.
* add DhcpLeaseTimeout in getConfigKeys() method
* Apply suggestion from @weizhouapache
---------
Co-authored-by: Wei Zhou <[email protected]>
---
.../cloud/agent/api/routing/DhcpEntryCommand.java | 9 +
.../virtualnetwork/facade/DhcpEntryConfigItem.java | 2 +-
.../virtualnetwork/model/VmDhcpConfig.java | 15 ++
.../service/NetworkOrchestrationService.java | 4 +
.../engine/orchestration/NetworkOrchestrator.java | 2 +-
scripts/network/exdhcp/dhcpd_edithosts.py | 21 ++-
scripts/network/exdhcp/dnsmasq_edithosts.sh | 9 +-
.../cloud/network/router/CommandSetupHelper.java | 4 +
.../network/router/CommandSetupHelperTest.java | 40 +++++
.../router/DhcpLeaseTimeoutIntegrationTest.java | 184 +++++++++++++++++++++
systemvm/debian/opt/cloud/bin/cs/CsDhcp.py | 6 +-
systemvm/debian/opt/cloud/bin/edithosts.sh | 17 +-
12 files changed, 295 insertions(+), 18 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 7fb65fe15cf..67ca447e385 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 class DhcpEntryCommand extends
NetworkElementCommand {
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 9c5b657bb4e..041645046b2 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.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 d9cb8b0b264..1c43cd1823b 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 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);
+ }
+
+ 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 class VmDhcpConfig extends ConfigBase {
this.staticRoutes = staticRoutes;
this.defaultEntry = defaultEntry;
this.remove = remove;
+ this.leaseTime = leaseTime;
}
public String getHostName() {
@@ -132,4 +139,12 @@ public class VmDhcpConfig extends ConfigBase {
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 030c1277efe..82960be3d9b 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<Integer> NetworkThrottlingRate = new ConfigKey<>("Network",
Integer.class, NetworkThrottlingRateCK, "200",
"Default data transfer rate in megabits per second allowed in
network.", true, ConfigKey.Scope.Zone);
+ ConfigKey<Integer> 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);
+
ConfigKey<Boolean> 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/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java
b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java
index 4262ee701aa..fe1761d4e5f 100644
---
a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java
+++
b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java
@@ -4955,7 +4955,7 @@ public class NetworkOrchestrator extends ManagerBase
implements NetworkOrchestra
@Override
public ConfigKey<?>[] getConfigKeys() {
return new ConfigKey<?>[]{NetworkGcWait, NetworkGcInterval,
NetworkLockTimeout, DeniedRoutes,
- GuestDomainSuffix, NetworkThrottlingRate, MinVRVersion,
+ GuestDomainSuffix, NetworkThrottlingRate, MinVRVersion,
DhcpLeaseTimeout,
PromiscuousMode, MacAddressChanges, ForgedTransmits,
MacLearning, RollingRestartEnabled,
TUNGSTEN_ENABLED, NSX_ENABLED, NETRIS_ENABLED,
NETWORK_LB_HAPROXY_MAX_CONN,
NETWORK_LB_HAPROXY_IDLE_TIMEOUT};
diff --git a/scripts/network/exdhcp/dhcpd_edithosts.py
b/scripts/network/exdhcp/dhcpd_edithosts.py
index 4df996dc032..a0deae63a50 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 @@ if __name__ == "__main__":
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 @@ if __name__ == "__main__":
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 f0744a9a822..522250d8622 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 c6296682bb0..628c36826de 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 class CommandSetupHelper {
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 bdd905841f9..34909e7e860 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.utils.net.Ip;
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,42 @@ public class CommandSetupHelperTest {
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<Integer> 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<Integer> configKey =
NetworkOrchestrationService.DhcpLeaseTimeout;
+ Assert.assertNotNull("ConfigKey should not be null", configKey);
+ // Verify the config key exists and has expected default
+ Assert.assertEquals("ConfigKey default should be 0", "0",
configKey.defaultValue());
+ }
+
+ @Test
+ public void testDhcpLeaseTimeoutHasZoneScope() {
+ // Test that the ConfigKey has Zone scope
+ ConfigKey<Integer> configKey =
NetworkOrchestrationService.DhcpLeaseTimeout;
+ Assert.assertTrue("ConfigKey should have Zone scope",
configKey.getScopes().contains(ConfigKey.Scope.Zone));
+ }
+
+ @Test
+ public void testDhcpLeaseTimeoutIsDynamic() {
+ // Test that the ConfigKey is dynamic (can be updated at runtime)
+ ConfigKey<Integer> 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 00000000000..198129ace6f
--- /dev/null
+++
b/server/src/test/java/com/cloud/network/router/DhcpLeaseTimeoutIntegrationTest.java
@@ -0,0 +1,184 @@
+// 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.Command;
+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.vm.UserVmVO;
+import com.cloud.vm.NicVO;
+import com.cloud.vm.dao.NicDao;
+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.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;
+
+ 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.getHostName()).thenReturn("test-vm");
+
+ 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(mockDc.getNetworkType()).thenReturn(com.cloud.dc.DataCenter.NetworkType.Advanced);
+
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(Command.OnError.Continue);
+ 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(Command.OnError.Continue);
+ 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<Integer> configKey =
NetworkOrchestrationService.DhcpLeaseTimeout;
+ Assert.assertEquals("Default value should be 0 for infinite lease",
"0", configKey.defaultValue());
+
+ Commands cmds = new Commands(Command.OnError.Continue);
+ 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(Command.OnError.Continue);
+ 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(Command.OnError.Continue);
+ 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 a2309067289..4840b5ed724 100755
--- a/systemvm/debian/opt/cloud/bin/cs/CsDhcp.py
+++ b/systemvm/debian/opt/cloud/bin/cs/CsDhcp.py
@@ -199,12 +199,14 @@ class CsDhcp(CsDataBag):
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 6f66331c88e..976f009c2b1 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 <MAC address> -4 <IPv4 address> -6 <IPv6 address> -h
<hostname> -d <default router> -n <name server address> -s <Routes> -u <DUID>
[-N]\n" $(basename $0) >&2
+ printf "Usage: %s: -m <MAC address> -4 <IPv4 address> -6 <IPv6 address> -h
<hostname> -d <default router> -n <name server address> -s <Routes> -u <DUID>
-l <lease time> [-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