Add the incremental change handlers for the en_gateway node so SB/IC-SB
gateway and chassis changes are reconciled in place instead of forcing a
full recompute.

The handlers report EN_HANDLED_UNCHANGED when the tracked rows required no
write at all (an unchanged interconn chassis, a gateway update we made
ourselves, a deleted chassis with no gateway to clean up), so a tracked
change that turns out to be a no-op does not mark the node as updated.

Add a tests/ovn-ic.at test exercising the en_dp_enum and en_gateway
incremental handlers (the change handler of the node that reads a changed
table fires, and a forced recompute produces no diff).

Assisted-by: Claude Opus 4.8, Claude Code
Signed-off-by: Paulo Guilherme Silva <[email protected]>
---
 ic/en-gateway.c  | 364 ++++++++++++++++++++++++++++++++++++++++++++++-
 ic/en-gateway.h  |   5 +
 ic/inc-proc-ic.c |  16 ++-
 ic/ovn-ic.c      | 157 +-------------------
 ic/ovn-ic.h      |   6 +-
 tests/ovn-ic.at  |  64 +++++++++
 6 files changed, 448 insertions(+), 164 deletions(-)

diff --git a/ic/en-gateway.c b/ic/en-gateway.c
index 3fb00ced9..e3013e6ae 100644
--- a/ic/en-gateway.c
+++ b/ic/en-gateway.c
@@ -14,14 +14,335 @@
 
 #include <config.h>
 
-#include "en-gateway.h"
 #include "en-az.h"
+#include "en-gateway.h"
 #include "lib/inc-proc-eng.h"
+#include "lib/ovn-ic-sb-idl.h"
+#include "lib/ovn-sb-idl.h"
 #include "openvswitch/vlog.h"
 #include "ovn-ic.h"
 
 VLOG_DEFINE_THIS_MODULE(en_ic_gateway);
 
