Until now any change to a logical switch port of type "router" fell back
to a full northd recompute, because lsp_can_be_inc_processed() only
allowed plain VIF and remote ports.  Enable incremental processing for
creation and deletion of router ports as well.

Unlike VIF/remote ports, a router port is not self-contained: a full
recompute wires a peer relationship to the logical router port (LRP) in
join_logical_ports() and populates aggregate datapath state
(od->router_ports, peer->od->ls_peers, the "router" address, ...).  The
incremental path now wires (and tears down) this peer relationship
itself, mirroring the router branch of join_logical_ports().  The wiring
runs before the SB port binding is synced, as ovn_port_update_sbrec()
consults op->peer.

Some flows that toggle with the presence of a router port are owned by
lflow_refs other than the port's own and are handled explicitly:

  - Sibling switch ports' ARP-resolve flows depend on od->router_ports,
    so the existing ports are re-tracked to the lflow engine.
  - The peer LRP's SB Port_Binding options:peer is (re)synced.
  - The per-switch ls_stateful lflow_ref (skip-conntrack flows generated
    for each router port) is regenerated in the lflow port-change handler.
  - The router port's stateful_lflow_ref is cleared on deletion (only
    router ports populate it via build_lbnat_lflows_iterate_by_lsp()).

Dependencies that live outside the router port's lflow_ref and that this
path does not keep in sync trigger a fall back to a full recompute
(router_lsp_needs_recompute()): distributed gateway ports, gateway
routers, NAT, load balancers, static or dynamic routing, IPv6 RA,
arp_proxy, ACLs, vtep ports, mcast relay, or more than one router port on
the switch.

An update to a router port other than its "up" column also falls back to
recompute, as re-wiring the peer relationship on reinit is not supported.

