Skip to content

Commit 70a586e

Browse files
committed
vr: wait and restart dnsmasq once per run, not once per leftover lease
When the release of several leases did not take, each of them waited up to 2 s and restarted dnsmasq on its own, so a run with many leftover leases kept DHCP and DNS on the router going down again and again. Send all the releases first, wait for them together, then take whatever is left out of the leases file in one pass and restart dnsmasq at most once. When dnsmasq releases the leases, as it normally does, nothing is restarted, as before.
1 parent d9058a9 commit 70a586e

2 files changed

Lines changed: 37 additions & 25 deletions

File tree

‎systemvm/debian/opt/cloud/bin/cs/CsDhcp.py‎

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ def delete_leases(self):
163163
for host in open(DHCP_HOSTS):
164164
macs_dhcphosts.append(host.split(',')[0])
165165

166-
removed = 0
166+
released = []
167167
# read it all first: dnsmasq and remove_lease rewrite the file in place
168168
with open(LEASES) as fp:
169169
leaselines = fp.readlines()
@@ -175,11 +175,11 @@ def delete_leases(self):
175175
logging.info("Releasing DHCP lease for IP: %s, mac: %s", ip, mac)
176176
if not self.cl.is_redundant() or self.cl.is_primary():
177177
self.release_lease(ip, mac)
178-
if self.ensure_lease_removed(ip):
179-
logging.info("Lease for %s still existed after the release; removed manually", ip)
180-
removed = removed + 1
178+
released.append(ip)
181179
self.del_host(ip)
182-
logging.info("Deleted %s entries from dnsmasq.leases file" % str(removed))
180+
if released:
181+
self.ensure_leases_removed(released)
182+
logging.info("Deleted %s entries from dnsmasq.leases file" % str(len(released)))
183183
except Exception as e:
184184
logging.error("Caught error while trying to delete entries from dnsmasq.leases file: %s" % e)
185185

@@ -222,7 +222,7 @@ def dnsmasq_writes_leases(self):
222222
except IOError:
223223
return True
224224

225-
def remove_lease(self, ip, restart_dnsmasq=True):
225+
def remove_lease(self, ip):
226226
if not os.path.exists(LEASES):
227227
return False
228228

@@ -239,23 +239,21 @@ def remove_lease(self, ip, restart_dnsmasq=True):
239239
fp.truncate()
240240
finally:
241241
fcntl.flock(fp.fileno(), fcntl.LOCK_UN)
242-
243-
if restart_dnsmasq:
244-
# dnsmasq reads the leases file only when it starts
245-
CsHelper.service("dnsmasq", "try-restart")
246242
return True
247243

248-
def ensure_lease_removed(self, ip):
249-
if not self.dnsmasq_writes_leases():
250-
# nothing else takes the line out of the file
251-
self.remove_lease(ip, restart_dnsmasq=False)
252-
return False
253-
# give dnsmasq time to drop the released lease from the file
254-
for _ in range(20):
255-
if not self.lease_exists(ip):
256-
return False
257-
time.sleep(0.1)
258-
return self.remove_lease(ip)
244+
def ensure_leases_removed(self, ips):
245+
writes_leases = self.dnsmasq_writes_leases()
246+
if writes_leases:
247+
# give dnsmasq time to drop the released leases from the file
248+
for _ in range(20):
249+
if not any(self.lease_exists(ip) for ip in ips):
250+
return
251+
time.sleep(0.1)
252+
left = [ip for ip in ips if self.remove_lease(ip)]
253+
if left and writes_leases:
254+
logging.warning("dnsmasq did not release the leases for %s; removed them from %s", ", ".join(left), LEASES)
255+
# dnsmasq reads the leases file only when it starts; once per run, however many were left
256+
CsHelper.service("dnsmasq", "try-restart")
259257

260258
def preseed(self):
261259
self.add_host("127.0.0.1", "localhost")

‎systemvm/test/TestCsDhcp.py‎

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ def test_remove_lease_in_place(self, service):
8181
self.assertEqual(inode, os.stat(self.leases).st_ino)
8282
with open(self.leases) as f:
8383
self.assertEqual("0 02:00:00:00:00:02 10.1.1.72 vm-b *\n", f.read())
84-
service.assert_called_once_with("dnsmasq", "try-restart")
84+
service.assert_not_called()
8585

8686
def router(self, redundant=False, primary=True):
8787
csdhcp = CsDhcp("dhcpentry", {})
@@ -92,17 +92,28 @@ def router(self, redundant=False, primary=True):
9292

9393
@mock.patch("cs.CsDhcp.time.sleep")
9494
@mock.patch("cs.CsDhcp.CsHelper.service")
95-
def test_lease_left_by_dnsmasq_is_removed_and_dnsmasq_restarted(self, service, sleep):
96-
self.assertTrue(self.router().ensure_lease_removed("10.1.1.71"))
95+
def test_leases_left_by_dnsmasq_are_removed_with_one_wait_and_one_restart(self, service, sleep):
96+
self.router().ensure_leases_removed(["10.1.1.71", "10.1.1.72"])
9797
self.assertEqual(20, sleep.call_count)
9898
service.assert_called_once_with("dnsmasq", "try-restart")
99+
with open(self.leases) as f:
100+
self.assertEqual("", f.read())
101+
102+
@mock.patch("cs.CsDhcp.time.sleep")
103+
@mock.patch("cs.CsDhcp.CsHelper.service")
104+
def test_released_leases_need_no_wait_or_restart(self, service, sleep):
105+
with open(self.leases, "w") as f:
106+
f.write("")
107+
self.router().ensure_leases_removed(["10.1.1.71", "10.1.1.72"])
108+
sleep.assert_not_called()
109+
service.assert_not_called()
99110

100111
@mock.patch("cs.CsDhcp.time.sleep")
101112
@mock.patch("cs.CsDhcp.CsHelper.service")
102113
def test_read_only_leases_file_is_cleaned_without_wait_or_restart(self, service, sleep):
103114
with open(self.managed_lease, "w") as f:
104115
f.write("0\n")
105-
self.router().ensure_lease_removed("10.1.1.71")
116+
self.router().ensure_leases_removed(["10.1.1.71"])
106117
sleep.assert_not_called()
107118
service.assert_not_called()
108119
with open(self.leases) as f:
@@ -137,6 +148,9 @@ def test_all_stale_leases_are_handled_while_the_file_is_rewritten(self, service,
137148
open(os.path.join(self.tmpdir, "dhcphosts.txt"), "w").close()
138149
csdhcp.delete_leases()
139150
self.assertEqual(1000, csdhcp.release_lease.call_count)
151+
# none of them was released (release_lease is a mock): still one wait and one restart
152+
self.assertEqual(20, sleep.call_count)
153+
service.assert_called_once_with("dnsmasq", "try-restart")
140154
with open(self.leases) as f:
141155
self.assertEqual("", f.read())
142156

0 commit comments

Comments
 (0)