]> git.ipfire.org Git - thirdparty/knot-dns.git/commitdiff
server: optimize catalogs_generate() depending on the reload type
authorDaniel Salzman <daniel.salzman@nic.cz>
Thu, 24 Jul 2025 06:37:26 +0000 (08:37 +0200)
committerDaniel Salzman <daniel.salzman@nic.cz>
Mon, 4 Aug 2025 15:00:49 +0000 (17:00 +0200)
src/knot/zone/zonedb-load.c
tests-extra/tests/catalog/generate/data/generic.upd [new file with mode: 0644]
tests-extra/tests/catalog/generate/data/generic.zone [new file with mode: 0644]
tests-extra/tests/catalog/generate/test.py

index a12b6fa0ccb799936791beba1d96d262397731f8..e7dc1658ab36e913139f41187f7c2b2807e60005 100644 (file)
@@ -406,6 +406,15 @@ static void reg_reverse(conf_t *conf, knot_zonedb_t *db_new, zone_t *zone)
        }
 }
 
+static bool same_group(zone_t *old_z, zone_t *new_z)
+{
+       if (old_z->catalog_group == NULL || new_z->catalog_group == NULL) {
+               return (old_z->catalog_group == new_z->catalog_group);
+       } else {
+               return (strcmp(old_z->catalog_group, new_z->catalog_group) == 0);
+       }
+}
+
 static knot_zonedb_t *create_zonedb_commit(conf_t *conf, server_t *server)
 {
        knot_zonedb_t *db_old = server->zone_db; // If NULL, zonedb is beeing initialized.
@@ -437,11 +446,18 @@ static knot_zonedb_t *create_zonedb_commit(conf_t *conf, server_t *server)
                                        }
                                }
                                knot_zonedb_insert(db_new, zone);
+                               catalog_generate_add(zone, db_new, false);
                                reg_reverse(conf, db_new, zone);
                        } else if (type & CONF_IO_TUNSET) {
+                               zone_t *zone = knot_zonedb_find(db_new, name);
                                knot_zonedb_del(db_new, name);
+                               catalog_generate_rem(zone, db_new);
                        } else {
                                zone_t *zone = knot_zonedb_find(db_new, name);
+                               zone_t *old = knot_zonedb_find(db_old, name);
+                               if (!same_group(old, zone)) {
+                                       catalog_generate_add(zone, db_new, true);
+                               }
                                reg_reverse(conf, db_new, zone);
                        }
                }
