← Back to team overview

sts-sponsors team mailing list archive

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

 

Christian Grabowski has proposed merging ~cgrabowski/maas:backport_fix_node_dns_generation_to_3.3 into maas:3.3.

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)



Requested reviews:
  Christian Grabowski (cgrabowski)

For more details, see:
https://code.launchpad.net/~cgrabowski/maas/+git/maas/+merge/434580
-- 
Your team MAAS Committers is subscribed to branch maas:3.3.
diff --git a/src/maasserver/dns/config.py b/src/maasserver/dns/config.py
index c484570..a0390c0 100644
--- a/src/maasserver/dns/config.py
+++ b/src/maasserver/dns/config.py
@@ -21,6 +21,7 @@ from maasserver.models.dnsdata import DNSData
 from maasserver.models.dnspublication import DNSPublication
 from maasserver.models.dnsresource import DNSResource
 from maasserver.models.domain import Domain
+from maasserver.models.interface import Interface
 from maasserver.models.node import RackController
 from maasserver.models.subnet import Subnet
 from provisioningserver.dns.actions import (
@@ -348,7 +349,7 @@ def process_dns_update_notify(message):
         case _:
             # special case where we know an IP has been deleted but, we can't fetch the value
             # and the rrecord may still have other answers
-            if op == "DELETE-IP":
+            if op == "DELETE-IP" or op == "DELETE-IFACE-IP":
                 updates.append(
                     DynamicDNSUpdate.create_from_trigger(
                         operation="DELETE",
@@ -366,23 +367,42 @@ def process_dns_update_notify(message):
                             rectype="AAAA",
                         )
                     )
-                resource = DNSResource.objects.get(
-                    name=update_list[2], domain__name=zone
-                )
+
+                ttl = None
+                ip_addresses = []
+                if op == "DELETE-IP":
+                    resource = DNSResource.objects.get(
+                        name=update_list[2], domain__name=zone
+                    )
+                    ttl = (
+                        int(resource.address_ttl)
+                        if resource.address_ttl
+                        else None
+                    )
+                    ip_addresses = list(
+                        resource.ip_addresses.exclude(ip__isnull=True)
+                    )
+                else:
+                    iface_id = int(update_list[-1])
+                    iface = Interface.objects.get(id=iface_id)
+                    default_domain = Domain.objects.get_default_domain()
+                    ttl = (
+                        int(default_domain.ttl) if default_domain.ttl else None
+                    )
+                    ip_addresses = list(
+                        iface.ip_addresses.exclude(ip__isnull=True)
+                    )
                 updates += [
                     DynamicDNSUpdate.create_from_trigger(
                         operation="INSERT",
                         zone=zone,
                         name=name,
                         rectype=rectype,
-                        ttl=int(resource.address_ttl)
-                        if resource.address_ttl
-                        else None,
+                        ttl=ttl,
                         answer=ip.ip,
                     )
-                    for ip in resource.ip_addresses.all()
+                    for ip in ip_addresses
                 ]
-
             elif len(update_list) > 4:  # has an answer
                 updates.append(
                     DynamicDNSUpdate.create_from_trigger(
diff --git a/src/maasserver/dns/tests/test_config.py b/src/maasserver/dns/tests/test_config.py
index 9a2835d..0a96814 100644
--- a/src/maasserver/dns/tests/test_config.py
+++ b/src/maasserver/dns/tests/test_config.py
@@ -873,7 +873,10 @@ class TestProcessDNSUpdateNotify(MAASServerTestCase):
         domain = factory.make_Domain()
         resource = factory.make_DNSResource(domain=domain)
         ip = resource.ip_addresses.first().ip
-        ip2 = factory.make_StaticIPAddress()
+        subnet = factory.make_Subnet()
+        ip2 = factory.make_StaticIPAddress(
+            subnet=subnet, ip=subnet.get_next_ip_for_allocation()[0]
+        )
         resource.ip_addresses.add(ip2)
         message = f"DELETE-IP {domain.name} {resource.name} A {resource.address_ttl if resource.address_ttl else 60} {ip}"
         resource.ip_addresses.first().delete()
@@ -902,3 +905,37 @@ class TestProcessDNSUpdateNotify(MAASServerTestCase):
             ],
             result,
         )
+
+    def test_delete_iface_ip(self):
+        domain = factory.make_Domain()
+        node = factory.make_Node_with_Interface_on_Subnet()
+        iface = node.current_config.interface_set.first()
+        ip1 = iface.ip_addresses.first()
+        ip2 = factory.make_StaticIPAddress(interface=iface)
+        ip1.delete()
+        message = f"DELETE-IFACE-IP {domain.name} {node.hostname} A {domain.ttl if domain.ttl else 60} {iface.id}"
+        result, _ = process_dns_update_notify(message)
+        self.assertCountEqual(
+            [
+                DynamicDNSUpdate(
+                    operation="DELETE",
+                    zone=domain.name,
+                    name=f"{node.hostname}.{domain.name}",
+                    rectype="A",
+                ),
+                DynamicDNSUpdate(
+                    operation="DELETE",
+                    zone=domain.name,
+                    name=f"{node.hostname}.{domain.name}",
+                    rectype="AAAA",
+                ),
+                DynamicDNSUpdate(
+                    operation="INSERT",
+                    zone=domain.name,
+                    name=f"{node.hostname}.{domain.name}",
+                    rectype="A" if IPAddress(ip2.ip).version == 4 else "AAAA",
+                    answer=ip2.ip,
+                ),
+            ],
+            result,
+        )
diff --git a/src/maasserver/triggers/system.py b/src/maasserver/triggers/system.py
index a18fe8a..3f7f328 100644
--- a/src/maasserver/triggers/system.py
+++ b/src/maasserver/triggers/system.py
@@ -2054,6 +2054,124 @@ def render_dns_dynamic_update_subnet_procedure(op):
     )
 
 
