Add the incremental change handlers for the en_ts node: an NB
Logical_Switch change that is not a transit-switch mirror is a no-op, and
transit-switch changes are reconciled per transit switch (scoped) instead
of forcing a full recompute of every transit switch.

Add a tests/ovn-ic.at test for the transit-switch handler scoping.

Assisted-by: Claude Opus 4.8, Claude Code
Co-authored-by: Tiago Matos <[email protected]>
Signed-off-by: Tiago Matos <[email protected]>
Signed-off-by: Paulo Guilherme Silva <[email protected]>
---
 ic/en-ts.c       | 158 +++++++++++++++++++++++++
 ic/en-ts.h       |   9 ++
 ic/inc-proc-ic.c |  31 ++++-
 ic/ovn-ic.c      | 293 ++++++++++++++++++++++++++++++++---------------
 ic/ovn-ic.h      |  12 +-
 5 files changed, 401 insertions(+), 102 deletions(-)

diff --git a/ic/en-ts.c b/ic/en-ts.c
index 8745e6aa4..7bad858cb 100644
--- a/ic/en-ts.c
+++ b/ic/en-ts.c
@@ -17,11 +17,26 @@
 #include "en-dp-enum.h"
 #include "en-ts.h"
 #include "lib/inc-proc-eng.h"
+#include "lib/ovn-ic-nb-idl.h"
+#include "lib/ovn-ic-sb-idl.h"
+#include "lib/ovn-nb-idl.h"
+#include "openvswitch/shash.h"
 #include "openvswitch/vlog.h"
+#include "ovsdb-idl.h"
+#include "smap.h"
+#include "sset.h"
 #include "ovn-ic.h"
 
 VLOG_DEFINE_THIS_MODULE(en_ic_ts);
 
+static void
+ts_run(struct ic_context *ctx, struct hmap *dp_tnlids,
+       struct shash *isb_ts_dps)
+{
+    ts_sync_scope(ctx, dp_tnlids, isb_ts_dps, NULL);
+}
+
+
 enum engine_node_state
 en_ts_run(struct engine_node *node, void *data OVS_UNUSED)
 {
@@ -46,6 +61,149 @@ en_ts_run(struct engine_node *node, void *data OVS_UNUSED)
     return EN_UPDATED;
 }
 