Add tests covering incremental create/delete of a router port (including
regeneration of a sibling VIF's ARP-resolve flow) and the recompute
fallbacks for the distributed-gateway and dynamic-routing cases.

Assisted-by: Claude Opus 4.8, ClaudeCode
Signed-off-by: Lucas Vargas Dias <[email protected]>
---
 northd/en-ls-stateful.c |   7 +
 northd/northd.c         | 340 +++++++++++++++++++++++++++++++++++++++-
 tests/ovn-northd.at     | 139 +++++++++++++++-
 3 files changed, 471 insertions(+), 15 deletions(-)

diff --git a/northd/en-ls-stateful.c b/northd/en-ls-stateful.c
index 1127b7d50..19c44a05e 100644
--- a/northd/en-ls-stateful.c
+++ b/northd/en-ls-stateful.c
@@ -336,6 +336,13 @@ ls_stateful_table_find_(const struct ls_stateful_table 
*table,
     return NULL;
 }
 
+const struct ls_stateful_record *
+ls_stateful_table_find(const struct ls_stateful_table *table,
+                       const struct nbrec_logical_switch *nbs)
+{
+    return ls_stateful_table_find_(table, nbs);
+}
+
 static struct ls_stateful_record *
 ls_stateful_record_create(struct ls_stateful_table *table,
                           const struct ovn_datapath *od,
diff --git a/northd/northd.c b/northd/northd.c
index f4bb5096e..404ab50a9 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -4331,13 +4331,35 @@ sync_pbs_for_northd_changed_ovn_ports(
     const struct lr_stateful_table *lr_stateful_table)
 {
     struct hmapx_node *hmapx_node;
+    struct ovn_port *op;
 
     HMAPX_FOR_EACH (hmapx_node, &trk_ovn_ports->created) {
-        sync_pb_for_lsp(hmapx_node->data, lr_stateful_table);
+        op = hmapx_node->data;
+        sync_pb_for_lsp(op, lr_stateful_table);
+        /* A newly created router port must set options:peer on its peer LRP's
+         * port binding. */
+        if (lsp_is_router(op->nbsp) && op->peer && op->peer->nbrp) {
+            sync_pb_for_lrp(op->peer, lr_stateful_table);
+        }
     }
 
     HMAPX_FOR_EACH (hmapx_node, &trk_ovn_ports->updated) {
-        sync_pb_for_lsp(hmapx_node->data, lr_stateful_table);
+        op = hmapx_node->data;
+        sync_pb_for_lsp(op, lr_stateful_table);
+        if (lsp_is_router(op->nbsp) && op->peer && op->peer->nbrp) {
+            sync_pb_for_lrp(op->peer, lr_stateful_table);
+        }
+    }
+
+    /* A deleted router port must clear options:peer on its (still existing)
+     * peer LRP's port binding.  ls_router_port_unwire_peer() already reset
+     * op->peer->peer to NULL, so sync_pb_for_lrp() will omit the peer
+     * option. */
+    HMAPX_FOR_EACH (hmapx_node, &trk_ovn_ports->deleted) {
+        op = hmapx_node->data;
+        if (lsp_is_router(op->nbsp) && op->peer && op->peer->nbrp) {
+            sync_pb_for_lrp(op->peer, lr_stateful_table);
+        }
     }
 }
 
@@ -4689,10 +4711,10 @@ destroy_northd_tracked_data(struct northd_data *nd)
 static bool
 lsp_can_be_inc_processed(const struct nbrec_logical_switch_port *nbsp)
 {
-    /* Support only normal VIF, remote, localport, and virtual ports for
-     * now. */
+    /* Support only normal VIF, remote, localport, virtual, and router
+     * ports for now. */
     if (nbsp->type[0] && !lsp_is_remote(nbsp) && !lsp_is_localport(nbsp) &&
-        !lsp_is_virtual(nbsp)) {
+        !lsp_is_virtual(nbsp) && !lsp_is_router(nbsp)) {
         return false;
     }
 
@@ -4779,6 +4801,177 @@ virtual_lsp_needs_recompute(struct ovn_datapath *od, 
const char *lport)
     return false;
 }
 
+
+/* A logical switch port of type "router" is not self-contained: in a full
+ * recompute join_logical_ports() wires a peer relationship to the logical
+ * router port (LRP) and populates aggregate datapath state (see
+ * ls_router_port_wire_peer()).  Several flows that toggle with the presence of
+ * such a port are owned by lflow_refs other than the port's own (the peer
+ * LRP's ref, the ls_stateful ref, the switch datapath ref, ...), which the
+ * incremental LSP path does not keep in sync.  Return true when any such
+ * dependency is present so the caller falls back to a full recompute.
+ *
+ * 'is_delete' is true when 'nbsp' is being removed (the port is still counted
+ * in od->router_ports at this point). */
+static bool
+router_lsp_needs_recompute(struct ovn_datapath *od,
+                           const struct nbrec_logical_switch_port *nbsp,
+                           const struct hmap *lr_ports, bool is_delete)
+{
+    /* arp_proxy adds proxy-arp admission flows owned by the peer LRP's
+     * lflow_ref and sets od->has_arp_proxy_port. */
+    if (smap_get(&nbsp->options, "arp_proxy")) {
+        return true;
+    }
+
+    /* Handle a single router port per switch for now.  With more than one
+     * router port the router ports generate inter-router-port
+     * ARP-resolve/routable flows for each other (owned by sibling refs) that
+     * this path does not regenerate. */
+    if (is_delete ? vector_len(&od->router_ports) > 1
+                  : !vector_is_empty(&od->router_ports)) {
+        return true;
+    }
+
+    /* Switch-level stateful/aggregate dependencies that live outside the
+     * port's own lflow_ref: ls_stateful skip-conntrack flows over
+     * od->router_ports, LB install set (od->ls_peers), and vtep hairpin flows
+     * owned by od->datapath_lflows. */
+    if (od->nbs->n_acls || od->nbs->n_load_balancer ||
+        od->nbs->n_load_balancer_group || od->has_vtep_lports) {
+        return true;
+    }
+
+    const char *peer_name = smap_get(&nbsp->options, "router-port");
+    if (!peer_name) {
+        /* No peer to wire; the port is inert. */
+        return false;
+    }
+
+    struct ovn_port *peer = ovn_port_find(lr_ports, peer_name);
+    if (!peer || !peer->nbrp) {
+        /* Peer LRP not present yet.  This matches join_logical_ports(), which
+         * leaves op->peer NULL and does not add the port to od->router_ports;
+         * the port is inert and can be processed incrementally. */
+        return false;
+    }
+
+    /* Bad LRP-to-LRP peering or a disabled LRP; let recompute deal with it. */
+    if (peer->nbrp->peer || !lrport_is_enabled(peer->nbrp)) {
+        return true;
+    }
+
+    /* Distributed gateway / gateway-router complexity: l3gateway and
+     * chassisredirect SB port types, GARP nat_addresses, cr_port. */
+    if (lrp_is_l3dgw(peer) || peer->cr_port ||
+        !vector_is_empty(&peer->od->l3dgw_ports) ||
+        peer->od->is_gw_router ||
+        smap_get(&peer->od->nbr->options, "chassis")) {
+        return true;
+    }
+
+    /* NAT, static routes, LBs and dynamic routing on the peer router pull in
+     * stateful/routable/advertised-route dependencies not tracked here. */
+    const struct nbrec_logical_router *nbr = peer->od->nbr;
+    if (nbr->n_nat || nbr->n_static_routes || nbr->n_load_balancer ||
+        nbr->n_load_balancer_group) {
+        return true;
+    }
+    if (peer->od->dynamic_routing ||
+        peer->od->dynamic_routing_redistribute != DRRM_NONE) {
+        return true;
+    }
+
+    /* IPv6 RA flows are owned by the peer LRP's lflow_ref and toggle with the
+     * peer's presence. */
+    if (!smap_is_empty(&peer->nbrp->ipv6_ra_configs)) {
+        return true;
+    }
+
+    /* mcast relay would flip od->mcast_info.sw.flood_relay, changing flows
+     * owned by od->datapath_lflows. */
+    if (peer->od->mcast_info.rtr.relay) {
+        return true;
+    }
+
+    return false;
+}
+
+/* Wire the peer relationship of a logical switch port 'op' of type "router",
+ * mirroring the router branch of join_logical_ports().  'op->od' must be set.
+ * Must run before the SB port binding is synced, as ovn_port_update_sbrec()
+ * consults op->peer. */
+static void
+ls_router_port_wire_peer(struct ovn_port *op, const struct hmap *lr_ports)
+{
+    const char *peer_name = smap_get(&op->nbsp->options, "router-port");
+    if (!peer_name) {
+        return;
+    }
+
+    struct ovn_port *peer = ovn_port_find(lr_ports, peer_name);
+    if (!peer || !peer->nbrp || peer->nbrp->peer) {
+        return;
+    }
+
+    vector_push(&op->od->router_ports, &op);
+    vector_push(&peer->od->ls_peers, &op->od);
+    peer->peer = op;
+    op->peer = peer;
+}
+
+/* Fill op->lsp_addrs for the "router" address of a router-type LSP from its
+ * peer LRP networks (skipped by parse_lsp_addrs()).  Must run after
+ * ls_port_init() and with op->peer set. */
+static void
+ls_router_port_add_peer_networks(struct ovn_port *op)
+{
+    for (size_t j = 0; j < op->nbsp->n_addresses; j++) {
+        if (!strcmp(op->nbsp->addresses[j], "router")) {
+            if (extract_lrp_networks(op->peer->nbrp,
+                                     &op->lsp_addrs[op->n_lsp_addrs])) {
+                op->n_lsp_addrs++;
+            }
+            break;
+        }
+    }
+}
+
+/* Tear down the peer relationship wired by ls_router_port_wire_peer() when a
+ * router-type LSP is deleted.  Keeps op->peer set so the SB port-binding sync
+ * node can still reach the peer LRP to clear its options:peer (deleted tracked
+ * ports are freed only at the end of the engine run). */
+static void
+ls_router_port_unwire_peer(struct ovn_port *op)
+{
+    struct ovn_port *peer = op->peer;
+    if (!peer) {
+        return;
+    }
+
+    struct ovn_port *rp;
+    size_t i = 0;
+    VECTOR_FOR_EACH (&op->od->router_ports, rp) {
+        if (rp == op) {
+            vector_remove(&op->od->router_ports, i, NULL);
+            break;
+        }
+        i++;
+    }
+
+    struct ovn_datapath *ls_od;
+    i = 0;
+    VECTOR_FOR_EACH (&peer->od->ls_peers, ls_od) {
+        if (ls_od == op->od) {
+            vector_remove(&peer->od->ls_peers, i, NULL);
+            break;
+        }
+        i++;
+    }
+
+    peer->peer = NULL;
+}
+
 static bool
 ls_port_has_changed(const struct nbrec_logical_switch_port *new)
 {
@@ -4846,7 +5039,7 @@ ls_port_init(struct ovn_port *op, struct ovsdb_idl_txn 
*ovnsb_txn,
 static struct ovn_port *
 ls_port_create(struct ovsdb_idl_txn *ovnsb_txn, struct hmap *ls_ports,
                const char *key, const struct nbrec_logical_switch_port *nbsp,
-               struct ovn_datapath *od,
+               struct ovn_datapath *od, const struct hmap *lr_ports,
                const struct sbrec_mirror_table *sbrec_mirror_table,
                struct ovsdb_idl_index *sbrec_chassis_by_name,
                struct ovsdb_idl_index *sbrec_chassis_by_hostname,
@@ -4855,6 +5048,16 @@ ls_port_create(struct ovsdb_idl_txn *ovnsb_txn, struct 
hmap *ls_ports,
     struct ovn_port *op = ovn_port_create(ls_ports, key, nbsp, NULL,
                                           NULL);
     hmap_insert(&od->ports, &op->dp_node, hmap_node_hash(&op->key_node));
+
+    /* A router-type LSP must have its peer LRP wired before the SB port
+     * binding is synced by ls_port_init() (ovn_port_update_sbrec() consults
+     * op->peer).  op->od is normally set inside ls_port_init(); set it early
+     * so the peer wiring can use op->od->router_ports. */
+    if (lsp_is_router(nbsp)) {
+        op->od = od;
+        ls_router_port_wire_peer(op, lr_ports);
+    }
+
     if (!ls_port_init(op, ovnsb_txn, od, NULL, sbrec_mirror_table,
                       sbrec_chassis_by_name, sbrec_chassis_by_hostname,
                       sbrec_encap_by_ip)) {
@@ -4862,6 +5065,10 @@ ls_port_create(struct ovsdb_idl_txn *ovnsb_txn, struct 
hmap *ls_ports,
         return NULL;
     }
 
+    if (lsp_is_router(nbsp) && op->peer) {
+        ls_router_port_add_peer_networks(op);
+    }
+
     return op;
 }
 
@@ -5056,6 +5263,7 @@ ls_handle_lsp_changes(struct ovsdb_idl_txn *ovnsb_idl_txn,
 
     bool ls_had_only_router_ports = (!vector_is_empty(&od->router_ports)
             && (vector_len(&od->router_ports) == hmap_count(&od->ports)));
+    bool router_ports_changed = false;
 
     struct ovs_list existing_virtual_ports;
     struct ovn_port *op;
@@ -5083,9 +5291,17 @@ ls_handle_lsp_changes(struct ovsdb_idl_txn 
*ovnsb_idl_txn,
                      * to recompute. */
                     goto fail;
                 }
+                if (lsp_is_router(new_nbsp) &&
+                    router_lsp_needs_recompute(od, new_nbsp, &nd->lr_ports,
+                                               false)) {
+                    /* This router port has a dependency on a connected router
+                     * that can't be handled incrementally.  Fall back to
+                     * recompute. */
+                    goto fail;
+                }
                 op = ls_port_create(ovnsb_idl_txn, &nd->ls_ports,
                                     new_nbsp->name, new_nbsp, od,
-                                    ni->sbrec_mirror_table,
+                                    &nd->lr_ports, ni->sbrec_mirror_table,
                                     ni->sbrec_chassis_by_name,
                                     ni->sbrec_chassis_by_hostname,
                                     ni->sbrec_encap_by_ip);
@@ -5093,9 +5309,30 @@ ls_handle_lsp_changes(struct ovsdb_idl_txn 
*ovnsb_idl_txn,
                     goto fail;
                 }
                 add_op_to_northd_tracked_ports(&trk_lsps->created, op);
+                if (lsp_is_router(new_nbsp) && op->peer) {
+                    /* A router port was added to od->router_ports; sibling
+                     * ports' ARP-resolve flows must be regenerated. */
+                    router_ports_changed = true;
+                }
             } else if (ls_port_has_changed(new_nbsp)) {
                 /* Existing port updated */
                 bool temp = false;
+                if (lsp_is_router(new_nbsp)) {
+                    /* The SB port binding type of a router port ("patch",
+                     * "l3gateway", ...) never matches its NB type ("router"),
+                     * so lsp_is_type_changed() can't be used here.  Re-wiring
+                     * the peer relationship on reinit is not supported, so
+                     * fall back to recompute on any change other than the "up"
+                     * column; an "up"-only change does not affect router-port
+                     * flows, so ignore it. */
+                    if (!op->lsp_can_be_inc_processed ||
+                        !lsp_can_be_inc_processed(new_nbsp) ||
+                        check_lsp_changes_other_than_up(new_nbsp)) {
+                        goto fail;
+                    }
+                    op->visited = true;
+                    continue;
+                }
                 if (lsp_is_type_changed(op->sb, new_nbsp, &temp) ||
                     !op->lsp_can_be_inc_processed ||
                     !lsp_can_be_inc_processed(new_nbsp)) {
@@ -5174,6 +5411,14 @@ ls_handle_lsp_changes(struct ovsdb_idl_txn 
*ovnsb_idl_txn,
                  * recompute. */
                 goto fail;
             }
+            if (lsp_is_router(op->nbsp) &&
+                router_lsp_needs_recompute(od, op->nbsp, &nd->lr_ports,
+                                           true)) {
+                /* This router port has a dependency on a connected router that
+                 * can't be regenerated incrementally.  Fall back to
+                 * recompute. */
+                goto fail;
+            }
             if (sset_contains(&nd->svc_monitor_lsps, op->key)) {
                 /* This port was used for svc monitor, which may be
                  * impacted by this deletion. Fallback to recompute. */
@@ -5187,6 +5432,14 @@ ls_handle_lsp_changes(struct ovsdb_idl_txn 
*ovnsb_idl_txn,
                  * resolved; fall back to recompute. */
                 goto fail;
             }
+            if (lsp_is_router(op->nbsp) && op->peer) {
+                /* Tear down the peer wiring and flag that sibling ports'
+                 * ARP-resolve flows must be regenerated.  op->peer is kept so
+                 * the SB port-binding sync node can clear the peer LRP's
+                 * options:peer. */
+                ls_router_port_unwire_peer(op);
+                router_ports_changed = true;
+            }
             add_op_to_northd_tracked_ports(&trk_lsps->deleted, op);
             hmap_remove(&nd->ls_ports, &op->key_node);
             hmap_remove(&od->ports, &op->dp_node);
@@ -5218,6 +5471,19 @@ ls_handle_lsp_changes(struct ovsdb_idl_txn 
*ovnsb_idl_txn,
         }
     }
 
+    /* Adding or removing a router port changes od->router_ports, on which the
+     * ARP-resolve flows of the sibling switch ports depend (see
+     * build_arp_resolve_flows_for_lsp()).  Re-track the existing sibling ports
+     * so the lflow engine regenerates their flows.  Newly created ports (in
+     * trk_lsps->created) already get their flows generated. */
+    if (router_ports_changed) {
+        HMAP_FOR_EACH (op, dp_node, &od->ports) {
+            if (!hmapx_contains(&trk_lsps->created, op)) {
+                add_op_to_northd_tracked_ports(&trk_lsps->updated, op);
+            }
+        }
+    }
+
     /* Update old virtual ports that have newly created or newly deleted
      * VIF as parent port. This code handles cases where the virtual port was
      * created before the parent port or when the parent port was recreated.
@@ -20857,16 +21123,35 @@ lflow_handle_northd_port_changes(struct ovsdb_idl_txn 
*ovnsb_txn,
     struct hmapx_node *hmapx_node;
     struct ovn_port *op;
 
+    /* Logical switches whose set of router ports changed.  The per-switch
+     * ls_stateful lflow_ref contains skip-conntrack flows generated for each
+     * router port (see build_ls_stateful_rec_pre_lb()/_pre_acls()), so it must
+     * be regenerated when a router port is created or deleted. */
+    struct hmapx ls_stateful_regen = HMAPX_INITIALIZER(&ls_stateful_regen);
+
     HMAPX_FOR_EACH (hmapx_node, &trk_lsps->deleted) {
         op = hmapx_node->data;
         /* Make sure 'op' is an lsp and not lrp. */
         ovs_assert(op->nbsp);
+        if (lsp_is_router(op->nbsp) && op->peer) {
+            hmapx_add(&ls_stateful_regen, op->od);
+        }
         bool handled = lflow_ref_resync_flows(
             op->lflow_ref, lflows, ovnsb_txn, lflow_input->dps,
             lflow_input->ovn_internal_version_changed,
             lflow_input->sbrec_logical_flow_table,
             lflow_input->sbrec_logical_dp_group_table);
+        if (handled) {
+            /* Router ports also own flows on their stateful_lflow_ref (see
+             * build_lbnat_lflows_iterate_by_lsp()); clear those too. */
+            handled = lflow_ref_resync_flows(
+                op->stateful_lflow_ref, lflows, ovnsb_txn, lflow_input->dps,
+                lflow_input->ovn_internal_version_changed,
+                lflow_input->sbrec_logical_flow_table,
+                lflow_input->sbrec_logical_dp_group_table);
+        }
         if (!handled) {
+            hmapx_destroy(&ls_stateful_regen);
             return false;
         }
         /* No need to update SB multicast groups, thanks to weak
@@ -20914,6 +21199,7 @@ lflow_handle_northd_port_changes(struct ovsdb_idl_txn 
*ovnsb_txn,
         ds_destroy(&actions);
 
         if (!handled) {
+            hmapx_destroy(&ls_stateful_regen);
             return false;
         }
     }
@@ -20922,6 +21208,9 @@ lflow_handle_northd_port_changes(struct ovsdb_idl_txn 
*ovnsb_txn,
         op = hmapx_node->data;
         /* Make sure 'op' is an lsp and not lrp. */
         ovs_assert(op->nbsp);
+        if (lsp_is_router(op->nbsp) && op->peer) {
+            hmapx_add(&ls_stateful_regen, op->od);
+        }
 
         struct ds match = DS_EMPTY_INITIALIZER;
         struct ds actions = DS_EMPTY_INITIALIZER;
@@ -20955,11 +21244,46 @@ lflow_handle_northd_port_changes(struct ovsdb_idl_txn 
*ovnsb_txn,
         ds_destroy(&actions);
 
         if (!handled) {
+            hmapx_destroy(&ls_stateful_regen);
             return false;
         }
     }
 
-    return true;
+    /* Regenerate the ls_stateful lflows of switches whose set of router ports
+     * changed (the skip-conntrack flows for router ports are owned by the
+     * per-switch ls_stateful lflow_ref, not by the port). */
+    bool handled = true;
+    HMAPX_FOR_EACH (hmapx_node, &ls_stateful_regen) {
+        struct ovn_datapath *od = hmapx_node->data;
+        const struct ls_stateful_record *ls_stateful_rec =
+            ls_stateful_table_find(lflow_input->ls_stateful_table, od->nbs);
+        if (!ls_stateful_rec) {
+            continue;
+        }
+
+        lflow_ref_unlink_lflows(ls_stateful_rec->lflow_ref);
+        build_ls_stateful_flows(ls_stateful_rec, od,
+                                lflow_input->ls_port_groups,
+                                lflow_input->meter_groups,
+                                lflow_input->sampling_apps,
+                                lflow_input->features,
+                                lflows,
+                                lflow_input->sbrec_acl_id_table);
+        build_network_function(od, lflows, lflow_input->ls_port_groups,
+                               ls_stateful_rec->lflow_ref);
+        handled = lflow_ref_sync_lflows(
+            ls_stateful_rec->lflow_ref, lflows, ovnsb_txn,
+            lflow_input->dps,
+            lflow_input->ovn_internal_version_changed,
+            lflow_input->sbrec_logical_flow_table,
+            lflow_input->sbrec_logical_dp_group_table);
+        if (!handled) {
+            break;
+        }
+    }
+
+    hmapx_destroy(&ls_stateful_regen);
+    return handled;
 }
 
 bool
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 558ce10d8..13df2b712 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -12285,6 +12285,132 @@ CHECK_NO_CHANGE_AFTER_RECOMPUTE
 OVN_CLEANUP_NORTHD
 AT_CLEANUP
 
+AT_SETUP([Router port incremental processing])
+AT_KEYWORDS([incremental processing])
+ovn_start
+
+check ovn-nbctl ls-add sw0
+check ovn-nbctl lr-add lr0
+check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24
+check ovn-nbctl --wait=sb lsp-add sw0 vif0 \
+    -- lsp-set-addresses vif0 "00:00:00:00:00:01 10.0.0.4"
+
+# Connecting the switch to the pre-existing LRP should be incrementally
+# processed (the Logical_Router row is untouched, so it is only a new LSP).
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb lsp-add-router-port sw0 sw0-lr0 lr0-sw0
+check_engine_compute northd incremental
+check_engine_compute lflow incremental
+
+# The peer relationship is reflected on both SB port bindings.
+AT_CHECK([ovn-sbctl get port_binding sw0-lr0 type], [0], [dnl
+patch
+])
+AT_CHECK([ovn-sbctl get port_binding sw0-lr0 options:peer], [0], [dnl
+lr0-sw0
+])
+AT_CHECK([ovn-sbctl get port_binding lr0-sw0 options:peer], [0], [dnl
+sw0-lr0
+])
+
+# The router port L2 lookup flow is present on the switch.
+AT_CHECK([ovn-sbctl dump-flows sw0 | grep ls_in_l2_lkup | grep sw0-lr0 \
+    | grep -c 'eth.dst == 00:00:00:00:ff:01'], [0], [1
+])
+
+# The sibling VIF's ARP-resolve flow was regenerated on the router pipeline
+# (it depends on sw0's set of router ports).
+AT_CHECK([ovn-sbctl dump-flows lr0 | grep lr_in_arp_resolve \
+    | grep 'reg0 == 10.0.0.4' | grep -c 'eth.dst = 00:00:00:00:00:01'], [0], [1
+])
+
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+# Disconnecting the switch from the router should be incrementally processed.
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb lsp-del sw0-lr0
+check_engine_compute northd incremental
+check_engine_compute lflow incremental
+
+# The peer LRP port binding no longer references the deleted switch port.
+AT_CHECK([ovn-sbctl get port_binding lr0-sw0 options:peer], [1], [], [dnl
+ovn-sbctl: no key "peer" in Port_Binding record "lr0-sw0" column options
+])
+
+# The router port L2 lookup flow and the VIF's ARP-resolve flow are gone.
+AT_CHECK([ovn-sbctl dump-flows sw0 | grep ls_in_l2_lkup | grep -c sw0-lr0],
+    [1], [0
+])
+AT_CHECK([ovn-sbctl dump-flows lr0 | grep lr_in_arp_resolve \
+    | grep -c 'reg0 == 10.0.0.4'], [1], [0
+])
+
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+OVN_CLEANUP_NORTHD
+AT_CLEANUP
+
+AT_SETUP([Router port incremental processing fallback with distributed 
gateway])
+AT_KEYWORDS([incremental processing])
+ovn_start
+
+check ovn-sbctl chassis-add gw1 geneve 127.0.0.1
+
+check ovn-nbctl ls-add sw0
+check ovn-nbctl lr-add lr0
+
+# Distributed gateway port on lr0.
+check ovn-nbctl ls-add public
+check ovn-nbctl lrp-add lr0 lr0-public 00:00:20:20:12:13 172.168.0.100/24
+check ovn-nbctl --wait=sb lsp-add-router-port public public-lr0 lr0-public
+check ovn-nbctl lsp-add-localnet-port public ln-public public
+check ovn-nbctl --wait=sb lrp-set-gateway-chassis lr0-public gw1 20
+
+check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24
+
+# Connecting a switch to a router that has a distributed gateway port must fall
+# back to recompute (l3gateway/chassisredirect SB types and GARP nat_addresses
+# depend on state outside the switch port's lflow_ref).
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb lsp-add-router-port sw0 sw0-lr0 lr0-sw0
+check_engine_compute northd recompute
+
+# Deleting it must also fall back to recompute.
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb lsp-del sw0-lr0
+check_engine_compute northd recompute
+
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+OVN_CLEANUP_NORTHD
+AT_CLEANUP
+
+AT_SETUP([Router port incremental processing fallback with dynamic routing])
+AT_KEYWORDS([incremental processing])
+ovn_start
+
+check ovn-nbctl ls-add sw0
+check ovn-nbctl lr-add lr0 \
+    -- set Logical_Router lr0 options:dynamic-routing=true
+check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24
+
+# Connecting a switch to a dynamic-routing router must fall back to recompute:
+# advertised/routable flows depend on state outside the switch port's
+# lflow_ref.
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb lsp-add-router-port sw0 sw0-lr0 lr0-sw0
+check_engine_compute northd recompute
+
+# Deleting it must also fall back to recompute.
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb lsp-del sw0-lr0
+check_engine_compute northd recompute
+
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+OVN_CLEANUP_NORTHD
+AT_CLEANUP
+
 OVN_FOR_EACH_NORTHD_NO_HV([
 AT_SETUP([SB Port binding incremental processing])
 ovn_start
@@ -12327,17 +12453,16 @@ check ovn-nbctl --wait=sb sync
 check_recompute_counter 0 0
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
-# Test lsp of type router
+# Test lsp of type router.  This port has no "router-port" option, so it has no
+# peer LRP and is inert; both the NB logical switch port change and the
+# subsequent "up" change set by ovn-northd are now incrementally processed.
 check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb lsp-add ls0 rp -- lsp-set-type rp router
-
-# northd engine recomputes twice. Both the times for handling NB logical 
switch port
-# changes and not because of SB port binding changes.  This is because 
ovn-northd
-# sets the "up" to true.
-check_recompute_counter 2 2
+check_recompute_counter 0 0
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
-# Set some options to 'rp'.  northd should only recompute once.
+# Set some options to 'rp'.  Updating a router port other than its "up" column
+# falls back to recompute (re-wiring the peer on reinit is not supported).
 check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb lsp-set-options rp foo=bar
 check_recompute_counter 1 1
-- 
2.43.0


-- 




_'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