Merge ~cgrabowski/maas:backport_fix_node_dns_generation_to_3.3 into maas:3.3

Proposed by Christian Grabowski
Status: Merged
Approved by: Christian Grabowski
Approved revision: 741b75b12b146c0a61910a822a3c35c6fc52936e
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~cgrabowski/maas:backport_fix_node_dns_generation_to_3.3
Merge into: maas:3.3
Diff against target: 638 lines (+498/-10)
6 files modified
src/maasserver/dns/config.py (+29/-9)
src/maasserver/dns/tests/test_config.py (+38/-1)
src/maasserver/triggers/system.py (+152/-0)
src/maasserver/triggers/tests/test_init.py (+5/-0)
src/maasserver/triggers/tests/test_system.py (+271/-0)
src/provisioningserver/dns/zoneconfig.py (+3/-0)
Reviewer Review Type Date Requested Status
Christian Grabowski Approve
Review via email: mp+434580@code.launchpad.net

Commit message

add trigger when existing static ip updates
add DELETE-IFACE-IP
add triggers for interface to ip dynamic DNS updates
(cherry picked from commit 147cc75221abc14a66bb8737057663065b5f6173)

To post a comment you must log in.
Revision history for this message
Christian Grabowski (cgrabowski) wrote :

top-approving backport