+/* Returns true if any information in gw and chassis is different. */
+static bool
+is_gateway_data_changed(const struct icsbrec_gateway *gw,
+                   const struct sbrec_chassis *chassis)
+{
+    if (strcmp(gw->hostname, chassis->hostname)) {
+        return true;
+    }
+
+    if (gw->n_encaps != chassis->n_encaps) {
+        return true;
+    }
+
+    for (size_t g = 0; g < gw->n_encaps; g++) {
+        bool found = false;
+        const struct icsbrec_encap *gw_encap = gw->encaps[g];
+        for (size_t s = 0; s < chassis->n_encaps; s++) {
+            const struct sbrec_encap *chassis_encap = chassis->encaps[s];
+            if (!strcmp(gw_encap->type, chassis_encap->type) &&
+                !strcmp(gw_encap->ip, chassis_encap->ip)) {
+                found = true;
+                if (!smap_equal(&gw_encap->options, &chassis_encap->options)) {
+                    return true;
+                }
+                break;
+            }
+        }
+        if (!found) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+static void
+sync_isb_gw_to_sb(struct ic_context *ctx,
+                  const struct icsbrec_gateway *gw,
+                  const struct sbrec_chassis *chassis)
+{
+    struct smap temp_map;
+    sbrec_chassis_set_hostname(chassis, gw->hostname);
+    smap_clone(&temp_map, &chassis->other_config);
+    smap_replace(&temp_map, "is-remote", "true");
+    /* Use sbrec_chassis_set_other_config instead of
+     * sbrec_chassis_update_other_config_setkey so the in-memory datum is
+     * updated for reads in the same loop iteration. */
+    sbrec_chassis_set_other_config(chassis, &temp_map);
+    smap_destroy(&temp_map);
+
+    /* Sync encaps used by this gateway. */
+    ovs_assert(gw->n_encaps);
+    struct sbrec_encap *sb_encap;
+    struct sbrec_encap **sb_encaps =
+        xmalloc(gw->n_encaps * sizeof *sb_encaps);
+    for (size_t i = 0; i < gw->n_encaps; i++) {
+        sb_encap = sbrec_encap_insert(ctx->ovnsb_txn);
+        sbrec_encap_set_chassis_name(sb_encap, gw->name);
+        sbrec_encap_set_ip(sb_encap, gw->encaps[i]->ip);
+        sbrec_encap_set_type(sb_encap, gw->encaps[i]->type);
+        sbrec_encap_set_options(sb_encap, &gw->encaps[i]->options);
+        sb_encaps[i] = sb_encap;
+    }
+    sbrec_chassis_set_encaps(chassis, sb_encaps, gw->n_encaps);
+    free(sb_encaps);
+}
+
+static void
+sync_sb_gw_to_isb(struct ic_context *ctx,
+                  const struct sbrec_chassis *chassis,
+                  const struct icsbrec_gateway *gw)
+{
+    icsbrec_gateway_set_hostname(gw, chassis->hostname);
+
+    /* Sync encaps used by this chassis. */
+    ovs_assert(chassis->n_encaps);
+    struct icsbrec_encap *isb_encap;
+    struct icsbrec_encap **isb_encaps =
+        xmalloc(chassis->n_encaps * sizeof *isb_encaps);
+    for (size_t i = 0; i < chassis->n_encaps; i++) {
+        isb_encap = icsbrec_encap_insert(ctx->ovnisb_unlocked_txn);
+        icsbrec_encap_set_gateway_name(isb_encap,
+                                      chassis->name);
+        icsbrec_encap_set_ip(isb_encap, chassis->encaps[i]->ip);
+        icsbrec_encap_set_type(isb_encap,
+                              chassis->encaps[i]->type);
+        icsbrec_encap_set_options(isb_encap,
+                                 &chassis->encaps[i]->options);
+        isb_encaps[i] = isb_encap;
+    }
+    icsbrec_gateway_set_encaps(gw, isb_encaps,
+                              chassis->n_encaps);
+    free(isb_encaps);
+}
+
+/* Returns true if it ran to completion, false if the IC-SB/SB transactions
+ * needed to sync were not available (in which case the caller should report
+ * that the node is still stale). */
+static bool
+gateway_run(struct ic_context *ctx,
+            const struct icsbrec_availability_zone *runned_az)
+{
+    if (!ctx->ovnisb_unlocked_txn || !ctx->ovnsb_txn) {
+        return false;
+    }
+
+    struct shash local_gws = SHASH_INITIALIZER(&local_gws);
+    struct shash remote_gws = SHASH_INITIALIZER(&remote_gws);
+    const struct icsbrec_gateway *gw;
+    ICSBREC_GATEWAY_FOR_EACH (gw, ctx->ovnisb_unlocked_idl) {
+        if (gw->availability_zone == runned_az) {
+            shash_add(&local_gws, gw->name, gw);
+        } else {
+            shash_add(&remote_gws, gw->name, gw);
+        }
+    }
+
+    const struct sbrec_chassis *chassis;
+    SBREC_CHASSIS_FOR_EACH (chassis, ctx->ovnsb_idl) {
+        if (smap_get_bool(&chassis->other_config, "is-interconn", false)) {
+            gw = shash_find_and_delete(&local_gws, chassis->name);
+            if (!gw) {
+                gw = icsbrec_gateway_insert(ctx->ovnisb_unlocked_txn);
+                icsbrec_gateway_set_availability_zone(gw, runned_az);
+                icsbrec_gateway_set_name(gw, chassis->name);
+                sync_sb_gw_to_isb(ctx, chassis, gw);
+            } else if (is_gateway_data_changed(gw, chassis)) {
+                sync_sb_gw_to_isb(ctx, chassis, gw);
+            }
+        } else if (smap_get_bool(&chassis->other_config, "is-remote", false)) {
+            gw = shash_find_and_delete(&remote_gws, chassis->name);
+            if (!gw) {
+                sbrec_chassis_delete(chassis);
+            } else if (is_gateway_data_changed(gw, chassis)) {
+                sync_isb_gw_to_sb(ctx, gw, chassis);
+            }
+        }
+    }
+
+    /* Delete extra gateways from ISB for the local AZ */
+    struct shash_node *node;
+    SHASH_FOR_EACH (node, &local_gws) {
+        icsbrec_gateway_delete(node->data);
+    }
+    shash_destroy(&local_gws);
+
+    /* Create SB chassis for remote gateways in ISB */
+    SHASH_FOR_EACH (node, &remote_gws) {
+        gw = node->data;
+        chassis = sbrec_chassis_insert(ctx->ovnsb_txn);
+        sbrec_chassis_set_name(chassis, gw->name);
+        sync_isb_gw_to_sb(ctx, gw, chassis);
+    }
+    shash_destroy(&remote_gws);
+
+    return true;
+}
+
+/* Returns the IC-SB gateway named 'name' whose availability zone is (when
+ * 'local') or is not (otherwise) this instance's AZ, or NULL.  There is no
+ * index on Gateway by name, but the number of gateways is small. */
+static const struct icsbrec_gateway *
+find_gw_by_name(struct ic_context *ctx,
+                const struct icsbrec_availability_zone *runned_az,
+                const char *name, bool local)
+{
+    const struct icsbrec_gateway *gw;
+    ICSBREC_GATEWAY_FOR_EACH (gw, ctx->ovnisb_unlocked_idl) {
+        if ((gw->availability_zone == runned_az) == local
+            && !strcmp(gw->name, name)) {
+            return gw;
+        }
+    }
+    return NULL;
+}
+
+/* Incremental handler for en_gateway's SB Chassis input.  Mirrors the
+ * SB-Chassis-driven half of gateway_run() for the tracked chassis only:
+ * an is-interconn chassis maintains its local IC-SB gateway, while an
+ * is-remote chassis (one we created from a remote gateway) is re-synced or
+ * deleted.  Returns EN_UNHANDLED to fall back to a full recompute for cases
+ * that cannot be handled incrementally, and EN_HANDLED_UNCHANGED when the
+ * tracked chassis required no write at all. */
+static enum engine_input_handler_result
+sync_gateway_handle_sb_chassis(
+    struct ic_context *ctx,
+    const struct icsbrec_availability_zone *runned_az,
+    const struct sbrec_chassis_table *tbl)
+{
+    if (!ctx->ovnisb_unlocked_txn || !ctx->ovnsb_txn) {
+        return EN_UNHANDLED;
+    }
+
+    bool changed = false;
+    const struct sbrec_chassis *chassis;
+    SBREC_CHASSIS_TABLE_FOR_EACH_TRACKED (chassis, tbl) {
+        const struct icsbrec_gateway *local_gw =
+            find_gw_by_name(ctx, runned_az, chassis->name, true);
+
+        if (sbrec_chassis_is_deleted(chassis)) {
+            /* The local gateway derived from this chassis (if any) is now
+             * orphaned.  IC-SB Gateway is indexed uniquely by name, so a local
+             * gateway with this name rules out a remote one: nothing more to
+             * do for this chassis. */
+            if (local_gw) {
+                icsbrec_gateway_delete(local_gw);
+                changed = true;
+                continue;
+            }
+            /* If this was a remote chassis we created and the remote gateway
+             * still exists, it must be recreated: leave that to a full
+             * recompute. */
+            if (find_gw_by_name(ctx, runned_az, chassis->name, false)) {
+                return EN_UNHANDLED;
+            }
+            continue;
+        }
+
+        if (smap_get_bool(&chassis->other_config, "is-interconn", false)) {
+            if (!local_gw) {
+                local_gw = icsbrec_gateway_insert(ctx->ovnisb_unlocked_txn);
+                icsbrec_gateway_set_availability_zone(local_gw,
+                                                      runned_az);
+                icsbrec_gateway_set_name(local_gw, chassis->name);
+                sync_sb_gw_to_isb(ctx, chassis, local_gw);
+                changed = true;
+            } else if (is_gateway_data_changed(local_gw, chassis)) {
+                sync_sb_gw_to_isb(ctx, chassis, local_gw);
+                changed = true;
+            }
+        } else {
+            /* No longer an interconn chassis: drop any local gateway derived
+             * from it. */
+            if (local_gw) {
+                icsbrec_gateway_delete(local_gw);
+                changed = true;
+            }
+            if (smap_get_bool(&chassis->other_config, "is-remote", false)) {
+                const struct icsbrec_gateway *remote_gw =
+                    find_gw_by_name(ctx, runned_az, chassis->name, false);
+                if (!remote_gw) {
+                    sbrec_chassis_delete(chassis);
+                    changed = true;
+                } else if (is_gateway_data_changed(remote_gw, chassis)) {
+                    sync_isb_gw_to_sb(ctx, remote_gw, chassis);
+                    changed = true;
+                }
+            }
+        }
+    }
+
+    return changed ? EN_HANDLED_UPDATED : EN_HANDLED_UNCHANGED;
+}
+
+/* Incremental handler for en_gateway's IC-SB Gateway input.  Mirrors the
+ * remote-gateway-driven half of gateway_run() for the tracked gateways only:
+ * a remote gateway maintains its SB chassis mirror.  Returns EN_UNHANDLED to
+ * fall back to a full recompute for cases that cannot be handled
+ * incrementally, and EN_HANDLED_UNCHANGED when the tracked gateways required
+ * no write at all. */
+static enum engine_input_handler_result
+sync_gateway_handle_icsb_gateway(
+    struct ic_context *ctx,
+    const struct icsbrec_availability_zone *runned_az,
+    const struct icsbrec_gateway_table *tbl)
+{
+    if (!ctx->ovnisb_unlocked_txn || !ctx->ovnsb_txn) {
+        return EN_UNHANDLED;
+    }
+
+    bool changed = false;
+    const struct icsbrec_gateway *gw;
+    ICSBREC_GATEWAY_TABLE_FOR_EACH_TRACKED (gw, tbl) {
+        bool local = gw->availability_zone == runned_az;
+        const struct sbrec_chassis *chassis = find_sb_chassis(ctx, gw->name);
+
+        if (icsbrec_gateway_is_deleted(gw)) {
+            if (!local && chassis &&
+                smap_get_bool(&chassis->other_config, "is-remote", false)) {
+                sbrec_chassis_delete(chassis);
+                changed = true;
+            }
+            /* A local gateway disappearing while its interconn chassis still
+             * exists needs the chassis-driven recreate path:
+             * full recompute. */
+            if (local && chassis &&
+                smap_get_bool(&chassis->other_config, "is-interconn", false)) {
+                return EN_UNHANDLED;
+            }
+            continue;
+        }
+
+        if (local) {
+            /* Local gateways are produced from SB chassis (handled by the
+             * SB Chassis handler); a create/update here is our own write. */
+            continue;
+        }
+
+        /* Remote gateway: ensure a matching SB chassis exists and is synced.
+         * If the only chassis with this name is a local interconn chassis,
+         * this is an unexpected name collision; fall back. */
+        if (chassis &&
+            smap_get_bool(&chassis->other_config, "is-interconn", false)) {
+            return EN_UNHANDLED;
+        }
+        if (!chassis) {
+            chassis = sbrec_chassis_insert(ctx->ovnsb_txn);
+            sbrec_chassis_set_name(chassis, gw->name);
+            sync_isb_gw_to_sb(ctx, gw, chassis);
+            changed = true;
+        } else if (is_gateway_data_changed(gw, chassis)) {
+            sync_isb_gw_to_sb(ctx, gw, chassis);
+            changed = true;
+        }
+    }
+
+    return changed ? EN_HANDLED_UPDATED : EN_HANDLED_UNCHANGED;
+}
+
 enum engine_node_state
 en_gateway_run(struct engine_node *node, void *data OVS_UNUSED)
 {
@@ -35,11 +356,50 @@ en_gateway_run(struct engine_node *node, void *data 
OVS_UNUSED)
         return EN_UNCHANGED;
     }
 
-    gateway_run(ctx, az->runned_az);
+    /* If the transactions needed to sync were not available, the node could
+     * not be brought up to date; report it as stale so the engine retries. */
+    if (!gateway_run(ctx, az->runned_az)) {
+        return EN_STALE;
+    }
 
     return EN_UPDATED;
 }
 
+enum engine_input_handler_result
+en_gateway_sb_chassis_handler(struct engine_node *node, void *data OVS_UNUSED)
+{
+    const struct engine_context *eng_ctx = engine_get_context();
+    struct ic_context *ctx = eng_ctx->client_ctx;
+    const struct ed_type_az *az = engine_get_input_data("az", node);
+
+    if (!az->runned_az) {
+        return EN_HANDLED_UNCHANGED;
+    }
+
+    const struct sbrec_chassis_table *tbl =
+        EN_OVSDB_GET(engine_get_input("SB_chassis", node));
+
+    return sync_gateway_handle_sb_chassis(ctx, az->runned_az, tbl);
+}
+
+enum engine_input_handler_result
+en_gateway_icsb_gateway_handler(struct engine_node *node,
+                                void *data OVS_UNUSED)
+{
+    const struct engine_context *eng_ctx = engine_get_context();
+    struct ic_context *ctx = eng_ctx->client_ctx;
+    const struct ed_type_az *az = engine_get_input_data("az", node);
+
+    if (!az->runned_az) {
+        return EN_HANDLED_UNCHANGED;
+    }
+
+    const struct icsbrec_gateway_table *tbl =
+        EN_OVSDB_GET(engine_get_input("ICSB_gateway", node));
+
+    return sync_gateway_handle_icsb_gateway(ctx, az->runned_az, tbl);
+}
+
 void *
 en_gateway_init(struct engine_node *node OVS_UNUSED,
                 struct engine_arg *arg OVS_UNUSED)
diff --git a/ic/en-gateway.h b/ic/en-gateway.h
index d804d114f..e6b8591a4 100644
--- a/ic/en-gateway.h
+++ b/ic/en-gateway.h
@@ -7,4 +7,9 @@ enum engine_node_state en_gateway_run(struct engine_node *node, 
void *data);
 void *en_gateway_init(struct engine_node *node, struct engine_arg *arg);
 void en_gateway_cleanup(void *data);
 
+enum engine_input_handler_result
+en_gateway_sb_chassis_handler(struct engine_node *node, void *data);
+enum engine_input_handler_result
+en_gateway_icsb_gateway_handler(struct engine_node *node, void *data);
+
 #endif /* EN_IC_GATEWAY_H */
diff --git a/ic/inc-proc-ic.c b/ic/inc-proc-ic.c
index 64aef2c82..a30ddf367 100644
--- a/ic/inc-proc-ic.c
+++ b/ic/inc-proc-ic.c
@@ -201,12 +201,20 @@ void inc_proc_ic_init(struct ovsdb_idl_loop *nb,
      * splitting the monolithic ovn_db_run() into independently-gated nodes.
      * Change handlers are added incrementally in a later step. */
 
-    /* en_gateway: sync gateways/chassis between SB and IC-SB. */
+    /* en_gateway: sync gateways/chassis between SB and IC-SB.
+     *
+     * The availability zone is provided by en_az (which reports EN_UPDATED
+     * only when the AZ identity changes).  en_gateway does not read the
+     * Availability_Zone table itself - only gateway rows' availability_zone
+     * reference and en_az's resolved AZ - so it deliberately does not depend
+     * on en_icsb_availability_zone, whose rows also carry the
+     * frequently-bumped nb_ic_cfg sequence number. */
     engine_add_input(&en_gateway, &en_az, NULL);
-    engine_add_input(&en_gateway, &en_icsb_availability_zone, NULL);
-    engine_add_input(&en_gateway, &en_icsb_gateway, NULL);
+    engine_add_input(&en_gateway, &en_icsb_gateway,
+                     en_gateway_icsb_gateway_handler);
     engine_add_input(&en_gateway, &en_icsb_encap, NULL);
-    engine_add_input(&en_gateway, &en_sb_chassis, NULL);
+    engine_add_input(&en_gateway, &en_sb_chassis,
+                     en_gateway_sb_chassis_handler);
     engine_add_input(&en_gateway, &en_sb_encap, NULL);
 
     /* en_ts: sync transit switches to their AZ NB Logical_Switch mirrors.
diff --git a/ic/ovn-ic.c b/ic/ovn-ic.c
index e49ef6a17..8b2cd7c1c 100644
--- a/ic/ovn-ic.c
+++ b/ic/ovn-ic.c
@@ -302,102 +302,6 @@ ts_sync_scope(struct ic_context *ctx, struct shash 
*isb_ts_dps,
     shash_destroy(&nb_ts_mirrors);
 }
 
-/* Returns true if any information in gw and chassis is different. */
-static bool
-is_gateway_data_changed(const struct icsbrec_gateway *gw,
-                   const struct sbrec_chassis *chassis)
-{
-    if (strcmp(gw->hostname, chassis->hostname)) {
-        return true;
-    }
-
-    if (gw->n_encaps != chassis->n_encaps) {
-        return true;
-    }
-
-    for (int g = 0; g < gw->n_encaps; g++) {
-
-        bool found = false;
-        const struct icsbrec_encap *gw_encap = gw->encaps[g];
-        for (int s = 0; s < chassis->n_encaps; s++) {
-            const struct sbrec_encap *chassis_encap = chassis->encaps[s];
-            if (!strcmp(gw_encap->type, chassis_encap->type) &&
-                !strcmp(gw_encap->ip, chassis_encap->ip)) {
-                found = true;
-                if (!smap_equal(&gw_encap->options, &chassis_encap->options)) {
-                    return true;
-                }
-                break;
-            }
-        }
-        if (!found) {
-            return true;
-        }
-    }
-
-    return false;
-}
-
-static void
-sync_isb_gw_to_sb(struct ic_context *ctx,
-                  const struct icsbrec_gateway *gw,
-                  const struct sbrec_chassis *chassis)
-{
-    struct smap temp_map;
-    sbrec_chassis_set_hostname(chassis, gw->hostname);
-    smap_clone(&temp_map, &chassis->other_config);
-    smap_replace(&temp_map, "is-remote", "true");
-    /* Use sbrec_chassis_set_other_config instead of
-     * sbrec_chassis_update_other_config_setkey so the in-memory datum is
-     * updated for reads in the same loop iteration. */
-    sbrec_chassis_set_other_config(chassis, &temp_map);
-    smap_destroy(&temp_map);
-
-    /* Sync encaps used by this gateway. */
-    ovs_assert(gw->n_encaps);
-    struct sbrec_encap *sb_encap;
-    struct sbrec_encap **sb_encaps =
-        xmalloc(gw->n_encaps * sizeof *sb_encaps);
-    for (int i = 0; i < gw->n_encaps; i++) {
-        sb_encap = sbrec_encap_insert(ctx->ovnsb_txn);
-        sbrec_encap_set_chassis_name(sb_encap, gw->name);
-        sbrec_encap_set_ip(sb_encap, gw->encaps[i]->ip);
-        sbrec_encap_set_type(sb_encap, gw->encaps[i]->type);
-        sbrec_encap_set_options(sb_encap, &gw->encaps[i]->options);
-        sb_encaps[i] = sb_encap;
-    }
-    sbrec_chassis_set_encaps(chassis, sb_encaps, gw->n_encaps);
-    free(sb_encaps);
-}
-
-static void
-sync_sb_gw_to_isb(struct ic_context *ctx,
-                  const struct sbrec_chassis *chassis,
-                  const struct icsbrec_gateway *gw)
-{
-    icsbrec_gateway_set_hostname(gw, chassis->hostname);
-
-    /* Sync encaps used by this chassis. */
-    ovs_assert(chassis->n_encaps);
-    struct icsbrec_encap *isb_encap;
-    struct icsbrec_encap **isb_encaps =
-        xmalloc(chassis->n_encaps * sizeof *isb_encaps);
-    for (int i = 0; i < chassis->n_encaps; i++) {
-        isb_encap = icsbrec_encap_insert(ctx->ovnisb_unlocked_txn);
-        icsbrec_encap_set_gateway_name(isb_encap,
-                                      chassis->name);
-        icsbrec_encap_set_ip(isb_encap, chassis->encaps[i]->ip);
-        icsbrec_encap_set_type(isb_encap,
-                              chassis->encaps[i]->type);
-        icsbrec_encap_set_options(isb_encap,
-                                 &chassis->encaps[i]->options);
-        isb_encaps[i] = isb_encap;
-    }
-    icsbrec_gateway_set_encaps(gw, isb_encaps,
-                              chassis->n_encaps);
-    free(isb_encaps);
-}
-
 static void
 nb_addr_set_apply_diff(const void *arg, const char *item, bool add)
 {
@@ -576,65 +480,6 @@ address_set_run(struct ic_context *ctx,
     shash_destroy(&ic_remote_as);
 }
 
-void
-gateway_run(struct ic_context *ctx,
-            const struct icsbrec_availability_zone *runned_az)
-{
-    if (!ctx->ovnisb_unlocked_txn || !ctx->ovnsb_txn) {
-        return;
-    }
-
-    struct shash local_gws = SHASH_INITIALIZER(&local_gws);
-    struct shash remote_gws = SHASH_INITIALIZER(&remote_gws);
-    const struct icsbrec_gateway *gw;
-    ICSBREC_GATEWAY_FOR_EACH (gw, ctx->ovnisb_unlocked_idl) {
-        if (gw->availability_zone == runned_az) {
-            shash_add(&local_gws, gw->name, gw);
-        } else {
-            shash_add(&remote_gws, gw->name, gw);
-        }
-    }
-
-    const struct sbrec_chassis *chassis;
-    SBREC_CHASSIS_FOR_EACH (chassis, ctx->ovnsb_idl) {
-        if (smap_get_bool(&chassis->other_config, "is-interconn", false)) {
-            gw = shash_find_and_delete(&local_gws, chassis->name);
-            if (!gw) {
-                gw = icsbrec_gateway_insert(ctx->ovnisb_unlocked_txn);
-                icsbrec_gateway_set_availability_zone(gw,
-                                                runned_az);
-                icsbrec_gateway_set_name(gw, chassis->name);
-                sync_sb_gw_to_isb(ctx, chassis, gw);
-            } else if (is_gateway_data_changed(gw, chassis)) {
-                sync_sb_gw_to_isb(ctx, chassis, gw);
-            }
-        } else if (smap_get_bool(&chassis->other_config, "is-remote", false)) {
-            gw = shash_find_and_delete(&remote_gws, chassis->name);
-            if (!gw) {
-                sbrec_chassis_delete(chassis);
-            } else if (is_gateway_data_changed(gw, chassis)) {
-                sync_isb_gw_to_sb(ctx, gw, chassis);
-            }
-        }
-    }
-
-    /* Delete extra gateways from ISB for the local AZ */
-    struct shash_node *node;
-    SHASH_FOR_EACH (node, &local_gws) {
-        icsbrec_gateway_delete(node->data);
-    }
-    shash_destroy(&local_gws);
-
-    /* Create SB chassis for remote gateways in ISB */
-    SHASH_FOR_EACH (node, &remote_gws) {
-        gw = node->data;
-        chassis = sbrec_chassis_insert(ctx->ovnsb_txn);
-        sbrec_chassis_set_name(chassis, gw->name);
-        sync_isb_gw_to_sb(ctx, gw, chassis);
-    }
-    shash_destroy(&remote_gws);
-}
-
 const struct nbrec_logical_switch *
 find_ts_in_nb(struct ic_context *ctx, char *ts_name)
 {
@@ -785,7 +630,7 @@ get_lp_address_for_sb_pb(struct ic_context *ctx,
     return peer->n_mac ? *peer->mac : NULL;
 }
 
-static const struct sbrec_chassis *
+const struct sbrec_chassis *
 find_sb_chassis(struct ic_context *ctx, const char *name)
 {
     const struct sbrec_chassis *key =
diff --git a/ic/ovn-ic.h b/ic/ovn-ic.h
index be6bb9f6c..8f1609b41 100644
--- a/ic/ovn-ic.h
+++ b/ic/ovn-ic.h
@@ -65,12 +65,12 @@ struct shash;
 struct sset;
 struct icsbrec_availability_zone;
 struct icsbrec_datapath_binding;
+struct sbrec_chassis_table;
+struct icsbrec_gateway_table;
 
 enum ic_datapath_type ic_dp_get_type(
     const struct icsbrec_datapath_binding *isb_dp);
 
-void gateway_run(struct ic_context *ctx,
-                 const struct icsbrec_availability_zone *runned_az);
 void address_set_run(struct ic_context *ctx,
                      const struct icsbrec_availability_zone *runned_az);
 
@@ -97,6 +97,8 @@ void sync_service_monitor(struct ic_context *ctx,
 /* Shared IC helpers used by more than one engine node. */
 uint32_t
 allocate_dp_key(struct hmap *dp_tnlids, bool vxlan_mode, const char *name);
+const struct sbrec_chassis *
+find_sb_chassis(struct ic_context *ctx, const char *name);
 const struct nbrec_logical_switch *
 find_ts_in_nb(struct ic_context *ctx, char *ts_name);
 bool
diff --git a/tests/ovn-ic.at b/tests/ovn-ic.at
index 1435a19a3..49172951c 100644
--- a/tests/ovn-ic.at
+++ b/tests/ovn-ic.at
@@ -817,6 +817,70 @@ OVN_CLEANUP_IC([az1], [az2])
 AT_CLEANUP
 ])
 
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([ovn-ic -- incremental processing - dp_enum and gateway])
+
+# Validates the incremental-processing change handlers of the en_dp_enum and
+# en_gateway engine nodes in two ways:
+#  - the change handler of the node that reads the affected table fires
+#    (its compute stat is non-zero), and
+#  - forcing a full recompute afterwards leaves the databases unchanged,
+#    i.e. the incremental result matches a full recompute.
+
+ovn_init_ic_db
+net_add n1
+ovn_start az1
+sim_add gw1
+as gw1
+check ovs-vsctl add-br br-phys
+ovn_az_attach az1 n1 br-phys 192.168.0.1
+check ovs-vsctl set open . external-ids:ovn-is-interconn=true \
+                          external-ids:hostname=gw1
+as az1
+
+# Wait until the local gateway is registered in IC-SB.
+OVS_WAIT_UNTIL([test "$(ovn-ic-sbctl --bare --columns=hostname \
+                            find gateway name=gw1)" = gw1])
+check ovn-ic-nbctl --wait=sb sync
+
+# A targeted SB Chassis change is handled by the gateway node's change
+# handler: its compute stat becomes non-zero.
+check ovn-appctl -t ic/ovn-ic inc-engine/clear-stats
+as gw1
+check ovs-vsctl set open . external-ids:hostname=gw1-new
+as az1
+OVS_WAIT_UNTIL([test "$(ovn-ic-sbctl --bare --columns=hostname \
+                            find gateway name=gw1)" = gw1-new])
+AT_CHECK([test "$(ovn-appctl -t ic/ovn-ic \
+                      inc-engine/show-stats gateway compute)" -gt 0])
+
+# Exercise the dp_enum handler too: a transit switch creates an IC-SB
+# datapath binding, and adding a port binding drives further IC-SB updates.
+check ovn-ic-nbctl --wait=sb ts-add ts1
+OVS_WAIT_UNTIL([test "$(ovn-nbctl --bare --columns=name \
+                            find logical_switch name=ts1)" = ts1])
+check ovn-ic-nbctl --wait=sb sync
+
+# After all the incremental updates above, a forced full recompute must not
+# change any IC-managed table: incremental processing produced the same result.
+m4_define([_IC_DUMP], [
+    ovn-ic-sbctl --columns=name,hostname,availability_zone list gateway \
+        | sort > $1
+    ovn-ic-sbctl --columns=transit_switch,type,tunnel_key \
+        list datapath_binding | sort >> $1
+    ovn-nbctl --columns=name,other_config list logical_switch | sort >> $1
+    ovn-sbctl --columns=name,hostname,other_config list chassis | sort >> $1
+])
+_IC_DUMP([ic_before])
+check ovn-appctl -t ic/ovn-ic inc-engine/recompute
+check ovn-ic-nbctl --wait=sb sync
+_IC_DUMP([ic_after])
+AT_CHECK([diff ic_before ic_after])
+
+OVN_CLEANUP_IC([az1])
+AT_CLEANUP
+])
+
 OVN_FOR_EACH_NORTHD([
 AT_SETUP([ovn-ic -- port sync])
 
-- 
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