+/* Returns the ic_context, or NULL when there is no availability zone yet (in
+ * which case the caller should report EN_HANDLED_UNCHANGED). */
+static struct ic_context *
+ts_handler_ctx(void)
+{
+    struct ic_context *ctx = engine_get_context()->client_ctx;
+    return ctx->runned_az ? ctx : NULL;
+}
+
+/* Runs the scoped sync for the collected transit-switch scope and maps it to
+ * an engine result.  ts_sync_scope() gates each phase on the transactions it
+ * needs (ovnnb_txn for the NB mirror, the IC-SB lock for the datapath
+ * binding), exactly as the full recompute does, so the work for the affected
+ * switches can always be applied here. */
+static enum engine_input_handler_result
+ts_scope_finish(struct ic_context *ctx, struct ed_type_dp_enum *dp,
+                struct sset *ts_scope)
+{
+    if (sset_is_empty(ts_scope)) {
+        return EN_HANDLED_UNCHANGED;
+    }
+    ts_sync_scope(ctx, &dp->dp_tnlids, &dp->isb_ts_dps, ts_scope);
+    return EN_HANDLED_UPDATED;
+}
+
+/* IC-NB Transit_Switch: a new/deleted/renamed transit switch must have its NB
+ * mirror and IC-SB datapath binding reconciled.  A deletion is honoured via
+ * the scoped GC in ts_sync_scope() (the deleted row's name stays in scope but
+ * is absent from IC-NB, so its leftovers are removed). */
+enum engine_input_handler_result
+en_ts_icnb_transit_switch_handler(struct engine_node *node,
+                                  void *data OVS_UNUSED)
+{
+    struct ic_context *ctx = ts_handler_ctx();
+    if (!ctx) {
+        return EN_HANDLED_UNCHANGED;
+    }
+
+    struct ed_type_dp_enum *dp = engine_get_input_data("dp_enum", node);
+    const struct icnbrec_transit_switch_table *tbl =
+        EN_OVSDB_GET(engine_get_input("ICNB_transit_switch", node));
+    struct sset ts_scope = SSET_INITIALIZER(&ts_scope);
+    const struct icnbrec_transit_switch *ts;
+    ICNBREC_TRANSIT_SWITCH_TABLE_FOR_EACH_TRACKED (ts, tbl) {
+        sset_add(&ts_scope, ts->name);
+    }
+
+    enum engine_input_handler_result ret = ts_scope_finish(ctx, dp, &ts_scope);
+    sset_destroy(&ts_scope);
+    return ret;
+}
+
+/* Only transit-switch mirror logical switches (other_config:interconn-ts)
+ * affect en_ts.  A change to such a logical switch reconciles that transit
+ * switch (re-creating the mirror if it was deleted externally); any other
+ * logical switch is irrelevant to en_ts, so its change is a no-op, avoiding a
+ * recompute on unrelated NB Logical_Switch updates. */
+enum engine_input_handler_result
+en_ts_nb_logical_switch_handler(struct engine_node *node,
+                                void *data OVS_UNUSED)
+{
+    struct ic_context *ctx = ts_handler_ctx();
+    if (!ctx) {
+        return EN_HANDLED_UNCHANGED;
+    }
+
+    struct ed_type_dp_enum *dp = engine_get_input_data("dp_enum", node);
+    const struct nbrec_logical_switch_table *tbl =
+        EN_OVSDB_GET(engine_get_input("NB_logical_switch", node));
+    struct sset ts_scope = SSET_INITIALIZER(&ts_scope);
+    const struct nbrec_logical_switch *ls;
+    NBREC_LOGICAL_SWITCH_TABLE_FOR_EACH_TRACKED (ls, tbl) {
+        const char *ts_name = smap_get(&ls->other_config, "interconn-ts");
+        if (ts_name) {
+            sset_add(&ts_scope, ts_name);
+        }
+    }
+
+    enum engine_input_handler_result ret = ts_scope_finish(ctx, dp, &ts_scope);
+    sset_destroy(&ts_scope);
+    return ret;
+}
+
+/* IC-SB Datapath_Binding: when a transit switch's datapath tunnel key is
+ * (re)assigned, the NB Logical_Switch mirror's other_config:requested-tnl-key
+ * must be updated to the committed value.  This is what synchronizes the key
+ * after a global tunnel-key refresh: an IC-NB vxlan_mode change makes en_ts
+ * reallocate the datapath key into the VXLAN range and write it to IC-SB, but
+ * ts_sync_one() intentionally syncs the AZ NB mirror *before* that (committed)
+ * key exists, so the NB value only catches up on a follow-up iteration.  The
+ * full recompute got that follow-up for free every poll; incrementally it is
+ * this handler, driven by the resulting IC-SB Datapath_Binding change.
+ *
+ * en_dp_enum (an upstream input) has already folded the new key into its
+ * datapath map by the time this runs, so re-syncing just the affected transit
+ * switches propagates the committed key to NB.  Only transit-switch bindings
+ * have an NB mirror; transit routers (IC_ROUTER) are irrelevant.  Deletions
+ * are ignored: a transit switch removal is reconciled through
+ * en_ts_icnb_transit_switch_handler and the scoped GC in ts_sync_scope().
+ *
+ * Newly *inserted* bindings are also ignored, on purpose.  Creating a transit
+ * switch's NB mirror is owned by en_ts_icnb_transit_switch_handler; the leader
+ * inserts the IC-SB datapath binding in the very same iteration, so reacting
+ * to that insert here would run a second ts_sync_scope() for the same switch
+ * while the mirror the transit_switch handler just created is still
+ * uncommitted - find_ts_in_nb()'s index does not see the txn-local insert, so
+ * a duplicate NB Logical_Switch would be created ("Multiple logical switches
+ * named ...").  The newly-created mirror's requested-tnl-key is instead synced
+ * by the follow-up en_ts_nb_logical_switch_handler once the mirror is
+ * committed.  Here we only react to a tunnel-key *modify* on an
+ * already-existing binding (the vxlan refresh), whose mirror already exists
+ * and is found. */
+enum engine_input_handler_result
+en_ts_icsb_datapath_binding_handler(struct engine_node *node,
+                                    void *data OVS_UNUSED)
+{
+    struct ic_context *ctx = ts_handler_ctx();
+    if (!ctx) {
+        return EN_HANDLED_UNCHANGED;
+    }
+
+    struct ed_type_dp_enum *dp = engine_get_input_data("dp_enum", node);
+    const struct icsbrec_datapath_binding_table *tbl =
+        EN_OVSDB_GET(engine_get_input("ICSB_datapath_binding", node));
+    struct sset ts_scope = SSET_INITIALIZER(&ts_scope);
+    const struct icsbrec_datapath_binding *isb_dp;
+    ICSBREC_DATAPATH_BINDING_TABLE_FOR_EACH_TRACKED (isb_dp, tbl) {
+        if (icsbrec_datapath_binding_is_deleted(isb_dp) ||
+            icsbrec_datapath_binding_is_new(isb_dp) ||
+            ic_dp_get_type(isb_dp) != IC_SWITCH) {
+            continue;
+        }
+        if (ovsdb_idl_track_is_updated(&isb_dp->header_,
+                    &icsbrec_datapath_binding_col_tunnel_key)) {
+            sset_add(&ts_scope, isb_dp->transit_switch);
+        }
+    }
+
+    enum engine_input_handler_result ret = ts_scope_finish(ctx, dp, &ts_scope);
+    sset_destroy(&ts_scope);
+    return ret;
+}
+
 void *
 en_ts_init(struct engine_node *node OVS_UNUSED,
            struct engine_arg *arg OVS_UNUSED)