Revision history for this message
Christian Grabowski (cgrabowski) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/maasserver/dns/config.py b/src/maasserver/dns/config.py
2index c484570..a0390c0 100644
3--- a/src/maasserver/dns/config.py
4+++ b/src/maasserver/dns/config.py
5@@ -21,6 +21,7 @@ from maasserver.models.dnsdata import DNSData
6 from maasserver.models.dnspublication import DNSPublication
7 from maasserver.models.dnsresource import DNSResource
8 from maasserver.models.domain import Domain
9+from maasserver.models.interface import Interface
10 from maasserver.models.node import RackController
11 from maasserver.models.subnet import Subnet
12 from provisioningserver.dns.actions import (
13@@ -348,7 +349,7 @@ def process_dns_update_notify(message):
14 case _:
15 # special case where we know an IP has been deleted but, we can't fetch the value
16 # and the rrecord may still have other answers
17- if op == "DELETE-IP":
18+ if op == "DELETE-IP" or op == "DELETE-IFACE-IP":
19 updates.append(
20 DynamicDNSUpdate.create_from_trigger(
21 operation="DELETE",
22@@ -366,23 +367,42 @@ def process_dns_update_notify(message):
23 rectype="AAAA",
24 )
25 )
26- resource = DNSResource.objects.get(
27- name=update_list[2], domain__name=zone
28- )
29+
30+ ttl = None
31+ ip_addresses = []
32+ if op == "DELETE-IP":
33+ resource = DNSResource.objects.get(
34+ name=update_list[2], domain__name=zone
35+ )
36+ ttl = (
37+ int(resource.address_ttl)
38+ if resource.address_ttl
39+ else None
40+ )
41+ ip_addresses = list(
42+ resource.ip_addresses.exclude(ip__isnull=True)
43+ )
44+ else:
45+ iface_id = int(update_list[-1])
46+ iface = Interface.objects.get(id=iface_id)
47+ default_domain = Domain.objects.get_default_domain()
48+ ttl = (
49+ int(default_domain.ttl) if default_domain.ttl else None
50+ )
51+ ip_addresses = list(
52+ iface.ip_addresses.exclude(ip__isnull=True)
53+ )
54 updates += [
55 DynamicDNSUpdate.create_from_trigger(
56 operation="INSERT",
57 zone=zone,
58 name=name,
59 rectype=rectype,
60- ttl=int(resource.address_ttl)
61- if resource.address_ttl
62- else None,
63+ ttl=ttl,
64 answer=ip.ip,
65 )
66- for ip in resource.ip_addresses.all()
67+ for ip in ip_addresses
68 ]
69-
70 elif len(update_list) > 4: # has an answer
71 updates.append(
72 DynamicDNSUpdate.create_from_trigger(
73diff --git a/src/maasserver/dns/tests/test_config.py b/src/maasserver/dns/tests/test_config.py
74index 9a2835d..0a96814 100644
75--- a/src/maasserver/dns/tests/test_config.py
76+++ b/src/maasserver/dns/tests/test_config.py
77@@ -873,7 +873,10 @@ class TestProcessDNSUpdateNotify(MAASServerTestCase):
78 domain = factory.make_Domain()
79 resource = factory.make_DNSResource(domain=domain)
80 ip = resource.ip_addresses.first().ip
81- ip2 = factory.make_StaticIPAddress()
82+ subnet = factory.make_Subnet()
83+ ip2 = factory.make_StaticIPAddress(
84+ subnet=subnet, ip=subnet.get_next_ip_for_allocation()[0]
85+ )
86 resource.ip_addresses.add(ip2)
87 message = f"DELETE-IP {domain.name} {resource.name} A {resource.address_ttl if resource.address_ttl else 60} {ip}"
88 resource.ip_addresses.first().delete()
89@@ -902,3 +905,37 @@ class TestProcessDNSUpdateNotify(MAASServerTestCase):
90 ],
91 result,
92 )
93+
94+ def test_delete_iface_ip(self):
95+ domain = factory.make_Domain()
96+ node = factory.make_Node_with_Interface_on_Subnet()
97+ iface = node.current_config.interface_set.first()
98+ ip1 = iface.ip_addresses.first()
99+ ip2 = factory.make_StaticIPAddress(interface=iface)
100+ ip1.delete()
101+ message = f"DELETE-IFACE-IP {domain.name} {node.hostname} A {domain.ttl if domain.ttl else 60} {iface.id}"
102+ result, _ = process_dns_update_notify(message)
103+ self.assertCountEqual(
104+ [
105+ DynamicDNSUpdate(
106+ operation="DELETE",
107+ zone=domain.name,
108+ name=f"{node.hostname}.{domain.name}",
109+ rectype="A",
110+ ),
111+ DynamicDNSUpdate(
112+ operation="DELETE",
113+ zone=domain.name,
114+ name=f"{node.hostname}.{domain.name}",
115+ rectype="AAAA",
116+ ),
117+ DynamicDNSUpdate(
118+ operation="INSERT",
119+ zone=domain.name,
120+ name=f"{node.hostname}.{domain.name}",
121+ rectype="A" if IPAddress(ip2.ip).version == 4 else "AAAA",
122+ answer=ip2.ip,
123+ ),
124+ ],
125+ result,
126+ )
127diff --git a/src/maasserver/triggers/system.py b/src/maasserver/triggers/system.py
128index a18fe8a..3f7f328 100644
129--- a/src/maasserver/triggers/system.py
130+++ b/src/maasserver/triggers/system.py
131@@ -2054,6 +2054,124 @@ def render_dns_dynamic_update_subnet_procedure(op):
132 )
133
134
135+def render_dns_dynamic_update_interface_static_ip_address(op):
136+ return dedent(
137+ f"""\
138+ CREATE OR REPLACE FUNCTION sys_dns_updates_interface_ip_{op}()
139+ RETURNS trigger as $$
140+ DECLARE
141+ current_hostname text;
142+ domain text;
143+ iface_name text;
144+ ip_addr text;
145+ address_ttl int;
146+ BEGIN
147+ ASSERT TG_WHEN = 'AFTER', 'May only run as an AFTER trigger';
148+ ASSERT TG_LEVEL <> 'STATEMENT', 'Should not be used as a STATEMENT level trigger', TG_NAME;
149+ IF (TG_OP = 'INSERT' AND TG_LEVEL = 'ROW') THEN
150+ SELECT iface.name, node.hostname, domain_tbl.name, COALESCE(domain_tbl.ttl, 0) INTO iface_name, current_hostname, domain, address_ttl
151+ FROM maasserver_interface AS iface
152+ JOIN maasserver_node AS node ON iface.node_config_id = node.current_config_id
153+ JOIN maasserver_domain AS domain_tbl ON domain_tbl.id=node.domain_id WHERE iface.id=NEW.interface_id;
154+ SELECT host(ip) INTO ip_addr FROM maasserver_staticipaddress WHERE id=NEW.staticipaddress_id;
155+ PERFORM pg_notify('sys_dns_updates', 'INSERT ' || domain || ' ' || current_hostname || ' A ' || address_ttl || ' ' || ip_addr);
156+ PERFORM pg_notify('sys_dns_updates', 'INSERT ' || domain || ' ' || iface_name || '.' || current_hostname || ' A ' || address_ttl || ' ' || ip_addr);
157+ ELSIF (TG_OP = 'DELETE' AND TG_LEVEL = 'ROW') THEN
158+ IF EXISTS(SELECT id FROM maasserver_interface WHERE id=OLD.interface_id) THEN
159+ SELECT iface.name, node.hostname, domain_tbl.name, COALESCE(domain_tbl.ttl, 0) INTO iface_name, current_hostname, domain, address_ttl
160+ FROM maasserver_interface AS iface
161+ JOIN maasserver_node AS node ON iface.node_config_id = node.current_config_id
162+ JOIN maasserver_domain AS domain_tbl ON domain_tbl.id=node.domain_id WHERE iface.id=OLD.interface_id;
163+ IF EXISTS(SELECT id FROM maasserver_staticipaddress WHERE id=OLD.staticipaddress_id) THEN
164+ SELECT host(ip) INTO ip_addr FROM maasserver_staticipaddress WHERE id=OLD.staticipaddress_id;
165+ PERFORM pg_notify('sys_dns_updates', 'DELETE ' || domain || ' ' || current_hostname || ' A ' || ip_addr);
166+ PERFORM pg_notify('sys_dns_updates', 'DELETE ' || domain || ' ' || iface_name || '.' || current_hostname || ' A ' || ip_addr);
167+ ELSE
168+ PERFORM pg_notify('sys_dns_updates', 'DELETE-IFACE-IP ' || domain || ' ' || current_hostname || ' A ' || OLD.interface_id);
169+ PERFORM pg_notify('sys_dns_updates', 'DELETE-IFACE-IP ' || domain || ' ' || current_hostname || ' AAAA ' || OLD.interface_id);
170+ END IF;
171+ END IF;
172+ END IF;
173+ RETURN NULL;
174+ END;
175+ $$ LANGUAGE plpgsql;
176+ """
177+ )
178+
179+
180+dns_dynamic_update_static_ip_address_update = dedent(
181+ """\
182+ CREATE OR REPLACE FUNCTION sys_dns_updates_ip_update()
183+ RETURNS trigger as $$
184+ DECLARE
185+ current_hostname text;
186+ domain text;
187+ iface_name text;
188+ address_ttl int;
189+ current_interface_id bigint;
190+ BEGIN
191+ IF NEW IS DISTINCT FROM OLD THEN
192+ IF EXISTS(SELECT id FROM maasserver_interface_ip_addresses WHERE staticipaddress_id=NEW.id) THEN
193+ SELECT interface_id INTO current_interface_id FROM maasserver_interface_ip_addresses WHERE staticipaddress_id=NEW.id;
194+ SELECT iface.name, node.hostname, domain_tbl.name, COALESCE(domain_tbl.ttl, 0) INTO iface_name, current_hostname, domain, address_ttl
195+ FROM maasserver_interface AS iface
196+ JOIN maasserver_node AS node ON iface.node_config_id = node.current_config_id
197+ JOIN maasserver_domain AS domain_tbl ON domain_tbl.id=node.domain_id WHERE iface.id=current_interface_id;
198+ IF OLD.ip IS NOT NULL THEN
199+ PERFORM pg_notify('sys_dns_updates', 'DELETE ' || domain || ' ' || current_hostname || ' A ' || host(OLD.ip));
200+ PERFORM pg_notify('sys_dns_updates', 'DELETE ' || domain || ' ' || iface_name || '.' || current_hostname || ' A ' || host(OLD.ip));
201+ END IF;
202+ PERFORM pg_notify('sys_dns_updates', 'INSERT ' || domain || ' ' || current_hostname || ' A ' || address_ttl || ' ' || host(NEW.ip));
203+ PERFORM pg_notify('sys_dns_updates', 'INSERT ' || domain || ' ' || iface_name || '.' || current_hostname || ' A ' || address_ttl || ' ' || host(NEW.ip));
204+ END IF;
205+ END IF;
206+ RETURN NULL;
207+ END;
208+ $$ LANGUAGE plpgsql;
209+ """
210+)
211+
212+dns_dynamic_update_node_delete = dedent(
213+ """\
214+ CREATE OR REPLACE FUNCTION sys_dns_updates_maasserver_node_delete()
215+ RETURNS trigger as $$
216+ DECLARE
217+ hostname text;
218+ domain text;
219+ address_ttl int;
220+ BEGIN
221+ SELECT name, COALESCE(ttl, 0) INTO domain, address_ttl FROM maasserver_domain WHERE id=OLD.domain_id;
222+ PERFORM pg_notify('sys_dns_updates', 'DELETE ' || domain || ' ' || OLD.hostname || ' A');
223+ RETURN NULL;
224+ END;
225+ $$ LANGUAGE plpgsql;
226+ """
227+)
228+
229+
230+dns_dynamic_update_interface_delete = dedent(
231+ """\
232+ CREATE OR REPLACE FUNCTION sys_dns_updates_maasserver_interface_delete()
233+ RETURNS trigger as $$
234+ DECLARE
235+ current_hostname text;
236+ current_domain_id bigint;
237+ domain text;
238+ current_node_id bigint;
239+ BEGIN
240+ IF EXISTS(SELECT id FROM maasserver_nodeconfig WHERE id=OLD.node_config_id) THEN
241+ SELECT node_id INTO current_node_id FROM maasserver_nodeconfig WHERE id=OLD.node_config_id;
242+ SELECT hostname, domain_id INTO current_hostname, current_domain_id FROM maasserver_node WHERE id=current_node_id;
243+ SELECT name INTO domain FROM maasserver_domain WHERE id=current_domain_id;
244+ PERFORM pg_notify('sys_dns_updates', 'DELETE ' || domain || ' ' || OLD.name || '.' || current_hostname || ' A');
245+ END IF;
246+ RETURN NULL;
247+ END;
248+ $$ LANGUAGE plpgsql;
249+ """
250+)
251+
252+
253 def render_sys_proxy_procedure(proc_name, on_delete=False):
254 """Render a database procedure with name `proc_name` that notifies that a
255 proxy update is needed.
256@@ -2403,3 +2521,37 @@ def register_system_triggers():
257 "sys_dns_updates_maasserver_subnet_delete",
258 "delete",
259 )
260+ register_procedure(
261+ render_dns_dynamic_update_interface_static_ip_address("insert")
262+ )
263+ register_trigger(
264+ "maasserver_interface_ip_addresses",
265+ "sys_dns_updates_interface_ip_insert",
266+ "insert",
267+ )
268+ register_procedure(
269+ render_dns_dynamic_update_interface_static_ip_address("delete")
270+ )
271+ register_trigger(
272+ "maasserver_interface_ip_addresses",
273+ "sys_dns_updates_interface_ip_delete",
274+ "delete",
275+ )
276+ register_procedure(dns_dynamic_update_static_ip_address_update)
277+ register_trigger(
278+ "maasserver_staticipaddress",
279+ "sys_dns_updates_ip_update",
280+ "update",
281+ )
282+ register_procedure(dns_dynamic_update_node_delete)
283+ register_trigger(
284+ "maasserver_node",
285+ "sys_dns_updates_maasserver_node_delete",
286+ "delete",
287+ )
288+ register_procedure(dns_dynamic_update_interface_delete)
289+ register_trigger(
290+ "maasserver_interface",
291+ "sys_dns_updates_maasserver_interface_delete",
292+ "delete",
293+ )
294diff --git a/src/maasserver/triggers/tests/test_init.py b/src/maasserver/triggers/tests/test_init.py
295index e4fb3ed..34395f1 100644
296--- a/src/maasserver/triggers/tests/test_init.py
297+++ b/src/maasserver/triggers/tests/test_init.py
298@@ -91,14 +91,18 @@ class TestTriggersUsed(MAASServerTestCase):
299 "domain_sys_dns_updates_maasserver_domain_update",
300 "interface_ip_addresses_sys_dns_nic_ip_link",
301 "interface_ip_addresses_sys_dns_nic_ip_unlink",
302+ "interface_ip_addresses_sys_dns_updates_interface_ip_insert",
303+ "interface_ip_addresses_sys_dns_updates_interface_ip_delete",
304 "interface_sys_dhcp_interface_update",
305 "interface_sys_dns_interface_update",
306+ "interface_sys_dns_updates_maasserver_interface_delete",
307 "iprange_sys_dhcp_iprange_delete",
308 "iprange_sys_dhcp_iprange_insert",
309 "iprange_sys_dhcp_iprange_update",
310 "node_sys_dhcp_node_update",
311 "node_sys_dns_node_delete",
312 "node_sys_dns_node_update",
313+ "node_sys_dns_updates_maasserver_node_delete",
314 "rbacsync_sys_rbac_sync",
315 "regionrackrpcconnection_sys_core_rpc_delete",
316 "regionrackrpcconnection_sys_core_rpc_insert",
317@@ -109,6 +113,7 @@ class TestTriggersUsed(MAASServerTestCase):
318 "staticipaddress_sys_dhcp_staticipaddress_insert",
319 "staticipaddress_sys_dhcp_staticipaddress_update",
320 "staticipaddress_sys_dns_staticipaddress_update",
321+ "staticipaddress_sys_dns_updates_ip_update",
322 "subnet_sys_dns_updates_maasserver_subnet_delete",
323 "subnet_sys_dns_updates_maasserver_subnet_insert",
324 "subnet_sys_dns_updates_maasserver_subnet_update",
325diff --git a/src/maasserver/triggers/tests/test_system.py b/src/maasserver/triggers/tests/test_system.py
326index d5222b4..4337984 100644
327--- a/src/maasserver/triggers/tests/test_system.py
328+++ b/src/maasserver/triggers/tests/test_system.py
329@@ -7,6 +7,8 @@ from contextlib import closing
330 from django.db import connection
331 from twisted.internet.defer import inlineCallbacks
332
333+from maasserver.enum import NODE_STATUS
334+from maasserver.models import Domain
335 from maasserver.models.dnspublication import zone_serial
336 from maasserver.testing.factory import factory
337 from maasserver.testing.testcase import (
338@@ -367,3 +369,272 @@ class TestSysDNSUpdates(
339 finally:
340 self.stop_reading()
341 yield self.postgres_listener_service.stopService()
342+
343+ @wait_for_reactor
344+ @inlineCallbacks
345+ def test_dns_dynamic_update_interface_static_ip_address_insert(self):
346+ listener = self.make_listener_without_delay()
347+ yield self.set_service(listener)
348+ yield deferToDatabase(
349+ self.register_trigger,
350+ "maasserver_interface_ip_addresses",
351+ "sys_dns_updates",
352+ ops=("insert",),
353+ trigger="sys_dns_updates_interface_ip_insert",
354+ )
355+ vlan = yield deferToDatabase(self.create_vlan)
356+ subnet = yield deferToDatabase(
357+ self.create_subnet, params={"vlan": vlan}
358+ )
359+ self.start_reading()
360+ try:
361+ node = yield deferToDatabase(
362+ self.create_node_with_interface,
363+ params={"subnet": subnet, "status": NODE_STATUS.DEPLOYED},
364+ )
365+ domain = yield deferToDatabase(Domain.objects.get_default_domain)
366+ expected_iface = yield deferToDatabase(
367+ lambda: node.current_config.interface_set.first()
368+ )
369+ expected_ip = yield deferToDatabase(
370+ lambda: self.create_staticipaddress(
371+ params={
372+ "ip": subnet.get_next_ip_for_allocation()[0],
373+ "interface": expected_iface,
374+ "subnet": subnet,
375+ }
376+ )
377+ )
378+ msg1 = yield self.get_notify("sys_dns_updates")
379+ self.assertEqual(
380+ msg1,
381+ f"INSERT {domain.name} {node.hostname} A 0 {expected_ip.ip}",
382+ )
383+ msg2 = yield self.get_notify("sys_dns_updates")
384+ self.assertEqual(
385+ msg2,
386+ f"INSERT {domain.name} {expected_iface.name}.{node.hostname} A 0 {expected_ip.ip}",
387+ )
388+ finally:
389+ self.stop_reading()
390+ yield self.postgres_listener_service.stopService()
391+
392+ @wait_for_reactor
393+ @inlineCallbacks
394+ def test_dns_dynamic_update_interface_static_ip_address_insert_with_non_default_domain(
395+ self,
396+ ):
397+ listener = self.make_listener_without_delay()
398+ yield self.set_service(listener)
399+ yield deferToDatabase(
400+ self.register_trigger,
401+ "maasserver_interface_ip_addresses",
402+ "sys_dns_updates",
403+ ops=("insert",),
404+ trigger="sys_dns_updates_interface_ip_insert",
405+ )
406+ vlan = yield deferToDatabase(self.create_vlan)
407+ subnet = yield deferToDatabase(
408+ self.create_subnet, params={"vlan": vlan}
409+ )
410+ domain = yield deferToDatabase(self.create_domain)
411+ self.start_reading()
412+ try:
413+ node = yield deferToDatabase(
414+ self.create_node_with_interface,
415+ params={
416+ "subnet": subnet,
417+ "status": NODE_STATUS.DEPLOYED,
418+ "domain": domain,
419+ },
420+ )
421+ expected_iface = yield deferToDatabase(
422+ lambda: node.current_config.interface_set.first()
423+ )
424+ expected_ip = yield deferToDatabase(
425+ lambda: self.create_staticipaddress(
426+ params={
427+ "ip": subnet.get_next_ip_for_allocation()[0],
428+ "interface": expected_iface,
429+ "subnet": subnet,
430+ }
431+ )
432+ )
433+ msg1 = yield self.get_notify("sys_dns_updates")
434+ self.assertEqual(
435+ msg1,
436+ f"INSERT {domain.name} {node.hostname} A 0 {expected_ip.ip}",
437+ )
438+ msg2 = yield self.get_notify("sys_dns_updates")
439+ self.assertEqual(
440+ msg2,
441+ f"INSERT {domain.name} {expected_iface.name}.{node.hostname} A 0 {expected_ip.ip}",
442+ )
443+ finally:
444+ self.stop_reading()
445+ yield self.postgres_listener_service.stopService()
446+
447+ @wait_for_reactor
448+ @inlineCallbacks
449+ def test_dns_dynamic_update_interface_static_ip_address_delete(self):
450+ listener = self.make_listener_without_delay()
451+ yield self.set_service(listener)
452+ yield deferToDatabase(
453+ self.register_trigger,
454+ "maasserver_interface_ip_addresses",
455+ "sys_dns_updates",
456+ ops=("delete",),
457+ trigger="sys_dns_updates_interface_ip_delete",
458+ )
459+ vlan = yield deferToDatabase(self.create_vlan)
460+ subnet = yield deferToDatabase(
461+ self.create_subnet, params={"vlan": vlan}
462+ )
463+ node = yield deferToDatabase(
464+ self.create_node_with_interface,
465+ params={"subnet": subnet, "status": NODE_STATUS.DEPLOYED},
466+ )
467+ domain = yield deferToDatabase(Domain.objects.get_default_domain)
468+ iface = yield deferToDatabase(
469+ lambda: node.current_config.interface_set.first()
470+ )
471+ ip1 = yield deferToDatabase(
472+ lambda: self.create_staticipaddress(
473+ params={
474+ "ip": subnet.get_next_ip_for_allocation()[0],
475+ "interface": iface,
476+ "subnet": subnet,
477+ }
478+ )
479+ )
480+ self.start_reading()
481+ try:
482+ yield deferToDatabase(iface.unlink_ip_address, ip1)
483+ msg1 = yield self.get_notify("sys_dns_updates")
484+ self.assertEqual(
485+ msg1, f"DELETE {domain.name} {node.hostname} A {ip1.ip}"
486+ )
487+ msg2 = yield self.get_notify("sys_dns_updates")
488+ self.assertEqual(
489+ msg2,
490+ f"DELETE {domain.name} {iface.name}.{node.hostname} A {ip1.ip}",
491+ )
492+ finally:
493+ self.stop_reading()
494+ yield self.postgres_listener_service.stopService()
495+
496+ @wait_for_reactor
497+ @inlineCallbacks
498+ def test_dns_dynamc_update_ip_update(self):
499+ listener = self.make_listener_without_delay()
500+ yield self.set_service(listener)
501+ yield deferToDatabase(
502+ self.register_trigger,
503+ "maasserver_staticipaddress",
504+ "sys_dns_updates",
505+ ops=("update",),
506+ trigger="sys_dns_updates_ip_update",
507+ )
508+ vlan = yield deferToDatabase(self.create_vlan)
509+ subnet = yield deferToDatabase(
510+ self.create_subnet, params={"vlan": vlan}
511+ )
512+ node = yield deferToDatabase(
513+ self.create_node_with_interface,
514+ params={"subnet": subnet, "status": NODE_STATUS.DEPLOYED},
515+ )
516+ domain = yield deferToDatabase(Domain.objects.get_default_domain)
517+ iface = yield deferToDatabase(
518+ lambda: node.current_config.interface_set.first()
519+ )
520+ ip = yield deferToDatabase(
521+ lambda: self.create_staticipaddress(
522+ params={
523+ "ip": subnet.get_next_ip_for_allocation()[0],
524+ "interface": iface,
525+ "subnet": subnet,
526+ }
527+ )
528+ )
529+ old_ip = ip.ip
530+
531+ def _set_new_ip():
532+ ip.ip = subnet.get_next_ip_for_allocation()[0]
533+ ip.save()
534+
535+ self.start_reading()
536+ try:
537+ yield deferToDatabase(_set_new_ip)
538+ msg1 = yield self.get_notify("sys_dns_updates")
539+ self.assertEqual(
540+ msg1, f"DELETE {domain.name} {node.hostname} A {old_ip}"
541+ )
542+ msg2 = yield self.get_notify("sys_dns_updates")
543+ self.assertEqual(
544+ msg2,
545+ f"DELETE {domain.name} {iface.name}.{node.hostname} A {old_ip}",
546+ )
547+ msg3 = yield self.get_notify("sys_dns_updates")
548+ self.assertEqual(
549+ msg3, f"INSERT {domain.name} {node.hostname} A 0 {ip.ip}"
550+ )
551+ msg4 = yield self.get_notify("sys_dns_updates")
552+ self.assertEqual(
553+ msg4,
554+ f"INSERT {domain.name} {iface.name}.{node.hostname} A 0 {ip.ip}",
555+ )
556+ finally:
557+ self.stop_reading()
558+ yield self.postgres_listener_service.stopService()
559+
560+ @wait_for_reactor
561+ @inlineCallbacks
562+ def test_dns_dynamic_update_node_delete(self):
563+ listener = self.make_listener_without_delay()
564+ yield self.set_service(listener)
565+ yield deferToDatabase(
566+ self.register_trigger,
567+ "maasserver_node",
568+ "sys_dns_updates",
569+ ops=("delete",),
570+ )
571+ node = yield deferToDatabase(self.create_node)
572+ domain = yield deferToDatabase(Domain.objects.get_default_domain)
573+ self.start_reading()
574+ try:
575+ yield deferToDatabase(node.delete)
576+ msg = yield self.get_notify("sys_dns_updates")
577+ self.assertEqual(msg, f"DELETE {domain.name} {node.hostname} A")
578+ finally:
579+ self.stop_reading()
580+ yield self.postgres_listener_service.stopService()
581+
582+ @wait_for_reactor
583+ @inlineCallbacks
584+ def test_dns_dynamic_update_interface_delete(self):
585+ listener = self.make_listener_without_delay()
586+ yield self.set_service(listener)
587+ yield deferToDatabase(
588+ self.register_trigger,
589+ "maasserver_node",
590+ "sys_dns_updates",
591+ ops=("delete",),
592+ )
593+ subnet = yield deferToDatabase(self.create_subnet)
594+ node = yield deferToDatabase(
595+ self.create_node_with_interface, params={"subnet": subnet}
596+ )
597+ domain = yield deferToDatabase(Domain.objects.get_default_domain)
598+ iface = yield deferToDatabase(
599+ lambda: node.current_config.interface_set.first()
600+ )
601+ self.start_reading()
602+ try:
603+ yield deferToDatabase(iface.delete)
604+ msg = yield self.get_notify("sys_dns_updates")
605+ self.assertEqual(
606+ msg, f"DELETE {domain.name} {iface.name}.{node.hostname} A"
607+ )
608+ finally:
609+ self.stop_reading()
610+ yield self.postgres_listener_service.stopService()
611diff --git a/src/provisioningserver/dns/zoneconfig.py b/src/provisioningserver/dns/zoneconfig.py
612index 13dca69..eed539a 100644
613--- a/src/provisioningserver/dns/zoneconfig.py
614+++ b/src/provisioningserver/dns/zoneconfig.py
615@@ -7,6 +7,7 @@
616 from datetime import datetime
617 from itertools import chain
618 import os
619+from pathlib import Path
620
621 from netaddr import IPAddress, IPNetwork, spanning_cidr
622 from netaddr.core import AddrFormatError
623@@ -323,6 +324,7 @@ class DNSForwardZoneConfig(DomainConfigBase):
624 if not self.force_config_write and self.zone_file_exists(zi):
625 self.dynamic_update(zi)
626 else:
627+ Path(f"{zi.target_path}.jnl").unlink(missing_ok=True)
628 self.requires_reload = True
629 self.write_zone_file(
630 zi.target_path,
631@@ -612,6 +614,7 @@ class DNSReverseZoneConfig(DomainConfigBase):
632 if not self.force_config_write and self.zone_file_exists(zi):
633 self.dynamic_update(zi)
634 else:
635+ Path(f"{zi.target_path}.jnl").unlink(missing_ok=True)
636 self.requires_reload = True
637 self.write_zone_file(
638 zi.target_path,

Subscribers

People subscribed via source and target branches