@@ -489,10 +505,13 @@ static knot_zonedb_t *create_zonedb_catalog(conf_t *conf, server_t *server,
                case CAT_UPD_ADD:
                        zone = add_member_zone(upd, db_new, server, conf);
                        knot_zonedb_insert(db_new, zone);
+                       catalog_generate_add(zone, db_new, false);
                        reg_reverse(conf, db_new, zone);
                        break;
                case CAT_UPD_REM:
+                       zone = knot_zonedb_find(db_new, upd->member);
                        knot_zonedb_del(db_new, upd->member);
+                       catalog_generate_rem(zone, db_new);
                        break;
                case CAT_UPD_UNIQ:
                case CAT_UPD_PROP:
@@ -503,6 +522,10 @@ static knot_zonedb_t *create_zonedb_catalog(conf_t *conf, server_t *server,
                                ptrlist_add(expired_contents, zone_expire(zone, true), NULL);
                                knot_sem_post(&zone->cow_lock);
                        }
+                       zone_t *old = knot_zonedb_find(db_old, upd->member);
+                       if (!same_group(old, zone)) {
+                               catalog_generate_add(zone, db_new, true);
+                       }
                        reg_reverse(conf, db_new, zone);
                        break;
                default:
@@ -514,41 +537,6 @@ static knot_zonedb_t *create_zonedb_catalog(conf_t *conf, server_t *server,
        return db_new;
 }
 
-static bool same_group(zone_t *old_z, zone_t *new_z)
-{
-       if (old_z->catalog_group == NULL || new_z->catalog_group == NULL) {
-               return (old_z->catalog_group == new_z->catalog_group);
-       } else {
-               return (strcmp(old_z->catalog_group, new_z->catalog_group) == 0);
-       }
-}
-
-static void catalogs_generate(struct knot_zonedb *db_new, struct knot_zonedb *db_old)
-{
-       if (db_old != NULL) {
-               knot_zonedb_iter_t *it = knot_zonedb_iter_begin(db_old);
-               for (; !knot_zonedb_iter_finished(it); knot_zonedb_iter_next(it)) {
-                       zone_t *zone = knot_zonedb_iter_val(it);
-                       if (knot_zonedb_find(db_new, zone->name) == NULL) {
-                               catalog_generate_rem(zone, db_new);
-                       }
-               }
-               knot_zonedb_iter_free(it);
-       }
-
-       knot_zonedb_iter_t *it = knot_zonedb_iter_begin(db_new);
-       for (; !knot_zonedb_iter_finished(it); knot_zonedb_iter_next(it)) {
-               zone_t *zone = knot_zonedb_iter_val(it);
-               zone_t *old = knot_zonedb_find(db_old, zone->name);
-               if (old == NULL) {
-                       catalog_generate_add(zone, db_new, false);
-               } else if (!same_group(zone, old)) {
-                       catalog_generate_add(zone, db_new, true);
-               }
-       }
-       knot_zonedb_iter_free(it);
-}
-
 static knot_zonedb_t *create_zonedb_full(conf_t *conf, server_t *server,
                                          list_t *expired_contents)
 {
@@ -631,9 +619,28 @@ static knot_zonedb_t *create_zonedb_full(conf_t *conf, server_t *server,
        }
        catalog_it_free(cat_it);
 
+       /* Update generated catalogs - remove members. */
+       if (db_old != NULL) {
+               knot_zonedb_iter_t *db_it = knot_zonedb_iter_begin(db_old);
+               for (; !knot_zonedb_iter_finished(db_it); knot_zonedb_iter_next(db_it)) {
+                       zone_t *zone = knot_zonedb_iter_val(db_it);
+                       if (knot_zonedb_find(db_new, zone->name) == NULL) {
+                               catalog_generate_rem(zone, db_new);
+                       }
+               }
+               knot_zonedb_iter_free(db_it);
+       }
+
+       /* Update generated catalogs - add members, updated reversed zones. */
        knot_zonedb_iter_t *db_it = knot_zonedb_iter_begin(db_new);
        for (; !knot_zonedb_iter_finished(db_it); knot_zonedb_iter_next(db_it)) {
                zone_t *zone = knot_zonedb_iter_val(db_it);
+               zone_t *old = knot_zonedb_find(db_old, zone->name);
+               if (old == NULL) {
+                       catalog_generate_add(zone, db_new, false);
+               } else if (!same_group(old, zone)) {
+                       catalog_generate_add(zone, db_new, true);
+               }
                reg_reverse(conf, db_new, zone);
        }
        knot_zonedb_iter_free(db_it);
@@ -847,9 +854,6 @@ void zonedb_reload(conf_t *conf, server_t *server, reload_t mode)
                return;
        }
 
-       // TODO: optimize within create_zonedb.
-       catalogs_generate(db_new, server->zone_db);
-
        /* Switch the databases. */
        knot_zonedb_t **db_current = &server->zone_db;
        knot_zonedb_t *db_old = rcu_xchg_pointer(db_current, db_new);
diff --git a/tests-extra/tests/catalog/generate/data/generic.upd b/tests-extra/tests/catalog/generate/data/generic.upd
new file mode 100644 (file)
index 0000000..52bd567
--- /dev/null
@@ -0,0 +1,3 @@
+@      SOA     dns1 hostmaster 10 10800 3600 1209600 7200
+       NS      dns1
+dns1   A       192.0.2.1
diff --git a/tests-extra/tests/catalog/generate/data/generic.zone b/tests-extra/tests/catalog/generate/data/generic.zone
new file mode 100644 (file)
index 0000000..46c183b
--- /dev/null
@@ -0,0 +1,3 @@
+@      SOA     dns1 hostmaster 1 10800 3600 1209600 7200
+       NS      dns1
+dns1   A       192.0.2.1
index 0ffa65c4ea3206335ad4f751f8d339685ce962cc..4d892eb1246016a7932564404d87ea059ad50507 100644 (file)
@@ -3,15 +3,19 @@
 '''Test of Catalog zone generation.'''
 
 from dnstest.test import Test
-from dnstest.utils import set_err, detail_log
+from dnstest.utils import compare, set_err, detail_log
+from dnstest.libknot import libknot
 import os
 import random
+import shutil
 import time
 
+USE_CTL = random.choice([True, False, False])
+
 t = Test()
 
-def wait_for_zonefile(server, role, zonename, max_age, timeout):
-    fn = os.path.join(server.dir, role, zonename + "zone")
+def wait_for_zonefile(server, zonename, max_age, timeout):
+    fn = os.path.join(server.dir, "catalog", zonename + "zone")
     while timeout > 0:
         if os.path.exists(fn):
             age = time.time() - os.path.getmtime(fn)
@@ -40,45 +44,81 @@ slave.cat_hidden(zone)
 slave.dnssec(catz[0]).enable = True
 slave.dnssec(catz[0]).single_type_signing = True
 
+def ctl_begin():
+    ctl = libknot.control.KnotCtl()
+    ctl.connect(os.path.join(master.dir, "knot.sock"))
+    ctl.send_block(cmd="conf-begin")
+    resp = ctl.receive_block()
+    return ctl
+
+def ctl_end(ctl):
+    ctl.send_block(cmd="conf-commit")
+    resp = ctl.receive_block()
+    ctl.send(libknot.control.KnotCtlType.END)
+    ctl.close()
+
+def ctl_add_zone(ctl, zone_name):
+    ctl.send_block(cmd="conf-set", section="zone", item="domain", data=zone_name)
+    resp = ctl.receive_block()
+    ctl.send_block(cmd="conf-set", section="zone", item="template", identifier=zone_name, data="catalog-default")
+    resp = ctl.receive_block()
+    ctl.send_block(cmd="conf-set", section="zone", item="catalog-zone", identifier=zone_name, data=catz[0].name)
+    resp = ctl.receive_block()
+    ctl.send_block(cmd="conf-set", section="zone", item="catalog-role", identifier=zone_name, data="member")
+    resp = ctl.receive_block()
+    ctl.send_block(cmd="conf-set", section="zone", item="file", identifier=zone_name, data=os.path.join(master.dir, "generic.zone"))
+    resp = ctl.receive_block()
+
 t.start()
 
 # testcase 1: initial catalog zone with 1 member
 slave.zones_wait(zone)
 
-# testcase 2: adding member zones online/offline
-add_online = random.choice([True, False])
-
+# testcase 2: adding member zones dynamically/online/offline
 zone_add = t.zone("flags.") + t.zone("records.")
-t.link(zone_add, master, slave)
-for z in zone_add:
-    master.cat_member(z, catz)
-    slave.cat_hidden(z)
-
-master.gen_confile()
-
-if add_online:
-    master.reload()
+if USE_CTL:
+    shutil.copy(t.data_dir + "generic.zone", os.path.join(master.dir, "generic.zone"))
+    ctl = ctl_begin()
+    ctl_add_zone(ctl, "flags.")
+    ctl_add_zone(ctl, "records.")
+    ctl_end(ctl)
 else:
-    master.stop()
-    t.sleep(1)
-    master.start()
+    t.link(zone_add, master, slave)
+    for z in zone_add:
+        master.cat_member(z, catz)
+        slave.cat_hidden(z)
+
+    master.gen_confile()
+
+    add_online = random.choice([True, False])
+    if add_online:
+        master.reload()
+    else:
+        master.stop()
+        t.sleep(1)
+        master.start()
 
 slave.zones_wait(zone + zone_add)
 
-# testcase 3: removing member zone online/offline
-rem_online = random.choice([True, False])
-
+# testcase 3: removing member zone dynamically/online/offline
 serial_bef_rem = slave.zone_wait(catz, tsig=True)
 master.ctl("-f zone-purge example.com")
-master.zones.pop("example.com.")
-master.gen_confile()
-
-if rem_online:
-    master.reload()
+if USE_CTL:
+    ctl = ctl_begin()
+    ctl.send_block(cmd="conf-unset", section="zone", item="domain", data="example.com.")
+    resp = ctl.receive_block()
+    ctl_end(ctl)
 else:
-    master.stop()
-    t.sleep(1)
-    master.start()
+    master.zones.pop("example.com.")
+    master.gen_confile()
+
+    add_online = random.choice([True, False])
+    if add_online:
+        master.reload()
+    else:
+        master.stop()
+        t.sleep(1)
+        master.start()
 
 slave.zone_wait(catz, serial_bef_rem, tsig=True)
 t.sleep(2) # allow the member zone to actually be purged
@@ -91,23 +131,52 @@ resp0.check_count(1, "DNSKEY")
 dnskey0 = resp0.resp.answer[0].to_rdataset()
 slave.stop()
 
-temp_rem = master.zones.pop("records.")
-master.gen_confile()
-master.reload()
-t.sleep(7)
-master.ctl("-f zone-purge +orphan records.")
-master.zones["records."] = temp_rem
-master.gen_confile()
-master.reload()
+if USE_CTL:
+    ctl = ctl_begin()
+    ctl.send_block(cmd="conf-unset", section="zone", item="domain", data="records.")
+    resp = ctl.receive_block()
+    ctl_end(ctl)
+    t.sleep(7)
+    master.ctl("-f zone-purge +orphan records.")
+    ctl = ctl_begin()
+    ctl_add_zone(ctl, "records.")
+    ctl_end(ctl)
+else:
+    temp_rem = master.zones.pop("records.")
+    master.gen_confile()
+    master.reload()
+    t.sleep(7)
+    master.ctl("-f zone-purge +orphan records.")
+    master.zones["records."] = temp_rem
+    master.gen_confile()
+    master.reload()
 
 slave.start()
-wait_for_zonefile(slave, "master", "records.", 3, 30)
+wait_for_zonefile(slave, "records.", 3, 30)
 slave.ctl("zone-refresh")
-wait_for_zonefile(slave, "master", "records.", 3, 30)
+wait_for_zonefile(slave, "records.", 3, 30)
 resp1 = slave.dig("records.", "DNSKEY")
 resp1.check_count(1, "DNSKEY")
 dnskey1 = resp1.resp.answer[0].to_rdataset()
 if dnskey0 == dnskey1:
     set_err("ZONE NOT PURGED")
 
+#testcase 5: reload and don't reload a zone depending on the config change
+if USE_CTL:
+    shutil.copy(t.data_dir + "generic.upd", os.path.join(master.dir, "generic.zone"))
+
+    ctl = ctl_begin()
+    ctl.send_block(cmd="conf-set", section="zone", identifier="flags.", item="zone-max-size", data="10000")
+    resp = ctl.receive_block()
+    ctl.send_block(cmd="conf-set", section="zone", identifier="records.", item="comment", data="don't reload")
+    resp = ctl.receive_block()
+    ctl_end(ctl)
+
+    t.sleep(2)
+    resp = master.dig("flags.", "SOA")
+    compare(resp.soa_serial(), 10, "master zone reloaded")
+    resp = master.dig("records.", "SOA")
+    compare(resp.soa_serial(), 1, "master zone not reloaded")
+    slave.zone_wait(zone_add[0], 1)
+
 t.end()