diff --git a/ic/en-ts.h b/ic/en-ts.h
index 039b3196f..d1890205b 100644
--- a/ic/en-ts.h
+++ b/ic/en-ts.h
@@ -9,4 +9,13 @@ enum engine_node_state en_ts_run(struct engine_node *node, 
void *data);
 void *en_ts_init(struct engine_node *node, struct engine_arg *arg);
 void en_ts_cleanup(void *data);
 
+enum engine_input_handler_result
+en_ts_icnb_transit_switch_handler(struct engine_node *node, void *data);
+
+enum engine_input_handler_result
+en_ts_nb_logical_switch_handler(struct engine_node *node, void *data);
+
+enum engine_input_handler_result
+en_ts_icsb_datapath_binding_handler(struct engine_node *node, void *data);
+
 #endif /* EN_IC_TS_H */
diff --git a/ic/inc-proc-ic.c b/ic/inc-proc-ic.c
index 7b87ee0fc..3e0f87118 100644
--- a/ic/inc-proc-ic.c
+++ b/ic/inc-proc-ic.c
@@ -249,12 +249,33 @@ void inc_proc_ic_init(struct ovsdb_idl_loop *nb,
                      en_gateway_sb_chassis_handler);
     engine_add_input(&en_gateway, &en_sb_encap, NULL);
 
-    /* en_ts: sync transit switches to NB and IC-SB datapath bindings. */
+    /* en_ts: sync transit switches to NB and IC-SB datapath bindings.
+     *
+     * en_dp_enum is an ordering dependency only: it owns the shared tunnel-key
+     * allocator (dp_tnlids) and transit-switch datapath map, both maintained
+     * incrementally and read live by en_ts.  Because it recomputes en_ts's
+     * datapath map in place, the en_dp_enum edge itself must not force a full
+     * en_ts recompute, so it uses a no-op handler; the resulting IC-SB
+     * Datapath_Binding change is instead reacted to directly via
+     * en_icsb_datapath_binding below, which re-syncs only the affected transit
+     * switches. This matters at scale (tens of thousands of transit switches):
+     * the alternative NULL edge would recompute every transit switch on any
+     * datapath-binding change.
+     *
+     * en_icsb_datapath_binding drives the follow-up NB requested-tnl-key sync
+     * after a tunnel-key (re)assignment - notably the global refresh from an
+     * IC-NB vxlan_mode change (see en_ts_icsb_datapath_binding_handler).  It
+     * is ordered after en_dp_enum (which depends on the same table), so en_ts
+     * sees the freshly folded key. */
     engine_add_input(&en_ts, &en_az, NULL);
