When a load balancer with options:distributed=true is attached to a
logical router that has a chassis-redirect port, the incremental
engine path in northd_handle_lb_data_changes calls
handle_od_lb_datapath_modes, which sets od->is_distributed=true on
that LR datapath. However, the LRP-level lr_in_admission flows
generated by build_adm_ctrl_flows_for_lrouter_port check
od_is_centralized(op->od) (== !od->is_distributed) via
consider_l3dgw_port_is_centralized, and those flows are not rebuilt
on LB-association changes. The result is that lr_in_admission keeps a
stale is_chassis_resident("cr-lrp-X") guard on the gateway-LRP MAC
match, so ingress traffic for the LB VIP landing on any chassis other
than the cr-port host gets dropped at admission (priority-50/55
flows).

Toggling options:distributed=false then back to true masks the bug
because the second update path in en_lb_data returns EN_UNHANDLED
(lb_data_load_balancer_handler), forcing a full recompute which
re-runs the LRP-level admission flow generator with the now-correct
od->is_distributed.

With this change, when has_distributed_lb is set,
northd_handle_lb_data_changes collects a snapshot of each
affected LR's is_distributed state before incremental
processing, then after processing recomputes the flag per LR
using the per-LR LB list (lr_lb_map) and the
lb_group->has_distributed_lb bit. If any LR's flag changed,
fall back to full recompute; otherwise the incremental path
is safe.

Also fix has_routable_lb and has_distributed_lb tracking in
handle_od_lb_changes (guard prevented flags beyond the first LB) and
add has_distributed_lb reset in destroy_tracked_data (replacing a
duplicate has_health_checks reset).

Reproducer:
  ovn-nbctl lb-add lb0 VIP:80 BE1:8080,BE2:8080 tcp
  ovn-nbctl set load_balancer lb0 options:distributed=true
  ovn-nbctl lr-lb-add ROUTER_WITH_DGP lb0
  ovn-nbctl ls-lb-add LS lb0

Without this fix the priority-50/55 admission flows on the DGP LRP
retain the is_chassis_resident("cr-lrp-...") guard and ingress LB
traffic from non-gateway chassis is dropped.

Fixes: 7b0eb4d9ed05 ("northd: Add distributed load balancer support.")
Signed-off-by: Dmitrii Shcherbakov <[email protected]>
---
 northd/en-lb-data.c     |  22 +--
 northd/en-lb-data.h     |   3 +
 northd/en-lr-stateful.c |   2 +
 northd/en-northd.c      |   1 +
 northd/lb.c             |   2 +
 northd/lb.h             |   1 +
 northd/northd.c         | 189 ++++++++++++++++++-
 northd/northd.h         |   1 +
 tests/ovn-northd.at     | 409 ++++++++++++++++++++++++++++++++++++++++
 9 files changed, 613 insertions(+), 17 deletions(-)

diff --git a/northd/en-lb-data.c b/northd/en-lb-data.c
index 04ef20bcc..3bc2e0443 100644
--- a/northd/en-lb-data.c
+++ b/northd/en-lb-data.c
@@ -45,8 +45,6 @@ static void build_lbs(const struct nbrec_load_balancer_table 
*,
 static void build_od_lb_map(const struct ovn_synced_logical_switch_map *,
                             const struct ovn_synced_logical_router_map *,
                             struct hmap *ls_lb_map, struct hmap *lr_lb_map);
-static struct od_lb_data *find_od_lb_data(struct hmap *od_lb_map,
-                                          const struct uuid *od_uuid);
 static void destroy_od_lb_data(struct od_lb_data *od_lb_data);
 static struct od_lb_data *create_od_lb_data(struct hmap *od_lb_map,
                                             const struct uuid *od_uuid);
@@ -270,6 +268,7 @@ lb_data_load_balancer_group_handler(struct engine_node 
*node, void *data)
 
             trk_lb_data->has_health_checks |= lb_group->has_health_checks;
             trk_lb_data->has_routable_lb |= lb_group->has_routable_lb;
+            trk_lb_data->has_distributed_lb |= lb_group->has_distributed_lb;
             continue;
         }
 
@@ -286,6 +285,7 @@ lb_data_load_balancer_group_handler(struct engine_node 
*node, void *data)
             add_deleted_lbgrp_to_tracked_data(lb_group, trk_lb_data);
             trk_lb_data->has_health_checks |= lb_group->has_health_checks;
             trk_lb_data->has_routable_lb |= lb_group->has_routable_lb;