+def render_dns_dynamic_update_interface_static_ip_address(op):
+    return dedent(
+        f"""\
+        CREATE OR REPLACE FUNCTION sys_dns_updates_interface_ip_{op}()
+        RETURNS trigger as $$
+        DECLARE
+          current_hostname text;
+          domain text;
+          iface_name text;
+          ip_addr text;
+          address_ttl int;
+        BEGIN
+          ASSERT TG_WHEN = 'AFTER', 'May only run as an AFTER trigger';
+          ASSERT TG_LEVEL <> 'STATEMENT', 'Should not be used as a STATEMENT level trigger', TG_NAME;
+          IF (TG_OP = 'INSERT' AND TG_LEVEL = 'ROW') THEN
+            SELECT iface.name, node.hostname, domain_tbl.name, COALESCE(domain_tbl.ttl, 0) INTO iface_name, current_hostname, domain, address_ttl
+              FROM maasserver_interface AS iface
+              JOIN maasserver_node AS node ON iface.node_config_id = node.current_config_id
+              JOIN maasserver_domain AS domain_tbl ON domain_tbl.id=node.domain_id WHERE iface.id=NEW.interface_id;
+            SELECT host(ip) INTO ip_addr FROM maasserver_staticipaddress WHERE id=NEW.staticipaddress_id;
+            PERFORM pg_notify('sys_dns_updates', 'INSERT ' || domain || ' ' || current_hostname || ' A ' || address_ttl || ' ' || ip_addr);
+            PERFORM pg_notify('sys_dns_updates', 'INSERT ' || domain || ' ' || iface_name || '.' || current_hostname || ' A ' || address_ttl || ' ' || ip_addr);
+          ELSIF (TG_OP = 'DELETE' AND TG_LEVEL = 'ROW') THEN
+            IF EXISTS(SELECT id FROM maasserver_interface WHERE id=OLD.interface_id) THEN
+                SELECT iface.name, node.hostname, domain_tbl.name, COALESCE(domain_tbl.ttl, 0) INTO iface_name, current_hostname, domain, address_ttl
+                  FROM maasserver_interface AS iface
+                  JOIN maasserver_node AS node ON iface.node_config_id = node.current_config_id
+                  JOIN maasserver_domain AS domain_tbl ON domain_tbl.id=node.domain_id WHERE iface.id=OLD.interface_id;
+                IF EXISTS(SELECT id FROM maasserver_staticipaddress WHERE id=OLD.staticipaddress_id) THEN
+                  SELECT host(ip) INTO ip_addr FROM maasserver_staticipaddress WHERE id=OLD.staticipaddress_id;
+                  PERFORM pg_notify('sys_dns_updates', 'DELETE ' || domain || ' ' || current_hostname || ' A ' || ip_addr);
+                  PERFORM pg_notify('sys_dns_updates', 'DELETE ' || domain || ' ' || iface_name || '.' || current_hostname || ' A ' || ip_addr);
+                ELSE
+                  PERFORM pg_notify('sys_dns_updates', 'DELETE-IFACE-IP ' || domain || ' ' || current_hostname || ' A ' || OLD.interface_id);
+                  PERFORM pg_notify('sys_dns_updates', 'DELETE-IFACE-IP ' || domain || ' ' || current_hostname || ' AAAA ' || OLD.interface_id);
+                END IF;
+            END IF;
+          END IF;
+          RETURN NULL;
+        END;
+        $$ LANGUAGE plpgsql;
+        """
+    )
+
+
+dns_dynamic_update_static_ip_address_update = dedent(
+    """\
+    CREATE OR REPLACE FUNCTION sys_dns_updates_ip_update()
+    RETURNS trigger as $$
+    DECLARE
+      current_hostname text;
+      domain text;
+      iface_name text;
+      address_ttl int;
+      current_interface_id bigint;
+    BEGIN
+      IF NEW IS DISTINCT FROM OLD THEN
+        IF EXISTS(SELECT id FROM maasserver_interface_ip_addresses WHERE staticipaddress_id=NEW.id) THEN
+          SELECT interface_id INTO current_interface_id FROM maasserver_interface_ip_addresses WHERE staticipaddress_id=NEW.id;
+          SELECT iface.name, node.hostname, domain_tbl.name, COALESCE(domain_tbl.ttl, 0) INTO iface_name, current_hostname, domain, address_ttl
+            FROM maasserver_interface AS iface
+            JOIN maasserver_node AS node ON iface.node_config_id = node.current_config_id
+            JOIN maasserver_domain AS domain_tbl ON domain_tbl.id=node.domain_id WHERE iface.id=current_interface_id;
+          IF OLD.ip IS NOT NULL THEN
+            PERFORM pg_notify('sys_dns_updates', 'DELETE ' || domain || ' ' || current_hostname || ' A ' || host(OLD.ip));
+            PERFORM pg_notify('sys_dns_updates', 'DELETE ' || domain || ' ' || iface_name || '.' || current_hostname || ' A ' || host(OLD.ip));
+          END IF;
+          PERFORM pg_notify('sys_dns_updates', 'INSERT ' || domain || ' ' || current_hostname || ' A ' || address_ttl || ' ' || host(NEW.ip));
+          PERFORM pg_notify('sys_dns_updates', 'INSERT ' || domain || ' ' || iface_name || '.' || current_hostname || ' A ' || address_ttl || ' ' || host(NEW.ip));
+        END IF;
+      END IF;
+      RETURN NULL;
+    END;
+    $$ LANGUAGE plpgsql;
+    """
+)
+
+dns_dynamic_update_node_delete = dedent(
+    """\
+    CREATE OR REPLACE FUNCTION sys_dns_updates_maasserver_node_delete()
+    RETURNS trigger as $$
+    DECLARE
+      hostname text;
+      domain text;
+      address_ttl int;
+    BEGIN
+      SELECT name, COALESCE(ttl, 0) INTO domain, address_ttl FROM maasserver_domain WHERE id=OLD.domain_id;
+      PERFORM pg_notify('sys_dns_updates', 'DELETE ' || domain || ' ' || OLD.hostname || ' A');
+      RETURN NULL;
+    END;
+    $$ LANGUAGE plpgsql;
+    """
+)
+
+
+dns_dynamic_update_interface_delete = dedent(
+    """\
+    CREATE OR REPLACE FUNCTION sys_dns_updates_maasserver_interface_delete()
+    RETURNS trigger as $$
+    DECLARE
+      current_hostname text;
+      current_domain_id bigint;
+      domain text;
+      current_node_id bigint;
+    BEGIN
+      IF EXISTS(SELECT id FROM maasserver_nodeconfig WHERE id=OLD.node_config_id) THEN
+        SELECT node_id INTO current_node_id FROM maasserver_nodeconfig WHERE id=OLD.node_config_id;
+        SELECT hostname, domain_id INTO current_hostname, current_domain_id FROM maasserver_node WHERE id=current_node_id;
+        SELECT name INTO domain FROM maasserver_domain WHERE id=current_domain_id;
+        PERFORM pg_notify('sys_dns_updates', 'DELETE ' || domain || ' ' || OLD.name || '.' || current_hostname || ' A');
+      END IF;
+      RETURN NULL;
+    END;
+    $$ LANGUAGE plpgsql;
+    """
+)
+
+
 def render_sys_proxy_procedure(proc_name, on_delete=False):
     """Render a database procedure with name `proc_name` that notifies that a
     proxy update is needed.
@@ -2403,3 +2521,37 @@ def register_system_triggers():
         "sys_dns_updates_maasserver_subnet_delete",
         "delete",
     )
+    register_procedure(
+        render_dns_dynamic_update_interface_static_ip_address("insert")
+    )
+    register_trigger(
+        "maasserver_interface_ip_addresses",
+        "sys_dns_updates_interface_ip_insert",
+        "insert",
+    )
+    register_procedure(
+        render_dns_dynamic_update_interface_static_ip_address("delete")
+    )
+    register_trigger(
+        "maasserver_interface_ip_addresses",
+        "sys_dns_updates_interface_ip_delete",
+        "delete",
+    )
+    register_procedure(dns_dynamic_update_static_ip_address_update)
+    register_trigger(
+        "maasserver_staticipaddress",
+        "sys_dns_updates_ip_update",
+        "update",
+    )
+    register_procedure(dns_dynamic_update_node_delete)
+    register_trigger(
+        "maasserver_node",
+        "sys_dns_updates_maasserver_node_delete",
+        "delete",
+    )
+    register_procedure(dns_dynamic_update_interface_delete)
+    register_trigger(
+        "maasserver_interface",
+        "sys_dns_updates_maasserver_interface_delete",
+        "delete",
+    )
diff --git a/src/maasserver/triggers/tests/test_init.py b/src/maasserver/triggers/tests/test_init.py
index e4fb3ed..34395f1 100644
--- a/src/maasserver/triggers/tests/test_init.py
+++ b/src/maasserver/triggers/tests/test_init.py
@@ -91,14 +91,18 @@ class TestTriggersUsed(MAASServerTestCase):
         "domain_sys_dns_updates_maasserver_domain_update",
         "interface_ip_addresses_sys_dns_nic_ip_link",
         "interface_ip_addresses_sys_dns_nic_ip_unlink",
+        "interface_ip_addresses_sys_dns_updates_interface_ip_insert",
+        "interface_ip_addresses_sys_dns_updates_interface_ip_delete",
         "interface_sys_dhcp_interface_update",
         "interface_sys_dns_interface_update",
+        "interface_sys_dns_updates_maasserver_interface_delete",
         "iprange_sys_dhcp_iprange_delete",
         "iprange_sys_dhcp_iprange_insert",
         "iprange_sys_dhcp_iprange_update",
         "node_sys_dhcp_node_update",
         "node_sys_dns_node_delete",
         "node_sys_dns_node_update",
+        "node_sys_dns_updates_maasserver_node_delete",
         "rbacsync_sys_rbac_sync",
         "regionrackrpcconnection_sys_core_rpc_delete",
         "regionrackrpcconnection_sys_core_rpc_insert",
@@ -109,6 +113,7 @@ class TestTriggersUsed(MAASServerTestCase):
         "staticipaddress_sys_dhcp_staticipaddress_insert",
         "staticipaddress_sys_dhcp_staticipaddress_update",
         "staticipaddress_sys_dns_staticipaddress_update",
+        "staticipaddress_sys_dns_updates_ip_update",
         "subnet_sys_dns_updates_maasserver_subnet_delete",
         "subnet_sys_dns_updates_maasserver_subnet_insert",
         "subnet_sys_dns_updates_maasserver_subnet_update",
diff --git a/src/maasserver/triggers/tests/test_system.py b/src/maasserver/triggers/tests/test_system.py
index d5222b4..4337984 100644
--- a/src/maasserver/triggers/tests/test_system.py
+++ b/src/maasserver/triggers/tests/test_system.py
@@ -7,6 +7,8 @@ from contextlib import closing
 from django.db import connection
 from twisted.internet.defer import inlineCallbacks
 
+from maasserver.enum import NODE_STATUS
+from maasserver.models import Domain
 from maasserver.models.dnspublication import zone_serial
 from maasserver.testing.factory import factory
 from maasserver.testing.testcase import (
@@ -367,3 +369,272 @@ class TestSysDNSUpdates(
         finally:
             self.stop_reading()
             yield self.postgres_listener_service.stopService()
+
+    @wait_for_reactor
+    @inlineCallbacks
+    def test_dns_dynamic_update_interface_static_ip_address_insert(self):
+        listener = self.make_listener_without_delay()
+        yield self.set_service(listener)
+        yield deferToDatabase(
+            self.register_trigger,
+            "maasserver_interface_ip_addresses",
+            "sys_dns_updates",
+            ops=("insert",),
+            trigger="sys_dns_updates_interface_ip_insert",
+        )
+        vlan = yield deferToDatabase(self.create_vlan)
+        subnet = yield deferToDatabase(
+            self.create_subnet, params={"vlan": vlan}
+        )
+        self.start_reading()
+        try:
+            node = yield deferToDatabase(
+                self.create_node_with_interface,
+                params={"subnet": subnet, "status": NODE_STATUS.DEPLOYED},
+            )
+            domain = yield deferToDatabase(Domain.objects.get_default_domain)
+            expected_iface = yield deferToDatabase(
+                lambda: node.current_config.interface_set.first()
+            )
+            expected_ip = yield deferToDatabase(
+                lambda: self.create_staticipaddress(
+                    params={
+                        "ip": subnet.get_next_ip_for_allocation()[0],
+                        "interface": expected_iface,
+                        "subnet": subnet,
+                    }
+                )
+            )
+            msg1 = yield self.get_notify("sys_dns_updates")
+            self.assertEqual(
+                msg1,
+                f"INSERT {domain.name} {node.hostname} A 0 {expected_ip.ip}",
+            )
+            msg2 = yield self.get_notify("sys_dns_updates")
+            self.assertEqual(
+                msg2,
+                f"INSERT {domain.name} {expected_iface.name}.{node.hostname} A 0 {expected_ip.ip}",
+            )
+        finally:
+            self.stop_reading()
+            yield self.postgres_listener_service.stopService()
+
+    @wait_for_reactor
+    @inlineCallbacks
+    def test_dns_dynamic_update_interface_static_ip_address_insert_with_non_default_domain(
+        self,
+    ):
+        listener = self.make_listener_without_delay()
+        yield self.set_service(listener)
+        yield deferToDatabase(
+            self.register_trigger,
+            "maasserver_interface_ip_addresses",
+            "sys_dns_updates",
+            ops=("insert",),
+            trigger="sys_dns_updates_interface_ip_insert",
+        )
+        vlan = yield deferToDatabase(self.create_vlan)
+        subnet = yield deferToDatabase(
+            self.create_subnet, params={"vlan": vlan}
+        )
+        domain = yield deferToDatabase(self.create_domain)
+        self.start_reading()
+        try:
+            node = yield deferToDatabase(
+                self.create_node_with_interface,
+                params={
+                    "subnet": subnet,
+                    "status": NODE_STATUS.DEPLOYED,
+                    "domain": domain,
+                },
+            )
+            expected_iface = yield deferToDatabase(
+                lambda: node.current_config.interface_set.first()
+            )
+            expected_ip = yield deferToDatabase(
+                lambda: self.create_staticipaddress(
+                    params={
+                        "ip": subnet.get_next_ip_for_allocation()[0],
+                        "interface": expected_iface,
+                        "subnet": subnet,
+                    }
+                )
+            )
+            msg1 = yield self.get_notify("sys_dns_updates")
+            self.assertEqual(
+                msg1,
+                f"INSERT {domain.name} {node.hostname} A 0 {expected_ip.ip}",
+            )
+            msg2 = yield self.get_notify("sys_dns_updates")
+            self.assertEqual(
+                msg2,
+                f"INSERT {domain.name} {expected_iface.name}.{node.hostname} A 0 {expected_ip.ip}",
+            )
+        finally:
+            self.stop_reading()
+            yield self.postgres_listener_service.stopService()
+
+    @wait_for_reactor
+    @inlineCallbacks
+    def test_dns_dynamic_update_interface_static_ip_address_delete(self):
+        listener = self.make_listener_without_delay()
+        yield self.set_service(listener)
+        yield deferToDatabase(
+            self.register_trigger,
+            "maasserver_interface_ip_addresses",
+            "sys_dns_updates",
+            ops=("delete",),
+            trigger="sys_dns_updates_interface_ip_delete",
+        )
+        vlan = yield deferToDatabase(self.create_vlan)
+        subnet = yield deferToDatabase(
+            self.create_subnet, params={"vlan": vlan}
+        )
+        node = yield deferToDatabase(
+            self.create_node_with_interface,
+            params={"subnet": subnet, "status": NODE_STATUS.DEPLOYED},
+        )
+        domain = yield deferToDatabase(Domain.objects.get_default_domain)
+        iface = yield deferToDatabase(
+            lambda: node.current_config.interface_set.first()
+        )
+        ip1 = yield deferToDatabase(
+            lambda: self.create_staticipaddress(
+                params={
+                    "ip": subnet.get_next_ip_for_allocation()[0],
+                    "interface": iface,
+                    "subnet": subnet,
+                }
+            )
+        )
+        self.start_reading()
+        try:
+            yield deferToDatabase(iface.unlink_ip_address, ip1)
+            msg1 = yield self.get_notify("sys_dns_updates")
+            self.assertEqual(
+                msg1, f"DELETE {domain.name} {node.hostname} A {ip1.ip}"
+            )
+            msg2 = yield self.get_notify("sys_dns_updates")
+            self.assertEqual(
+                msg2,
+                f"DELETE {domain.name} {iface.name}.{node.hostname} A {ip1.ip}",
+            )
+        finally:
+            self.stop_reading()
+            yield self.postgres_listener_service.stopService()
+
+    @wait_for_reactor
+    @inlineCallbacks
+    def test_dns_dynamc_update_ip_update(self):
+        listener = self.make_listener_without_delay()
+        yield self.set_service(listener)
+        yield deferToDatabase(
+            self.register_trigger,
+            "maasserver_staticipaddress",
+            "sys_dns_updates",
+            ops=("update",),
+            trigger="sys_dns_updates_ip_update",
+        )
+        vlan = yield deferToDatabase(self.create_vlan)
+        subnet = yield deferToDatabase(
+            self.create_subnet, params={"vlan": vlan}
+        )
+        node = yield deferToDatabase(
+            self.create_node_with_interface,
+            params={"subnet": subnet, "status": NODE_STATUS.DEPLOYED},
+        )
+        domain = yield deferToDatabase(Domain.objects.get_default_domain)
+        iface = yield deferToDatabase(
+            lambda: node.current_config.interface_set.first()
+        )
+        ip = yield deferToDatabase(
+            lambda: self.create_staticipaddress(
+                params={
+                    "ip": subnet.get_next_ip_for_allocation()[0],
+                    "interface": iface,
+                    "subnet": subnet,
+                }
+            )
+        )
+        old_ip = ip.ip
+
+        def _set_new_ip():
+            ip.ip = subnet.get_next_ip_for_allocation()[0]
+            ip.save()
+
+        self.start_reading()
+        try:
+            yield deferToDatabase(_set_new_ip)
+            msg1 = yield self.get_notify("sys_dns_updates")
+            self.assertEqual(
+                msg1, f"DELETE {domain.name} {node.hostname} A {old_ip}"
+            )
+            msg2 = yield self.get_notify("sys_dns_updates")
+            self.assertEqual(
+                msg2,
+                f"DELETE {domain.name} {iface.name}.{node.hostname} A {old_ip}",
+            )
+            msg3 = yield self.get_notify("sys_dns_updates")
+            self.assertEqual(
+                msg3, f"INSERT {domain.name} {node.hostname} A 0 {ip.ip}"
+            )
+            msg4 = yield self.get_notify("sys_dns_updates")
+            self.assertEqual(
+                msg4,
+                f"INSERT {domain.name} {iface.name}.{node.hostname} A 0 {ip.ip}",
+            )
+        finally:
+            self.stop_reading()
+            yield self.postgres_listener_service.stopService()
+
+    @wait_for_reactor
+    @inlineCallbacks
+    def test_dns_dynamic_update_node_delete(self):
+        listener = self.make_listener_without_delay()
+        yield self.set_service(listener)
+        yield deferToDatabase(
+            self.register_trigger,
+            "maasserver_node",
+            "sys_dns_updates",
+            ops=("delete",),
+        )
+        node = yield deferToDatabase(self.create_node)
+        domain = yield deferToDatabase(Domain.objects.get_default_domain)
+        self.start_reading()
+        try:
+            yield deferToDatabase(node.delete)
+            msg = yield self.get_notify("sys_dns_updates")
+            self.assertEqual(msg, f"DELETE {domain.name} {node.hostname} A")
+        finally:
+            self.stop_reading()
+            yield self.postgres_listener_service.stopService()
+
+    @wait_for_reactor
+    @inlineCallbacks
+    def test_dns_dynamic_update_interface_delete(self):
+        listener = self.make_listener_without_delay()
+        yield self.set_service(listener)
+        yield deferToDatabase(
+            self.register_trigger,
+            "maasserver_node",
+            "sys_dns_updates",
+            ops=("delete",),
+        )
+        subnet = yield deferToDatabase(self.create_subnet)
+        node = yield deferToDatabase(
+            self.create_node_with_interface, params={"subnet": subnet}
+        )
+        domain = yield deferToDatabase(Domain.objects.get_default_domain)
+        iface = yield deferToDatabase(
+            lambda: node.current_config.interface_set.first()
+        )
+        self.start_reading()
+        try:
+            yield deferToDatabase(iface.delete)
+            msg = yield self.get_notify("sys_dns_updates")
+            self.assertEqual(
+                msg, f"DELETE {domain.name} {iface.name}.{node.hostname} A"
+            )
+        finally:
+            self.stop_reading()
+            yield self.postgres_listener_service.stopService()
diff --git a/src/provisioningserver/dns/zoneconfig.py b/src/provisioningserver/dns/zoneconfig.py
index 13dca69..eed539a 100644
--- a/src/provisioningserver/dns/zoneconfig.py
+++ b/src/provisioningserver/dns/zoneconfig.py
@@ -7,6 +7,7 @@
 from datetime import datetime
 from itertools import chain
 import os
+from pathlib import Path
 
 from netaddr import IPAddress, IPNetwork, spanning_cidr
 from netaddr.core import AddrFormatError
@@ -323,6 +324,7 @@ class DNSForwardZoneConfig(DomainConfigBase):
             if not self.force_config_write and self.zone_file_exists(zi):
                 self.dynamic_update(zi)
             else:
+                Path(f"{zi.target_path}.jnl").unlink(missing_ok=True)
                 self.requires_reload = True
                 self.write_zone_file(
                     zi.target_path,
@@ -612,6 +614,7 @@ class DNSReverseZoneConfig(DomainConfigBase):
             if not self.force_config_write and self.zone_file_exists(zi):
                 self.dynamic_update(zi)
             else:
+                Path(f"{zi.target_path}.jnl").unlink(missing_ok=True)
                 self.requires_reload = True
                 self.write_zone_file(
                     zi.target_path,

References