-    engine_add_input(&en_ts, &en_dp_enum, NULL);
-    engine_add_input(&en_ts, &en_icnb_ic_nb_global, NULL);
-    engine_add_input(&en_ts, &en_icnb_transit_switch, NULL);
-    engine_add_input(&en_ts, &en_nb_logical_switch, NULL);
+    engine_add_input(&en_ts, &en_dp_enum, engine_noop_handler);
+    engine_add_input(&en_ts, &en_icsb_datapath_binding,
+                     en_ts_icsb_datapath_binding_handler);
+    engine_add_input(&en_ts, &en_icnb_ic_nb_global, en_ic_nb_global_handler);
+    engine_add_input(&en_ts, &en_icnb_transit_switch,
+                     en_ts_icnb_transit_switch_handler);
+    engine_add_input(&en_ts, &en_nb_logical_switch,
+                     en_ts_nb_logical_switch_handler);
     engine_add_input(&en_ts, &en_icsb_encap, NULL);
 
     /* en_tr: sync transit routers to NB and IC-SB datapath bindings. */
diff --git a/ic/ovn-ic.c b/ic/ovn-ic.c
index 887c4c30f..c1798b549 100644
--- a/ic/ovn-ic.c
+++ b/ic/ovn-ic.c
@@ -70,6 +70,8 @@ static const char *ssl_ca_cert_file;
 
 static const struct sbrec_port_binding * find_sb_pb_by_name(
     struct ovsdb_idl_index *sbrec_port_binding_by_name, const char *name);
+static const struct nbrec_logical_switch * find_ts_in_nb(
+    struct ic_context *ctx, char *ts_name);
 
 
 static void
@@ -139,137 +141,238 @@ is_az_leader(struct ovsdb_idl_txn *txn)
     return idl && ovsdb_idl_has_lock(idl);
 }
 
-void
-ts_run(struct ic_context *ctx, struct hmap *dp_tnlids,
-       struct shash *isb_ts_dps)
+/* Returns true if transit-switch datapaths must use the VXLAN tunnel-key
+ * range: IC-NB requests vxlan_mode and the IC-SB actually has a VXLAN encap.
+ *
+ * Warning: ovnisb_unlocked should not be used to insert data on IC_SB which
+ * can cause a constraint violation, as an example, inserting data to IC-SB
+ * datapath_binding. */
+static bool
+ts_compute_vxlan_mode(struct ic_context *ctx)
 {
-    const struct icnbrec_transit_switch *ts;
-    bool dp_key_refresh = false;
-    bool vxlan_mode = false;
     const struct icnbrec_ic_nb_global *ic_nb =
         icnbrec_ic_nb_global_first(ctx->ovninb_idl);
 
-    /*
-     * Warning: ovnisb_unlocked should not be used to insert data on IC_SB
-     * which can cause a constraint violation, as an example, inserting data to
-     * IC-SB datapath_binding.
-     */
     if (ic_nb && smap_get_bool(&ic_nb->options, "vxlan_mode", false)) {
         const struct icsbrec_encap *encap;
         ICSBREC_ENCAP_FOR_EACH (encap, ctx->ovnisb_unlocked_idl) {
             if (!strcmp(encap->type, "vxlan")) {
-                vxlan_mode = true;
-                break;
+                return true;
             }
         }
     }
+    return false;
+}
 