+            trk_lb_data->has_distributed_lb |= lb_group->has_distributed_lb;
         } else {
             /* Determine the lbs which are added or deleted for this
              * lb group and add them to tracked data.
@@ -303,6 +303,7 @@ lb_data_load_balancer_group_handler(struct engine_node 
*node, void *data)
 
             trk_lb_data->has_health_checks |= lb_group->has_health_checks;
             trk_lb_data->has_routable_lb |= lb_group->has_routable_lb;
+            trk_lb_data->has_distributed_lb |= lb_group->has_distributed_lb;
             struct crupdated_lbgrp *clbg =
                 add_crupdated_lbgrp_to_tracked_data(lb_group, trk_lb_data);
 
@@ -647,8 +648,8 @@ create_od_lb_data(struct hmap *od_lb_map, const struct uuid 
*od_uuid)
     return od_lb_data;
 }
 
-static struct od_lb_data *
-find_od_lb_data(struct hmap *od_lb_map, const struct uuid *od_uuid)
+struct od_lb_data *
+find_od_lb_data(const struct hmap *od_lb_map, const struct uuid *od_uuid)
 {
     struct od_lb_data *od_lb_data;
     HMAP_FOR_EACH_WITH_HASH (od_lb_data, hmap_node, uuid_hash(od_uuid),
@@ -698,10 +699,8 @@ handle_od_lb_changes(struct nbrec_load_balancer 
**nbrec_lbs,
             ovs_assert(lb);
 
             trk_lb_data->has_health_checks |= lb->health_checks;
-            if (!trk_lb_data->has_routable_lb) {
-                trk_lb_data->has_routable_lb |= lb->routable;
-                trk_lb_data->has_distributed_lb |= lb->is_distributed;
-            }
+            trk_lb_data->has_routable_lb |= lb->routable;
+            trk_lb_data->has_distributed_lb |= lb->is_distributed;
         }
 
         if (unode) {
@@ -740,9 +739,8 @@ handle_od_lbgrp_changes(struct nbrec_load_balancer_group 
**nbrec_lbgrps,
             ovs_assert(lbgrp);
 
             trk_lb_data->has_health_checks |= lbgrp->has_health_checks;
-            if (!trk_lb_data->has_routable_lb) {
-                trk_lb_data->has_routable_lb |= lbgrp->has_routable_lb;
-            }
+            trk_lb_data->has_routable_lb |= lbgrp->has_routable_lb;
+            trk_lb_data->has_distributed_lb |= lbgrp->has_distributed_lb;
         }
     }
 
@@ -762,8 +760,8 @@ destroy_tracked_data(struct ed_type_lb_data *lb_data)
     lb_data->tracked_lb_data.has_dissassoc_lbs_from_lbgrps = false;
     lb_data->tracked_lb_data.has_dissassoc_lbs_from_od = false;
     lb_data->tracked_lb_data.has_dissassoc_lbgrps_from_od = false;
-    lb_data->tracked_lb_data.has_health_checks = false;
     lb_data->tracked_lb_data.has_routable_lb = false;
+    lb_data->tracked_lb_data.has_distributed_lb = false;
 
     struct hmapx_node *node;
     HMAPX_FOR_EACH_SAFE (node, &lb_data->tracked_lb_data.deleted_lbs) {
diff --git a/northd/en-lb-data.h b/northd/en-lb-data.h
index 90e85b8c4..31d930995 100644
--- a/northd/en-lb-data.h
+++ b/northd/en-lb-data.h
@@ -95,6 +95,9 @@ struct od_lb_data {
     struct uuidset *lbgrps;
 };
 
+struct od_lb_data *find_od_lb_data(const struct hmap *od_lb_map,
+                                   const struct uuid *od_uuid);
+
 /* struct which maintains the data of the engine node lb_data. */
 struct ed_type_lb_data {
     /* hmap of load balancers.  hmap node is 'struct ovn_northd_lb *' */
diff --git a/northd/en-lr-stateful.c b/northd/en-lr-stateful.c
index 695c1adab..f9f6586e5 100644
--- a/northd/en-lr-stateful.c
+++ b/northd/en-lr-stateful.c
@@ -237,6 +237,8 @@ lr_stateful_lb_data_handler(struct engine_node *node, void 
*data_)
             }
         }
 
+        lr_stateful_rec->has_distributed_lb = od->is_distributed;
+
         /* Add the lr_stateful_rec rec to the tracking data. */
         hmapx_add(&data->trk_data.crupdated, lr_stateful_rec);
     }
diff --git a/northd/en-northd.c b/northd/en-northd.c
index c34818dba..cadcf7241 100644
--- a/northd/en-northd.c
+++ b/northd/en-northd.c
@@ -229,6 +229,7 @@ northd_lb_data_handler(struct engine_node *node, void *data)
                                        &nd->lr_datapaths,
                                        &nd->lb_datapaths_map,
                                        &nd->lb_group_datapaths_map,
+                                       &lb_data->lr_lb_map,
                                        &nd->trk_data)) {
         return EN_UNHANDLED;
     }
diff --git a/northd/lb.c b/northd/lb.c
index fab122c9f..5ff9d1fad 100644
--- a/northd/lb.c
+++ b/northd/lb.c
@@ -581,6 +581,7 @@ ovn_lb_group_init(struct ovn_lb_group *lb_group,
         lb_group->lbs[i] = ovn_northd_lb_find(lbs, lb_uuid);
         lb_group->has_health_checks |= lb_group->lbs[i]->health_checks;
         lb_group->has_routable_lb |= lb_group->lbs[i]->routable;
+        lb_group->has_distributed_lb |= lb_group->lbs[i]->is_distributed;
     }
 }
 
@@ -604,6 +605,7 @@ ovn_lb_group_cleanup(struct ovn_lb_group *lb_group)
     lb_group->lb_ips = NULL;
     lb_group->has_health_checks = false;
     lb_group->has_routable_lb = false;
+    lb_group->has_distributed_lb = false;
     free(lb_group->lbs);
 }
 
diff --git a/northd/lb.h b/northd/lb.h
index db665b1d0..7a98c2f55 100644
--- a/northd/lb.h
+++ b/northd/lb.h
@@ -137,6 +137,7 @@ struct ovn_lb_group {
     struct ovn_lb_ip_set *lb_ips;
     bool has_health_checks;
     bool has_routable_lb;
+    bool has_distributed_lb;
 };
 
 struct ovn_lb_group *ovn_lb_group_create(
diff --git a/northd/northd.c b/northd/northd.c
index 0dbf17426..e8c43585a 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -3692,6 +3692,15 @@ build_lb_datapaths(const struct hmap *lbs, const struct 
hmap *lb_groups,
             ovn_lb_datapaths_add_lr(lb_dps, vector_len(&lb_group_dps->lr),
                                     vector_get_array(&lb_group_dps->lr),
                                     ods_size(lr_datapaths));
+
+            struct ovn_datapath *grp_od;
+            VECTOR_FOR_EACH (&lb_group_dps->lr, grp_od) {
+                handle_od_lb_datapath_modes(grp_od, lb_dps);
+            }
+
+            VECTOR_FOR_EACH (&lb_group_dps->ls, grp_od) {
+                handle_od_lb_datapath_modes(grp_od, lb_dps);
+            }
         }
     }
 }