-    /* Sync INB TS to AZ NB */
-    if (ctx->ovnnb_txn) {
-        struct shash nb_tses = SHASH_INITIALIZER(&nb_tses);
-        const struct nbrec_logical_switch *ls;
-
-        /* Get current NB Logical_Switch with other_config:interconn-ts */
-        NBREC_LOGICAL_SWITCH_FOR_EACH (ls, ctx->ovnnb_idl) {
-            const char *ts_name = smap_get(&ls->other_config, "interconn-ts");
-            if (ts_name) {
-                shash_add(&nb_tses, ts_name, ls);
-            }
-        }
+/* Reconciles a single transit switch 'ts'.  Phase 1 (gated by 'nb_gc' being
+ * non-NULL, i.e. an NB transaction is available) keeps its AZ NB
+ * Logical_Switch mirror in sync; Phase 2 (gated by 'leader') keeps its IC-SB
+ * Datapath_Binding in sync, allocating a tunnel key from 'dp_tnlids' when one
+ * is missing.
+ *
+ * 'nb_gc' (keyed by transit-switch name) and 'isb_gc' (the datapath-binding
+ * map keyed by transit-switch name) double as garbage-collection sets: this
+ * function removes the entries it claims, so whatever remains after every
+ * in-scope switch has been reconciled is stale and deleted by the caller.
+ * Phase 1 runs before Phase 2 so that, for an already-committed binding,
+ * other_config:requested-tnl-key is derived from the committed IC-SB key
+ * rather than one that may still change (e.g. a vxlan-mode refresh).  The one
+ * exception is a brand-new binding, whose freshly-allocated key Phase 2
+ * publishes to the mirror in this same iteration (see there): that key is
+ * stable and doing so avoids a datapath tunnel-key flap on every TS creation.
+ * */
+static void
+ts_sync_one(struct ic_context *ctx, const struct icnbrec_transit_switch *ts,
+            struct hmap *dp_tnlids, struct shash *isb_gc, struct shash *nb_gc,
+            bool vxlan_mode, bool leader)
+{
+    bool dp_key_refresh = false;
+    const struct nbrec_logical_switch *ls = NULL;
 
-        /* Create/update NB Logical_Switch for each TS */
-        ICNBREC_TRANSIT_SWITCH_FOR_EACH (ts, ctx->ovninb_idl) {
-            ls = shash_find_and_delete(&nb_tses, ts->name);
-            if (!ls) {
-                ls = nbrec_logical_switch_insert(ctx->ovnnb_txn);
-                nbrec_logical_switch_set_name(ls, ts->name);
-                nbrec_logical_switch_update_other_config_setkey(ls,
-                                                                "interconn-ts",
-                                                                ts->name);
+    /* Phase 1: Sync INB TS to AZ NB. */
+    if (nb_gc) {
+        ls = shash_find_and_delete(nb_gc, ts->name);
+        if (!ls) {
+            ls = nbrec_logical_switch_insert(ctx->ovnnb_txn);
+            nbrec_logical_switch_set_name(ls, ts->name);
+            nbrec_logical_switch_update_other_config_setkey(ls, "interconn-ts",
+                                                            ts->name);
+            nbrec_logical_switch_update_other_config_setkey(
+                    ls, "ic-vxlan_mode", vxlan_mode ? "true" : "false");
+        } else {
+            bool _vxlan_mode = smap_get_bool(&ls->other_config,
+                                             "ic-vxlan_mode", false);
+            if (_vxlan_mode != vxlan_mode) {
+                dp_key_refresh = true;
                 nbrec_logical_switch_update_other_config_setkey(
                         ls, "ic-vxlan_mode", vxlan_mode ? "true" : "false");
-            } else {
-                bool _vxlan_mode = smap_get_bool(&ls->other_config,
-                                                 "ic-vxlan_mode", false);
-                if (_vxlan_mode != vxlan_mode) {
-                    dp_key_refresh = true;
-                    nbrec_logical_switch_update_other_config_setkey(
-                            ls, "ic-vxlan_mode",
-                            vxlan_mode ? "true" : "false");
-                }
-            }
-
-            const struct icsbrec_datapath_binding *isb_dp;
-            isb_dp = shash_find_data(isb_ts_dps, ts->name);
-            if (isb_dp) {
-                int64_t nb_tnl_key = smap_get_int(&ls->other_config,
-                                                  "requested-tnl-key",
-                                                  0);
-                if (nb_tnl_key != isb_dp->tunnel_key) {
-                    VLOG_DBG("Set other_config:requested-tnl-key %"PRId64
-                             " for transit switch %s in NB.",
-                             isb_dp->tunnel_key, ts->name);
-                    char *tnl_key_str = xasprintf("%"PRId64,
-                                                  isb_dp->tunnel_key);
-                    nbrec_logical_switch_update_other_config_setkey(
-                        ls, "requested-tnl-key", tnl_key_str);
-                    free(tnl_key_str);
-                }
             }
         }
 
-        /* Delete extra NB Logical_Switch with other_config:interconn-ts */
-        struct shash_node *node;
-        SHASH_FOR_EACH (node, &nb_tses) {
-            nbrec_logical_switch_delete(node->data);
+        const struct icsbrec_datapath_binding *isb_dp =
+            shash_find_data(isb_gc, ts->name);
+        if (isb_dp) {
+            int64_t nb_tnl_key = smap_get_int(&ls->other_config,
+                                              "requested-tnl-key", 0);
+            if (nb_tnl_key != isb_dp->tunnel_key) {
+                VLOG_DBG("Set other_config:requested-tnl-key %"PRId64
+                         " for transit switch %s in NB.",
+                         isb_dp->tunnel_key, ts->name);
+                char *tnl_key_str = xasprintf("%"PRId64, isb_dp->tunnel_key);
+                nbrec_logical_switch_update_other_config_setkey(
+                    ls, "requested-tnl-key", tnl_key_str);
+                free(tnl_key_str);
+            }
         }
-        shash_destroy(&nb_tses);
     }
 
     /* Sync TS between INB and ISB.  This is performed after syncing with AZ
      * SB, to avoid uncommitted ISB datapath tunnel key to be synced back to
      * AZ. */
-    if (ctx->ovnisb_txn &&
-        is_az_leader(ctx->ovnisb_txn)) {
-        /* Create ISB Datapath_Binding */
-        ICNBREC_TRANSIT_SWITCH_FOR_EACH (ts, ctx->ovninb_idl) {
-            const struct icsbrec_datapath_binding *isb_dp =
-                shash_find_and_delete(isb_ts_dps, ts->name);
-            if (!isb_dp) {
-                /* Allocate tunnel key */
-                int64_t dp_key = allocate_dp_key(dp_tnlids, vxlan_mode,
-                                                 "transit switch datapath");
-                if (!dp_key) {
-                    continue;
-                }
+    if (leader) {
+        const struct icsbrec_datapath_binding *isb_dp =
+            shash_find_and_delete(isb_gc, ts->name);
+        if (!isb_dp) {
+            /* Allocate tunnel key */
+            int64_t dp_key = allocate_dp_key(dp_tnlids, vxlan_mode,
+                                             "transit switch datapath");
+            if (!dp_key) {
+                return;
+            }
 
-                isb_dp = icsbrec_datapath_binding_insert(ctx->ovnisb_txn);
-                icsbrec_datapath_binding_set_transit_switch(isb_dp, ts->name);
+            isb_dp = icsbrec_datapath_binding_insert(ctx->ovnisb_txn);
+            icsbrec_datapath_binding_set_transit_switch(isb_dp, ts->name);
+            icsbrec_datapath_binding_set_tunnel_key(isb_dp, dp_key);
+
+            /* Publish the freshly-allocated key to the AZ NB mirror in this
+             * same iteration.  Otherwise the mirror carries no
+             * requested-tnl-key until a follow-up iteration copies the
+             * committed IC-SB key back (Phase 1 above), and in the meantime
+             * northd auto-assigns a different datapath tunnel key and then has
+             * to change it - a per-transit-switch datapath tunnel-key flap on
+             * every TS creation.  Unlike a refresh (handled on a later
+             * iteration once the new key is committed, to avoid publishing a
+             * key that may still change), a brand-new binding's key is stable:
+             * it is the one being committed now, and if the IC-SB transaction
+             * fails the whole run is retried, so the NB hint cannot outlive
+             * its binding. */
+            if (ls) {
+                char *tnl_key_str = xasprintf("%"PRId64, dp_key);
+                nbrec_logical_switch_update_other_config_setkey(
+                    ls, "requested-tnl-key", tnl_key_str);
+                free(tnl_key_str);
+            }
+        } else if (dp_key_refresh) {
+            /* Refresh tunnel key since encap mode has changed. */
+            int64_t dp_key = allocate_dp_key(dp_tnlids, vxlan_mode,
+                                             "transit switch datapath");
+            if (dp_key) {
                 icsbrec_datapath_binding_set_tunnel_key(isb_dp, dp_key);
-            } else if (dp_key_refresh) {
-                /* Refresh tunnel key since encap mode has changed. */
-                int64_t dp_key = allocate_dp_key(dp_tnlids, vxlan_mode,
-                                                 "transit switch datapath");
-                if (dp_key) {
-                    icsbrec_datapath_binding_set_tunnel_key(isb_dp, dp_key);
-                }
             }
+        }
 
-            if (!isb_dp->type) {
-                icsbrec_datapath_binding_set_type(isb_dp, "transit-switch");
+        if (!isb_dp->type) {
+            icsbrec_datapath_binding_set_type(isb_dp, "transit-switch");
+        }
+
+        if (!isb_dp->nb_ic_uuid) {
+            icsbrec_datapath_binding_set_nb_ic_uuid(isb_dp,
+                                                    &ts->header_.uuid, 1);
+        }
+    }
+}
+
+/* Synchronizes transit switches to their NB Logical_Switch mirrors and IC-SB
+ * Datapath_Bindings.  When 'ts_scope' is NULL every transit switch is
+ * reconciled (full recompute); otherwise only the switches named in
+ * 'ts_scope' are.  A name still in scope but no longer present in IC-NB (a
+ * deleted switch) is honoured: its mirror/datapath end up as
+ * garbage-collection leftovers and are deleted, matching full-recompute
+ * behaviour.
+ *
+ * In the full case 'isb_ts_dps' is consumed destructively (the caller must
+ * own a private copy); in the scoped case it is only read - a private scoped
+ * subset is built for garbage collection so the caller's authoritative map is
+ * left intact. */
+void
+ts_sync_scope(struct ic_context *ctx, struct hmap *dp_tnlids,
+              struct shash *isb_ts_dps, const struct sset *ts_scope)
+{
+    bool full = !ts_scope;
+    bool vxlan_mode = ts_compute_vxlan_mode(ctx);
+    bool leader = ctx->ovnisb_txn && is_az_leader(ctx->ovnisb_txn);
+
+    /* Build the NB Logical_Switch mirror GC set, keyed by transit-switch
+     * name.  Only needed when an NB transaction is available. */
+    struct shash nb_ts_mirrors = SHASH_INITIALIZER(&nb_ts_mirrors);
+    struct shash *nb_gc = NULL;
+    if (ctx->ovnnb_txn) {
+        nb_gc = &nb_ts_mirrors;
+        if (full) {
+            const struct nbrec_logical_switch *ls;
+            NBREC_LOGICAL_SWITCH_FOR_EACH (ls, ctx->ovnnb_idl) {
+                const char *ts_name = smap_get(&ls->other_config,
+                                               "interconn-ts");
+                if (ts_name) {
+                    shash_add(nb_gc, ts_name, ls);
+                }
+            }
+        } else {
+            const char *name;
+            SSET_FOR_EACH (name, ts_scope) {
+                const struct nbrec_logical_switch *ls =
+                    find_ts_in_nb(ctx, CONST_CAST(char *, name));
+                if (ls && !shash_find(nb_gc, name)) {
+                    shash_add(nb_gc, name, ls);
+                }
             }
+        }
+    }
 
-            if (!isb_dp->nb_ic_uuid) {
-                icsbrec_datapath_binding_set_nb_ic_uuid(isb_dp,
-                                                        &ts->header_.uuid, 1);
+    /* Build the IC-SB Datapath_Binding GC set.  In the full case this is the
+     * caller-owned map itself (consumed); in the scoped case it is a private
+     * subset of the in-scope names so the caller's map is preserved. */
+    struct shash isb_dps_scoped = SHASH_INITIALIZER(&isb_dps_scoped);
+    struct shash *isb_gc;
+    if (full) {
+        isb_gc = isb_ts_dps;
+    } else {
+        const char *name;
+        SSET_FOR_EACH (name, ts_scope) {
+            struct icsbrec_datapath_binding *isb_dp =
+                shash_find_data(isb_ts_dps, name);
+            if (isb_dp && !shash_find(&isb_dps_scoped, name)) {
+                shash_add(&isb_dps_scoped, name, isb_dp);
             }
         }
+        isb_gc = &isb_dps_scoped;
+    }
 
-        struct shash_node *node;
-        SHASH_FOR_EACH (node, isb_ts_dps) {
+    const struct icnbrec_transit_switch *ts;
+    ICNBREC_TRANSIT_SWITCH_FOR_EACH (ts, ctx->ovninb_idl) {
+        if (full || sset_contains(ts_scope, ts->name)) {
+            ts_sync_one(ctx, ts, dp_tnlids, isb_gc, nb_gc, vxlan_mode, leader);
+        }
+    }
+
+    struct shash_node *node;
+
+    /* Delete extra NB Logical_Switch with other_config:interconn-ts. */
+    if (nb_gc) {
+        SHASH_FOR_EACH (node, nb_gc) {
+            nbrec_logical_switch_delete(node->data);
+        }
+    }
+
+    /* Delete extra IC-SB Datapath_Binding. */
+    if (leader) {
+        SHASH_FOR_EACH (node, isb_gc) {
             icsbrec_datapath_binding_delete(node->data);
         }
     }
+
+    shash_destroy(&nb_ts_mirrors);
+    shash_destroy(&isb_dps_scoped);
 }
 
 void
diff --git a/ic/ovn-ic.h b/ic/ovn-ic.h
index b4b95b3ba..2e020db9d 100644
--- a/ic/ovn-ic.h
+++ b/ic/ovn-ic.h
@@ -71,11 +71,19 @@ enum ic_datapath_type ic_dp_get_type(
     const struct icsbrec_datapath_binding *isb_dp);
 
 void address_set_run(struct ic_context *ctx);
-void ts_run(struct ic_context *ctx, struct hmap *dp_tnlids,
-            struct shash *isb_ts_dps);
 void tr_run(struct ic_context *ctx, struct hmap *dp_tnlids,
             struct shash *isb_tr_dps);
 void port_binding_run(struct ic_context *ctx);
+
+struct sset;
+
+/* Reconciles the transit switches named in 'ts_scope' (NULL reconciles every
+ * transit switch, equivalent to ts_run()).  'isb_ts_dps' is the transit-switch
+ * datapath map owned by the dp_enum engine node; it is consumed destructively
+ * only in the full (NULL scope) case, so a private copy must be passed there.
+ */
+void ts_sync_scope(struct ic_context *ctx, struct hmap *dp_tnlids,
+                   struct shash *isb_ts_dps, const struct sset *ts_scope);
 void route_run(struct ic_context *ctx);
 void sync_service_monitor(struct ic_context *ctx);
 
-- 
2.34.1


-- 




_'Esta mensagem é direcionada apenas para os endereços constantes no 
cabeçalho inicial. Se você não está listado nos endereços constantes no 
cabeçalho, pedimos-lhe que desconsidere completamente o conteúdo dessa 
mensagem e cuja cópia, encaminhamento e/ou execução das ações citadas estão 
imediatamente anuladas e proibidas'._


* **'Apesar do Magazine Luiza tomar 
todas as precauções razoáveis para assegurar que nenhum vírus esteja 
presente nesse e-mail, a empresa não poderá aceitar a responsabilidade por 
quaisquer perdas ou danos causados por esse e-mail ou por seus anexos'.*



_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to