@@ -5647,13 +5656,148 @@ northd_handle_sb_port_binding_changes(
  *    the logical switch datapath is added to the load balancer (represented
  *    by 'struct ovn_lb_datapaths') by calling ovn_lb_datapaths_add_ls().
  * */
+
+struct lr_distributed_snapshot {
+    struct hmap_node hmap_node;
+    struct uuid lr_uuid;
+    bool was_distributed;
+};
+
+static void
+lr_distributed_snapshot_insert_if_new(struct hmap *lr_snapshots,
+                                      struct ovn_datapath *od)
+{
+    struct lr_distributed_snapshot *existing;
+    HMAP_FOR_EACH_WITH_HASH (existing, hmap_node, uuid_hash(&od->key),
+                             lr_snapshots) {
+        if (uuid_equals(&existing->lr_uuid, &od->key)) {
+            return;
+        }
+    }
+    struct lr_distributed_snapshot *s = xmalloc(sizeof *s);
+    s->lr_uuid = od->key;
+    s->was_distributed = od->is_distributed;
+    hmap_insert(lr_snapshots, &s->hmap_node, uuid_hash(&s->lr_uuid));
+}
+
+static bool
+lr_has_distributed_lb(const struct od_lb_data *lr_lb,
+                      const struct hmap *lb_datapaths_map,
+                      const struct hmap *lbgrp_datapaths_map)
+{
+    struct uuidset_node *un;
+    UUIDSET_FOR_EACH (un, lr_lb->lbs) {
+        struct ovn_lb_datapaths *lb_dps =
+            ovn_lb_datapaths_find(lb_datapaths_map, &un->uuid);
+        if (lb_dps && lb_dps->lb->is_distributed) {
+            return true;
+        }
+    }
+    UUIDSET_FOR_EACH (un, lr_lb->lbgrps) {
+        struct ovn_lb_group_datapaths *lbgrp_dps =
+            ovn_lb_group_datapaths_find(lbgrp_datapaths_map, &un->uuid);
+        if (lbgrp_dps && lbgrp_dps->lb_group->has_distributed_lb) {
+            return true;
+        }
+    }
+    return false;
+}
+
+static bool
+lr_distributed_snapshot_check(struct hmap *lr_snapshots,
+                               const struct hmap *lr_lb_map,
+                               struct ovn_datapaths *lr_datapaths,
+                               struct hmap *lb_datapaths_map,
+                               struct hmap *lbgrp_datapaths_map)
+{
+    bool transition = false;
+    struct lr_distributed_snapshot *snap;
+    HMAP_FOR_EACH (snap, hmap_node, lr_snapshots) {
+        struct ovn_datapath *snap_od = ovn_datapath_find_(
+            &lr_datapaths->datapaths, &snap->lr_uuid);
+        if (!snap_od) {
+            continue;
+        }
+
+        snap_od->is_distributed = false;
+
+        const struct od_lb_data *lr_lb =
+            find_od_lb_data(lr_lb_map, &snap->lr_uuid);
+        if (lr_lb) {
+            snap_od->is_distributed = lr_has_distributed_lb(
+                lr_lb, lb_datapaths_map, lbgrp_datapaths_map);
+        }
+
+        if (snap_od->is_distributed != snap->was_distributed) {
+            transition = true;
+            break;
+        }
+    }
+
+    struct lr_distributed_snapshot *s, *next;
+    HMAP_FOR_EACH_SAFE (s, next, hmap_node, lr_snapshots) {
+        hmap_remove(lr_snapshots, &s->hmap_node);
+        free(s);
+    }
+    hmap_destroy(lr_snapshots);
+
+    return transition;
+}
+
+static void
+lr_distributed_snapshot_collect(struct hmap *lr_snapshots,
+                                struct tracked_lb_data *trk_lb_data,
+                                struct ovn_datapaths *lr_datapaths,
+                                struct hmap *lb_datapaths_map,
+                                struct hmap *lbgrp_datapaths_map)
+{
+    struct crupdated_od_lb_data *codlb;
+    LIST_FOR_EACH (codlb, list_node, &trk_lb_data->crupdated_lr_lbs) {
+        struct ovn_datapath *od = ovn_datapath_find_(
+            &lr_datapaths->datapaths, &codlb->od_uuid);
+        ovs_assert(od);
+        lr_distributed_snapshot_insert_if_new(lr_snapshots, od);
+    }
+
+    struct crupdated_lb *clb;
+    HMAP_FOR_EACH (clb, hmap_node, &trk_lb_data->crupdated_lbs) {
+        struct ovn_lb_datapaths *lb_dps = ovn_lb_datapaths_find(
+            lb_datapaths_map, &clb->lb->nlb->header_.uuid);
+        if (!lb_dps) {
+            continue;
+        }
+        size_t lr_idx;
+        DYNAMIC_BITMAP_FOR_EACH_1 (lr_idx, &lb_dps->nb_lr_map) {
+            struct ovn_datapath *lr_od =
+                sparse_array_get(&lr_datapaths->dps, lr_idx);
+            lr_distributed_snapshot_insert_if_new(lr_snapshots, lr_od);
+        }
+    }
+
+    struct crupdated_lbgrp *crupdated_lbgrp;
+    HMAP_FOR_EACH (crupdated_lbgrp, hmap_node,
+                   &trk_lb_data->crupdated_lbgrps) {
+        struct ovn_lb_group_datapaths *lbgrp_dps =
+            ovn_lb_group_datapaths_find(lbgrp_datapaths_map,
+                                        &crupdated_lbgrp->lbgrp->uuid);
+        if (!lbgrp_dps) {
+            continue;
+        }
+        struct ovn_datapath *grp_od;
+        VECTOR_FOR_EACH (&lbgrp_dps->lr, grp_od) {
+            lr_distributed_snapshot_insert_if_new(lr_snapshots, grp_od);
+        }
+    }
+}
+
 bool
 northd_handle_lb_data_changes(struct tracked_lb_data *trk_lb_data,
-                              struct ovn_datapaths *ls_datapaths,
-                              struct ovn_datapaths *lr_datapaths,
-                              struct hmap *lb_datapaths_map,
-                              struct hmap *lbgrp_datapaths_map,
-                              struct northd_tracked_data *nd_changes)
+                               struct ovn_datapaths *ls_datapaths,
+                               struct ovn_datapaths *lr_datapaths,
+                               struct hmap *lb_datapaths_map,
+                               struct hmap *lbgrp_datapaths_map,
+                               const struct hmap *lr_lb_map,
+                               struct northd_tracked_data *nd_changes)
 {
     if (trk_lb_data->has_health_checks) {
         /* Fall back to recompute since a tracked load balancer
@@ -5695,6 +5839,29 @@ northd_handle_lb_data_changes(struct tracked_lb_data 
*trk_lb_data,
         return false;
     }
 
+    /* If any deleted LB was distributed, fall back to recompute.
+     * The deleted-lb path removes lb_dps from lb_datapaths_map before
+     * the transition check runs, so the post-check would not see the
+     * removed LB and could miss an is_distributed false transition. */
+    if (trk_lb_data->has_distributed_lb
+        && !hmapx_is_empty(&trk_lb_data->deleted_lbs)) {
+        struct hmapx_node *hmapx_node;
+        HMAPX_FOR_EACH (hmapx_node, &trk_lb_data->deleted_lbs) {
+            struct ovn_northd_lb *lb = hmapx_node->data;
+            if (lb->is_distributed) {
+                return false;
+            }
+        }
+    }
+
+    struct hmap lr_snapshots = HMAP_INITIALIZER(&lr_snapshots);
+
+    if (trk_lb_data->has_distributed_lb) {
+        lr_distributed_snapshot_collect(&lr_snapshots, trk_lb_data,
+                                       lr_datapaths, lb_datapaths_map,
+                                       lbgrp_datapaths_map);
+    }
+
     struct ovn_lb_datapaths *lb_dps;
     struct ovn_northd_lb *lb;
     struct ovn_datapath *od;
@@ -5784,6 +5951,7 @@ northd_handle_lb_data_changes(struct tracked_lb_data 
*trk_lb_data,
                 ovs_assert(lb_dps);
                 ovn_lb_datapaths_add_ls(lb_dps, 1, &od,
                                         ods_size(ls_datapaths));
+                handle_od_lb_datapath_modes(od, lb_dps);
 
                 /* Add the lb to the northd tracked data. */
                 hmapx_add(&nd_changes->trk_lbs.crupdated, lb_dps);
@@ -5823,6 +5991,7 @@ northd_handle_lb_data_changes(struct tracked_lb_data 
*trk_lb_data,
                 ovs_assert(lb_dps);
                 ovn_lb_datapaths_add_lr(lb_dps, 1, &od,
                                         ods_size(lr_datapaths));
+                handle_od_lb_datapath_modes(od, lb_dps);
 
                 /* Add the lb to the northd tracked data. */
                 hmapx_add(&nd_changes->trk_lbs.crupdated, lb_dps);
@@ -5864,11 +6033,13 @@ northd_handle_lb_data_changes(struct tracked_lb_data 
*trk_lb_data,
             VECTOR_FOR_EACH (&lbgrp_dps->lr, od) {
                 ovn_lb_datapaths_add_lr(lb_dps, 1, &od,
                                         ods_size(lr_datapaths));
+                handle_od_lb_datapath_modes(od, lb_dps);
             }
 
             VECTOR_FOR_EACH (&lbgrp_dps->ls, od) {
                 ovn_lb_datapaths_add_ls(lb_dps, 1, &od,
                                         ods_size(ls_datapaths));
+                handle_od_lb_datapath_modes(od, lb_dps);
 
                 /* Add the ls datapath to the northd tracked data. */
                 hmapx_add(&nd_changes->ls_with_changed_lbs, od);
@@ -5888,6 +6059,14 @@ northd_handle_lb_data_changes(struct tracked_lb_data 
*trk_lb_data,
         nd_changes->type |= NORTHD_TRACKED_LS_LBS;
     }
 
+    if (trk_lb_data->has_distributed_lb) {
+        if (lr_distributed_snapshot_check(&lr_snapshots, lr_lb_map,
+                                          lr_datapaths, lb_datapaths_map,
+                                          lbgrp_datapaths_map)) {
+            return false;
+        }
+    }
+
     return true;
 }
 
diff --git a/northd/northd.h b/northd/northd.h
index 74fb58848..b1168c207 100644
--- a/northd/northd.h
+++ b/northd/northd.h
@@ -987,6 +987,7 @@ bool northd_handle_lb_data_changes(struct tracked_lb_data *,
                                    struct ovn_datapaths *lr_datapaths,
                                    struct hmap *lb_datapaths_map,
                                    struct hmap *lbgrp_datapaths_map,
+                                   const struct hmap *lr_lb_map,
                                    struct northd_tracked_data *);
 
 void build_route_policies(struct ovn_datapath *, const struct hmap *,
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index f87b14c9a..51c5b486a 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -19174,6 +19174,415 @@ OVN_CLEANUP_NORTHD
 AT_CLEANUP
 ])
 
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([lr_in_admission cr-port guard on distributed LB attach/detach])
+ovn_start
+
+check ovn-nbctl lr-add lr1
+check ovn-nbctl lrp-add lr1 lrp1 02:ac:10:01:00:01 172.16.1.1/24
+# Set gateway_mtu so build_gateway_mtu_flow() emits both the priority-50
+# (unicast, with check_pkt_larger) and priority-55 (ARP) lr_in_admission
+# flows on the LRP. Both code paths gate their is_chassis_resident
+# guard on consider_l3dgw_port_is_centralized().
+check ovn-nbctl set logical_router_port lrp1 options:gateway_mtu=1400
+check ovn-nbctl ls-add ls1
+check ovn-nbctl lsp-add-router-port ls1 ls1-lrp1 lrp1
+check ovn-nbctl lrp-set-gateway-chassis lrp1 ch1
+check ovn-nbctl --wait=sb sync
+
+# Create a distributed LB before attaching it to the router.
+check ovn-nbctl lb-add lb1 1.1.1.1:80 192.168.0.1:8080
+check ovn-nbctl set load_balancer lb1 options:distributed=true
+
+# --- Attach distributed LB to the DGP LR ---
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb lr-lb-add lr1 lb1
+
+# The lb_data handler must handle this incrementally (setting
+# has_distributed_lb in tracked data, not falling back to recompute).
+check_engine_compute lb_data incremental
+# northd recomputes on has_distributed_lb changes because LRP-level
+# lr_in_admission flows need rebuilding.
+check_engine_compute northd recompute
+
+ovn-sbctl lflow-list lr1 > lr1_lflows
+
+# Unicast (priority-50) and ARP (priority-55) admission flows on the
+# DGP LRP must not be gated by is_chassis_resident("cr-lrp1") once a
+# distributed LB is attached.
+AT_CHECK([grep lr_in_admission lr1_lflows | grep -E 'priority=5[[05]]' | grep 
'02:ac:10:01:00:01' | grep -v eth.mcast | ovn_strip_lflows], [0], [dnl
+  table=??(lr_in_admission    ), priority=50   , match=(eth.dst == 
02:ac:10:01:00:01 && inport == "lrp1"), action=(reg9[[1]] = 
check_pkt_larger(1414); xreg0[[0..47]] = 02:ac:10:01:00:01; next;)
+  table=??(lr_in_admission    ), priority=55   , match=(eth.dst == 
02:ac:10:01:00:01 && inport == "lrp1" && (arp)), action=(xreg0[[0..47]] = 
02:ac:10:01:00:01; next;)
+])
+
+# The cr-port's always-redirect option must be off, because
+# has_distributed_lb on the LR's lr_stateful record is now true.
+AT_CHECK([ovn-sbctl --bare --columns options find Port_Binding 
logical_port=cr-lrp1 | tr ' ' '\n' | sort], [0], [dnl
+distributed-port=lrp1
+])
+
+# --- Detach distributed LB from the DGP LR ---
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb lr-lb-del lr1 lb1
+
+check_engine_compute lb_data incremental
+check_engine_compute northd recompute
+
+ovn-sbctl lflow-list lr1 > lr1_lflows
+
+# The admission flows must now be gated by is_chassis_resident("cr-lrp1")
+# again, because no distributed LB is attached.
+AT_CHECK([grep lr_in_admission lr1_lflows | grep -E 'priority=5[[05]]' | grep 
'02:ac:10:01:00:01' | grep -v eth.mcast | ovn_strip_lflows], [0], [dnl
+  table=??(lr_in_admission    ), priority=50   , match=(eth.dst == 
02:ac:10:01:00:01 && inport == "lrp1" && is_chassis_resident("cr-lrp1")), 
action=(reg9[[1]] = check_pkt_larger(1414); xreg0[[0..47]] = 02:ac:10:01:00:01; 
next;)
+  table=??(lr_in_admission    ), priority=55   , match=(eth.dst == 
02:ac:10:01:00:01 && inport == "lrp1" && is_chassis_resident("cr-lrp1") && 
(arp)), action=(xreg0[[0..47]] = 02:ac:10:01:00:01; next;)
+])
+
+# The cr-port's always-redirect option must be back.
+wait_row_count Port_Binding 1 logical_port=cr-lrp1 
options:always-redirect="true"
+
+OVN_CLEANUP_NORTHD
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([lr_in_admission cr-port guard on distributed LB group attach/detach])
+ovn_start
+
+check ovn-nbctl lr-add lr1
+check ovn-nbctl lrp-add lr1 lrp1 02:ac:10:01:00:01 172.16.1.1/24
+check ovn-nbctl set logical_router_port lrp1 options:gateway_mtu=1400
+check ovn-nbctl ls-add ls1
+check ovn-nbctl lsp-add-router-port ls1 ls1-lrp1 lrp1
+check ovn-nbctl lrp-set-gateway-chassis lrp1 ch1
+check ovn-nbctl --wait=sb sync
+
+check ovn-nbctl lb-add lb1 1.1.1.1:80 192.168.0.1:8080
+check ovn-nbctl set load_balancer lb1 options:distributed=true
+lbg=$(ovn-nbctl create load_balancer_group name=lbg \
+  -- add load_balancer_group lbg load_balancer $(fetch_column nb:load_balancer 
_uuid name=lb1))
+
+# --- Attach distributed LB group to the DGP LR ---
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb add logical_router lr1 load_balancer_group $lbg
+
+check_engine_compute lb_data incremental
+check_engine_compute northd recompute
+
+ovn-sbctl lflow-list lr1 > lr1_lflows
+
+AT_CHECK([grep lr_in_admission lr1_lflows | grep -E 'priority=5[[05]]' | grep 
'02:ac:10:01:00:01' | grep -v eth.mcast | ovn_strip_lflows], [0], [dnl
+  table=??(lr_in_admission    ), priority=50   , match=(eth.dst == 
02:ac:10:01:00:01 && inport == "lrp1"), action=(reg9[[1]] = 
check_pkt_larger(1414); xreg0[[0..47]] = 02:ac:10:01:00:01; next;)
+  table=??(lr_in_admission    ), priority=55   , match=(eth.dst == 
02:ac:10:01:00:01 && inport == "lrp1" && (arp)), action=(xreg0[[0..47]] = 
02:ac:10:01:00:01; next;)
+])
+
+AT_CHECK([ovn-sbctl --bare --columns options find Port_Binding 
logical_port=cr-lrp1 | tr ' ' '\n' | sort], [0], [dnl
+distributed-port=lrp1
+])
+
+# --- Detach distributed LB group from the DGP LR ---
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb remove logical_router lr1 load_balancer_group $lbg
+
+check_engine_compute lb_data incremental
+check_engine_compute northd recompute
+
+ovn-sbctl lflow-list lr1 > lr1_lflows
+
+AT_CHECK([grep lr_in_admission lr1_lflows | grep -E 'priority=5[[05]]' | grep 
'02:ac:10:01:00:01' | grep -v eth.mcast | ovn_strip_lflows], [0], [dnl
+  table=??(lr_in_admission    ), priority=50   , match=(eth.dst == 
02:ac:10:01:00:01 && inport == "lrp1" && is_chassis_resident("cr-lrp1")), 
action=(reg9[[1]] = check_pkt_larger(1414); xreg0[[0..47]] = 02:ac:10:01:00:01; 
next;)
+  table=??(lr_in_admission    ), priority=55   , match=(eth.dst == 
02:ac:10:01:00:01 && inport == "lrp1" && is_chassis_resident("cr-lrp1") && 
(arp)), action=(xreg0[[0..47]] = 02:ac:10:01:00:01; next;)
+])
+
+wait_row_count Port_Binding 1 logical_port=cr-lrp1 
options:always-redirect="true"
+
+OVN_CLEANUP_NORTHD
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([lr_in_admission cr-port guard toggle on distributed LB option])
+ovn_start
+
+check ovn-nbctl lr-add lr1
+check ovn-nbctl lrp-add lr1 lrp1 02:ac:10:01:00:01 172.16.1.1/24
+check ovn-nbctl set logical_router_port lrp1 options:gateway_mtu=1400
+check ovn-nbctl ls-add ls1
+check ovn-nbctl lsp-add-router-port ls1 ls1-lrp1 lrp1
+check ovn-nbctl lrp-set-gateway-chassis lrp1 ch1
+check ovn-nbctl lb-add lb1 1.1.1.1:80 192.168.0.1:8080
+check ovn-nbctl --wait=sb lr-lb-add lr1 lb1
+
+# Toggle options:distributed on an already-attached LB.
+
+# --- distributed=true: admission guards must drop ---
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb set load_balancer lb1 options:distributed=true
+
+check_engine_compute northd recompute
+
+ovn-sbctl lflow-list lr1 > lr1_lflows
+AT_CHECK([grep lr_in_admission lr1_lflows | grep -E 'priority=5[[05]]' | grep 
'02:ac:10:01:00:01' | grep -v eth.mcast | ovn_strip_lflows], [0], [dnl
+  table=??(lr_in_admission    ), priority=50   , match=(eth.dst == 
02:ac:10:01:00:01 && inport == "lrp1"), action=(reg9[[1]] = 
check_pkt_larger(1414); xreg0[[0..47]] = 02:ac:10:01:00:01; next;)
+  table=??(lr_in_admission    ), priority=55   , match=(eth.dst == 
02:ac:10:01:00:01 && inport == "lrp1" && (arp)), action=(xreg0[[0..47]] = 
02:ac:10:01:00:01; next;)
+])
+
+# cr-port options must drop always-redirect=true.
+AT_CHECK([ovn-sbctl --bare --columns options find Port_Binding 
logical_port=cr-lrp1 | tr ' ' '\n' | sort], [0], [dnl
+distributed-port=lrp1
+])
+
+# --- distributed=false: admission guards must return ---
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb set load_balancer lb1 options:distributed=false
+
+check_engine_compute northd recompute
+
+ovn-sbctl lflow-list lr1 > lr1_lflows
+AT_CHECK([grep lr_in_admission lr1_lflows | grep -E 'priority=5[[05]]' | grep 
'02:ac:10:01:00:01' | grep -v eth.mcast | ovn_strip_lflows], [0], [dnl
+  table=??(lr_in_admission    ), priority=50   , match=(eth.dst == 
02:ac:10:01:00:01 && inport == "lrp1" && is_chassis_resident("cr-lrp1")), 
action=(reg9[[1]] = check_pkt_larger(1414); xreg0[[0..47]] = 02:ac:10:01:00:01; 
next;)
+  table=??(lr_in_admission    ), priority=55   , match=(eth.dst == 
02:ac:10:01:00:01 && inport == "lrp1" && is_chassis_resident("cr-lrp1") && 
(arp)), action=(xreg0[[0..47]] = 02:ac:10:01:00:01; next;)
+])
+
+# cr-port options must regain always-redirect=true.
+wait_row_count Port_Binding 1 logical_port=cr-lrp1 
options:always-redirect="true"
+
+# --- distributed=true again: admission guards must drop again ---
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb set load_balancer lb1 options:distributed=true
+
+check_engine_compute northd recompute
+
+ovn-sbctl lflow-list lr1 > lr1_lflows
+AT_CHECK([grep lr_in_admission lr1_lflows | grep -E 'priority=5[[05]]' | grep 
'02:ac:10:01:00:01' | grep -v eth.mcast | ovn_strip_lflows], [0], [dnl
+  table=??(lr_in_admission    ), priority=50   , match=(eth.dst == 
02:ac:10:01:00:01 && inport == "lrp1"), action=(reg9[[1]] = 
check_pkt_larger(1414); xreg0[[0..47]] = 02:ac:10:01:00:01; next;)
+  table=??(lr_in_admission    ), priority=55   , match=(eth.dst == 
02:ac:10:01:00:01 && inport == "lrp1" && (arp)), action=(xreg0[[0..47]] = 
02:ac:10:01:00:01; next;)
+])
+
+AT_CHECK([ovn-sbctl --bare --columns options find Port_Binding 
logical_port=cr-lrp1 | tr ' ' '\n' | sort], [0], [dnl
+distributed-port=lrp1
+])
+
+OVN_CLEANUP_NORTHD
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([lr_in_admission cr-port guard on LB group membership changes])
+ovn_start
+
+check ovn-nbctl lr-add lr1
+check ovn-nbctl lrp-add lr1 lrp1 02:ac:10:01:00:01 172.16.1.1/24
+check ovn-nbctl set logical_router_port lrp1 options:gateway_mtu=1400
+check ovn-nbctl ls-add ls1
+check ovn-nbctl lsp-add-router-port ls1 ls1-lrp1 lrp1
+check ovn-nbctl lrp-set-gateway-chassis lrp1 ch1
+check ovn-nbctl --wait=sb sync
+
+check ovn-nbctl lb-add lb1 1.1.1.1:80 192.168.0.1:8080
+check ovn-nbctl set load_balancer lb1 options:distributed=true
+lbg=$(ovn-nbctl create load_balancer_group name=lbg)
+
+# Attach the empty group, then add the distributed LB to it.
+check ovn-nbctl --wait=sb add logical_router lr1 load_balancer_group $lbg
+
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb add load_balancer_group $lbg load_balancer 
$(fetch_column nb:load_balancer _uuid name=lb1)
+
+# Adding an LB to an attached group goes through
+# lb_data_load_balancer_group_handler; the lb_data engine must handle
+# it incrementally.
+check_engine_compute lb_data incremental
+check_engine_compute northd recompute
+
+ovn-sbctl lflow-list lr1 > lr1_lflows
+AT_CHECK([grep lr_in_admission lr1_lflows | grep -E 'priority=5[[05]]' | grep 
'02:ac:10:01:00:01' | grep -v eth.mcast | ovn_strip_lflows], [0], [dnl
+  table=??(lr_in_admission    ), priority=50   , match=(eth.dst == 
02:ac:10:01:00:01 && inport == "lrp1"), action=(reg9[[1]] = 
check_pkt_larger(1414); xreg0[[0..47]] = 02:ac:10:01:00:01; next;)
+  table=??(lr_in_admission    ), priority=55   , match=(eth.dst == 
02:ac:10:01:00:01 && inport == "lrp1" && (arp)), action=(xreg0[[0..47]] = 
02:ac:10:01:00:01; next;)
+])
+
+AT_CHECK([ovn-sbctl --bare --columns options find Port_Binding 
logical_port=cr-lrp1 | tr ' ' '\n' | sort], [0], [dnl
+distributed-port=lrp1
+])
+
+# Remove the distributed LB from the group. The group is now empty,
+# so is_distributed must flip back to false.
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb remove load_balancer_group $lbg load_balancer 
$(fetch_column nb:load_balancer _uuid name=lb1)
+
+check_engine_compute lb_data incremental
+check_engine_compute northd recompute
+
+ovn-sbctl lflow-list lr1 > lr1_lflows
+AT_CHECK([grep lr_in_admission lr1_lflows | grep -E 'priority=5[[05]]' | grep 
'02:ac:10:01:00:01' | grep -v eth.mcast | ovn_strip_lflows], [0], [dnl
+  table=??(lr_in_admission    ), priority=50   , match=(eth.dst == 
02:ac:10:01:00:01 && inport == "lrp1" && is_chassis_resident("cr-lrp1")), 
action=(reg9[[1]] = check_pkt_larger(1414); xreg0[[0..47]] = 02:ac:10:01:00:01; 
next;)
+  table=??(lr_in_admission    ), priority=55   , match=(eth.dst == 
02:ac:10:01:00:01 && inport == "lrp1" && is_chassis_resident("cr-lrp1") && 
(arp)), action=(xreg0[[0..47]] = 02:ac:10:01:00:01; next;)
+])
+
+wait_row_count Port_Binding 1 logical_port=cr-lrp1 
options:always-redirect="true"
+
+OVN_CLEANUP_NORTHD
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([lr_in_admission cr-port guard with multiple distributed LBs])
+ovn_start
+
+check ovn-nbctl lr-add lr1
+check ovn-nbctl lrp-add lr1 lrp1 02:ac:10:01:00:01 172.16.1.1/24
+check ovn-nbctl set logical_router_port lrp1 options:gateway_mtu=1400
+check ovn-nbctl ls-add ls1
+check ovn-nbctl lsp-add-router-port ls1 ls1-lrp1 lrp1
+check ovn-nbctl lrp-set-gateway-chassis lrp1 ch1
+check ovn-nbctl --wait=sb sync
+
+check ovn-nbctl lb-add lb1 1.1.1.1:80 192.168.0.1:8080
+check ovn-nbctl set load_balancer lb1 options:distributed=true
+check ovn-nbctl lb-add lb2 2.2.2.2:80 192.168.0.2:8080
+check ovn-nbctl set load_balancer lb2 options:distributed=true
+check ovn-nbctl --wait=sb lr-lb-add lr1 lb1
+check ovn-nbctl --wait=sb lr-lb-add lr1 lb2
+
+# Both distributed LBs are attached. Admission guards must be off.
+ovn-sbctl lflow-list lr1 > lr1_lflows
+AT_CHECK([grep lr_in_admission lr1_lflows | grep -E 'priority=5[[05]]' | grep 
'02:ac:10:01:00:01' | grep -v eth.mcast | ovn_strip_lflows], [0], [dnl
+  table=??(lr_in_admission    ), priority=50   , match=(eth.dst == 
02:ac:10:01:00:01 && inport == "lrp1"), action=(reg9[[1]] = 
check_pkt_larger(1414); xreg0[[0..47]] = 02:ac:10:01:00:01; next;)
+  table=??(lr_in_admission    ), priority=55   , match=(eth.dst == 
02:ac:10:01:00:01 && inport == "lrp1" && (arp)), action=(xreg0[[0..47]] = 
02:ac:10:01:00:01; next;)
+])
+
+# Remove lb1. lb2 is still distributed, so is_distributed must remain
+# true and admission guards must stay off.
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb lr-lb-del lr1 lb1
+
+# Detach goes through the per-LB lb_data path; must stay incremental.
+check_engine_compute lb_data incremental
+check_engine_compute northd recompute
+
+ovn-sbctl lflow-list lr1 > lr1_lflows
+AT_CHECK([grep lr_in_admission lr1_lflows | grep -E 'priority=5[[05]]' | grep 
'02:ac:10:01:00:01' | grep -v eth.mcast | ovn_strip_lflows], [0], [dnl
+  table=??(lr_in_admission    ), priority=50   , match=(eth.dst == 
02:ac:10:01:00:01 && inport == "lrp1"), action=(reg9[[1]] = 
check_pkt_larger(1414); xreg0[[0..47]] = 02:ac:10:01:00:01; next;)
+  table=??(lr_in_admission    ), priority=55   , match=(eth.dst == 
02:ac:10:01:00:01 && inport == "lrp1" && (arp)), action=(xreg0[[0..47]] = 
02:ac:10:01:00:01; next;)
+])
+
+# lb2 still distributed -- cr-port options must keep always-redirect off.
+AT_CHECK([ovn-sbctl --bare --columns options find Port_Binding 
logical_port=cr-lrp1 | tr ' ' '\n' | sort], [0], [dnl
+distributed-port=lrp1
+])
+
+# Remove lb2. No distributed LBs remain, so is_distributed must flip
+# back to false and admission guards must return.
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb lr-lb-del lr1 lb2
+
+check_engine_compute lb_data incremental
+check_engine_compute northd recompute
+
+ovn-sbctl lflow-list lr1 > lr1_lflows
+AT_CHECK([grep lr_in_admission lr1_lflows | grep -E 'priority=5[[05]]' | grep 
'02:ac:10:01:00:01' | grep -v eth.mcast | ovn_strip_lflows], [0], [dnl
+  table=??(lr_in_admission    ), priority=50   , match=(eth.dst == 
02:ac:10:01:00:01 && inport == "lrp1" && is_chassis_resident("cr-lrp1")), 
action=(reg9[[1]] = check_pkt_larger(1414); xreg0[[0..47]] = 02:ac:10:01:00:01; 
next;)
+  table=??(lr_in_admission    ), priority=55   , match=(eth.dst == 
02:ac:10:01:00:01 && inport == "lrp1" && is_chassis_resident("cr-lrp1") && 
(arp)), action=(xreg0[[0..47]] = 02:ac:10:01:00:01; next;)
+])
+
+wait_row_count Port_Binding 1 logical_port=cr-lrp1 
options:always-redirect="true"
+
+OVN_CLEANUP_NORTHD
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([lr_in_admission cr-port guard incremental on subsequent distributed 
LB])
+ovn_start
+
+check ovn-nbctl lr-add lr1
+check ovn-nbctl lrp-add lr1 lrp1 02:ac:10:01:00:01 172.16.1.1/24
+check ovn-nbctl set logical_router_port lrp1 options:gateway_mtu=1400
+check ovn-nbctl ls-add ls1
+check ovn-nbctl lsp-add-router-port ls1 ls1-lrp1 lrp1
+check ovn-nbctl lrp-set-gateway-chassis lrp1 ch1
+check ovn-nbctl --wait=sb sync
+
+check ovn-nbctl lb-add lb1 1.1.1.1:80 192.168.0.1:8080
+check ovn-nbctl set load_balancer lb1 options:distributed=true
+check ovn-nbctl lb-add lb2 1.1.1.2:80 192.168.0.2:8080
+check ovn-nbctl set load_balancer lb2 options:distributed=true
+
+# --- Attach lb1: is_distributed flips false->true; northd recompute. ---
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb lr-lb-add lr1 lb1
+
+check_engine_compute lb_data incremental
+check_engine_compute northd recompute
+
+# Admission guards must drop (no is_chassis_resident).
+ovn-sbctl lflow-list lr1 > lr1_lflows
+AT_CHECK([grep lr_in_admission lr1_lflows | grep -E 'priority=5[[05]]' | grep 
'02:ac:10:01:00:01' | grep -v eth.mcast | ovn_strip_lflows], [0], [dnl
+  table=??(lr_in_admission    ), priority=50   , match=(eth.dst == 
02:ac:10:01:00:01 && inport == "lrp1"), action=(reg9[[1]] = 
check_pkt_larger(1414); xreg0[[0..47]] = 02:ac:10:01:00:01; next;)
+  table=??(lr_in_admission    ), priority=55   , match=(eth.dst == 
02:ac:10:01:00:01 && inport == "lrp1" && (arp)), action=(xreg0[[0..47]] = 
02:ac:10:01:00:01; next;)
+])
+
+# --- Attach lb2: is_distributed already true, no transition.
+#     northd must stay incremental. ---
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb lr-lb-add lr1 lb2
+
+check_engine_compute lb_data incremental
+check_engine_compute northd incremental
+
+# Admission guards must still drop.
+ovn-sbctl lflow-list lr1 > lr1_lflows
+AT_CHECK([grep lr_in_admission lr1_lflows | grep -E 'priority=5[[05]]' | grep 
'02:ac:10:01:00:01' | grep -v eth.mcast | ovn_strip_lflows], [0], [dnl
+  table=??(lr_in_admission    ), priority=50   , match=(eth.dst == 
02:ac:10:01:00:01 && inport == "lrp1"), action=(reg9[[1]] = 
check_pkt_larger(1414); xreg0[[0..47]] = 02:ac:10:01:00:01; next;)
+  table=??(lr_in_admission    ), priority=55   , match=(eth.dst == 
02:ac:10:01:00:01 && inport == "lrp1" && (arp)), action=(xreg0[[0..47]] = 
02:ac:10:01:00:01; next;)
+])
+
+OVN_CLEANUP_NORTHD
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([lr_in_admission cr-port guard on distributed LB delete])
+ovn_start
+
+check ovn-nbctl lr-add lr1
+check ovn-nbctl lrp-add lr1 lrp1 02:ac:10:01:00:01 172.16.1.1/24
+check ovn-nbctl set logical_router_port lrp1 options:gateway_mtu=1400
+check ovn-nbctl ls-add ls1
+check ovn-nbctl lsp-add-router-port ls1 ls1-lrp1 lrp1
+check ovn-nbctl lrp-set-gateway-chassis lrp1 ch1
+check ovn-nbctl --wait=sb sync
+
+check ovn-nbctl lb-add lb1 1.1.1.1:80 192.168.0.1:8080
+check ovn-nbctl set load_balancer lb1 options:distributed=true
+check ovn-nbctl --wait=sb lr-lb-add lr1 lb1
+
+# Admission guards must drop (no is_chassis_resident).
+ovn-sbctl lflow-list lr1 > lr1_lflows
+AT_CHECK([grep lr_in_admission lr1_lflows | grep -E 'priority=5[[05]]' | grep 
'02:ac:10:01:00:01' | grep -v eth.mcast | ovn_strip_lflows], [0], [dnl
+  table=??(lr_in_admission    ), priority=50   , match=(eth.dst == 
02:ac:10:01:00:01 && inport == "lrp1"), action=(reg9[[1]] = 
check_pkt_larger(1414); xreg0[[0..47]] = 02:ac:10:01:00:01; next;)
+  table=??(lr_in_admission    ), priority=55   , match=(eth.dst == 
02:ac:10:01:00:01 && inport == "lrp1" && (arp)), action=(xreg0[[0..47]] = 
02:ac:10:01:00:01; next;)
+])
+
+# Delete lb1 entirely. is_distributed must flip back to false,
+# northd must recompute, and the cr-port guard must return.
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb lb-del lb1
+
+check_engine_compute lb_data incremental
+check_engine_compute northd recompute
+
+ovn-sbctl lflow-list lr1 > lr1_lflows
+AT_CHECK([grep lr_in_admission lr1_lflows | grep -E 'priority=5[[05]]' | grep 
'02:ac:10:01:00:01' | grep -v eth.mcast | ovn_strip_lflows], [0], [dnl
+  table=??(lr_in_admission    ), priority=50   , match=(eth.dst == 
02:ac:10:01:00:01 && inport == "lrp1" && is_chassis_resident("cr-lrp1")), 
action=(reg9[[1]] = check_pkt_larger(1414); xreg0[[0..47]] = 02:ac:10:01:00:01; 
next;)
+  table=??(lr_in_admission    ), priority=55   , match=(eth.dst == 
02:ac:10:01:00:01 && inport == "lrp1" && is_chassis_resident("cr-lrp1") && 
(arp)), action=(xreg0[[0..47]] = 02:ac:10:01:00:01; next;)
+])
+
+wait_row_count Port_Binding 1 logical_port=cr-lrp1 
options:always-redirect="true"
+
+OVN_CLEANUP_NORTHD
+AT_CLEANUP
+])
+
 OVN_FOR_EACH_NORTHD_NO_HV([
 AT_SETUP([ip_port_mappings validation: IPv6])
 ovn_start
-- 
2.53.0


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

Reply via email to