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. This also
covers the inter-router-port ARP-resolve flows that sibling router
ports generate for each other.
- The peer LRP's SB Port_Binding options:peer is (re)synced.
- The per-switch ls_stateful lflow_ref is regenerated in the lflow
port-change handler. It owns the skip-conntrack flows generated for
each router port and, more generally, every ACL-derived flow of the
switch.
- The router port's stateful_lflow_ref is cleared on deletion (only
router ports populate it via build_lbnat_lflows_iterate_by_lsp()).
A logical switch may hold any number of router ports, as long as all of
them peer with LRPs of the same logical router: a switch connecting two
distinct routers merges them into a single logical router group (see
build_lrouter_groups()), which is computed on a full recompute only.
Keeping a switch within a single router also means the peer-router checks
cover every router port of the switch.
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, dynamic routing, IPv6 RA, arp_proxy, vtep
ports, mcast relay, router ports of the switch peering with more than one
logical router, or an LRP that is already peered with another switch
port.
Static routes on the peer router are deliberately not on that list.
Unlike a logical router port, a switch port does not change the set of
LRPs a route resolves its output port against (see
find_static_route_outport()), and nothing outside the routes engine
consumes static routes, so routes_northd_change_handler() reports them
unchanged when only switch ports moved. Blocking on them would be
costly in practice: a CMS usually gives a router its default route
before attaching the switches, which would send every subsequent
lsp-add-router-port through a full recompute.
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), incremental
create/delete of a second router port to the same logical router,
create/delete on a switch with stateful, stateless, logged and port group
ACLs, create/delete on a router carrying static routes (resolved through
the peered LRP, through another LRP, through an explicit output port, and
a discard route), and the recompute fallbacks for the multiple-router,
distributed-gateway and dynamic-routing cases.
Assisted-by: Claude Opus 4.8, Claude Opus 5, ClaudeCode
Signed-off-by: Lucas Vargas Dias <[email protected]>
---
northd/en-ls-stateful.c | 7 +
northd/northd.c | 378 +++++++++++++++++++++++++++++++++++++++-
tests/ovn-northd.at | 356 ++++++++++++++++++++++++++++++++++++-
3 files changed, 726 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 f42b7fd69..63012c625 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -4348,13 +4348,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);
+ }
}
}
@@ -4706,10 +4728,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;
}
@@ -4796,6 +4818,207 @@ 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( ->options, "arp_proxy")) {
+ return true;
+ }
+
+ /* Switch-level aggregate dependencies that live outside the port's own
+ * lflow_ref and are not regenerated here: the LB install set (built over
+ * od->ls_peers) and the vtep hairpin flows owned by od->datapath_lflows.
+ *
+ * Note that ACLs need no such check: every ACL-derived flow is owned by
+ * the per-switch ls_stateful lflow_ref, which is regenerated as a whole in
+ * lflow_handle_northd_port_changes(). */
+ if (od->nbs->n_load_balancer ||
+ od->nbs->n_load_balancer_group || od->has_vtep_lports) {
+ return true;
+ }
+
+ const char *peer_name = smap_get( ->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;
+ }
+
+ /* The LRP is already peered with another switch port. A recompute would
+ * pick one of the two non-deterministically; let it deal with such a
+ * misconfiguration. */
+ if (!is_delete && peer->peer) {
+ return true;
+ }
+
+ /* A switch may hold several router ports, but only if all of them peer
+ * with LRPs of the same logical router: a switch connecting two distinct
+ * routers merges them into a single logical router group (see
+ * build_lrouter_groups()), which is computed on a full recompute only.
+ * Staying within one router also means the peer-router checks below cover
+ * every router port of the switch.
+ *
+ * The flows that the router ports generate for each other (the
+ * inter-router-port ARP-resolve flows of
+ * build_arp_resolve_flows_for_lsp()) are owned by the sibling ports' own
+ * lflow_refs; ls_handle_lsp_changes() re-tracks those siblings so that the
+ * lflow engine regenerates them. */
+ struct ovn_port *rp;
+ VECTOR_FOR_EACH (&od->router_ports, rp) {
+ if (rp->nbsp == nbsp) {
+ /* The port being deleted, still in od->router_ports. */
+ continue;
+ }
+ if (!rp->peer || rp->peer->od != peer->od) {
+ 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, LBs and dynamic routing on the peer router pull in
+ * stateful/routable/advertised-route dependencies not tracked here.
+ *
+ * Static routes are not among them: they resolve their output port
+ * against the router's own LRPs (see find_static_route_outport()), a set
+ * a switch port does not change, and nothing outside the routes engine
+ * consumes them. routes_northd_change_handler() accordingly reports them
+ * unchanged when only switch ports moved. */
+ const struct nbrec_logical_router *nbr = peer->od->nbr;
+ if (nbr->n_nat || 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)
{
@@ -4863,7 +5086,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,
@@ -4872,13 +5095,33 @@ 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)) {
+ if (lsp_is_router(nbsp)) {
+ /* Undo the peer wiring so that 'op', which is about to be freed,
+ * is not left behind in od->router_ports / peer->peer, nor its
+ * datapath in peer->od->ls_peers. */
+ ls_router_port_unwire_peer(op);
+ }
ovn_port_destroy(ls_ports, op);
return NULL;
}
+ if (lsp_is_router(nbsp) && op->peer) {
+ ls_router_port_add_peer_networks(op);
+ }
+
return op;
}
@@ -5073,6 +5316,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;
@@ -5100,9 +5344,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);
@@ -5110,9 +5362,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)) {
@@ -5191,6 +5464,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. */
@@ -5204,6 +5485,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);
@@ -5235,6 +5524,21 @@ 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, and deleted ports
+ * were already removed from od->ports above, so they are not walked
+ * here. */
+ 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.
@@ -21225,16 +21529,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
@@ -21282,6 +21605,7 @@ lflow_handle_northd_port_changes(struct ovsdb_idl_txn
*ovnsb_txn,
ds_destroy(&actions);
if (!handled) {
+ hmapx_destroy(&ls_stateful_regen);
return false;
}
}
@@ -21290,6 +21614,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;
@@ -21323,11 +21650,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, lflows);
+ 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 748d8a438..a57858a55 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -12377,6 +12377,349 @@ 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 with multiple router ports])
+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-a 00:00:00:00:ff:01 10.0.0.1/24
+check ovn-nbctl lrp-add lr0 lr0-sw0-b 00:00:00:00:ff:02 20.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"
+
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb lsp-add-router-port sw0 sw0-lr0-a lr0-sw0-a
+check_engine_compute northd incremental
+check_engine_compute lflow incremental
+
+# A second router port to the same logical router is also incrementally
+# processed.
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb lsp-add-router-port sw0 sw0-lr0-b lr0-sw0-b
+check_engine_compute northd incremental
+check_engine_compute lflow incremental
+
+AT_CHECK([ovn-sbctl get port_binding lr0-sw0-a options:peer], [0], [dnl
+sw0-lr0-a
+])
+AT_CHECK([ovn-sbctl get port_binding lr0-sw0-b options:peer], [0], [dnl
+sw0-lr0-b
+])
+
+# The inter-router-port ARP-resolve flows are owned by the sibling switch
+# ports' lflow_refs, so adding the second router port must have regenerated
+# the first one's flows too.
+AT_CHECK([ovn-sbctl dump-flows lr0 | grep lr_in_arp_resolve \
+ | grep 'outport == "lr0-sw0-a"' | grep -c 'reg0 == 20.0.0.1'], [0], [1
+])
+AT_CHECK([ovn-sbctl dump-flows lr0 | grep lr_in_arp_resolve \
+ | grep 'outport == "lr0-sw0-b"' | grep -c 'reg0 == 10.0.0.1'], [0], [1
+])
+
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+# Removing one of them is incrementally processed as well.
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb lsp-del sw0-lr0-b
+check_engine_compute northd incremental
+check_engine_compute lflow incremental
+
+AT_CHECK([ovn-sbctl dump-flows lr0 | grep lr_in_arp_resolve \
+ | grep -c 'reg0 == 20.0.0.1'], [1], [0
+])
+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
+
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb lsp-del sw0-lr0-a
+check_engine_compute northd incremental
+check_engine_compute lflow incremental
+
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+OVN_CLEANUP_NORTHD
+AT_CLEANUP
+
+AT_SETUP([Router port incremental processing with static routes])
+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 lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 20.0.0.1/24
+check ovn-nbctl lsp-add sw0 vif0 \
+ -- lsp-set-addresses vif0 "00:00:00:00:00:01 10.0.0.2"
+
+# Routes resolving through the LRP the switch is about to peer with, through
+# another LRP, through an explicit output port, and a discard route.
+check ovn-nbctl lr-route-add lr0 8.8.8.8 10.0.0.2
+check ovn-nbctl lr-route-add lr0 9.9.9.9 20.0.0.2
+check ovn-nbctl lr-route-add lr0 5.5.5.5 10.0.0.3 lr0-sw0
+check ovn-nbctl --wait=sb lr-route-add lr0 7.7.7.7 discard
+
+# Connecting the switch to a router that carries static routes is
+# incrementally processed: a switch port does not take part in resolving the
+# output port of a route.
+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 routes through the newly peered LRP still resolve ...
+AT_CHECK([ovn-sbctl dump-flows lr0 | grep lr_in_ip_routing | \
+ grep -c 'ip4.dst == 8.8.8.8/32'], [0], [1
+])
+AT_CHECK([ovn-sbctl dump-flows lr0 | grep lr_in_ip_routing | \
+ grep -c 'ip4.dst == 5.5.5.5/32'], [0], [1
+])
+# ... and the next hop's ARP-resolve flow was regenerated from the sibling VIF.
+AT_CHECK([ovn-sbctl dump-flows lr0 | grep lr_in_arp_resolve | \
+ grep 'reg0 == 10.0.0.2' | grep -c 'eth.dst = 00:00:00:00:00:01'], [0], [1
+])
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+# Adding a static route to the now connected router stays incremental too.
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb lr-route-add lr0 4.4.4.4 10.0.0.5
+check_engine_compute northd incremental
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+# Disconnecting the switch is incremental as well.
+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
+AT_CHECK([ovn-sbctl dump-flows lr0 | grep lr_in_ip_routing | \
+ grep -c 'ip4.dst == 8.8.8.8/32'], [0], [1
+])
+AT_CHECK([ovn-sbctl dump-flows lr0 | grep lr_in_arp_resolve | \
+ grep -c 'reg0 == 10.0.0.2'], [1], [0
+])
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+OVN_CLEANUP_NORTHD
+AT_CLEANUP
+
+AT_SETUP([Router port incremental processing fallback with multiple routers])
+AT_KEYWORDS([incremental processing])
+ovn_start
+
+check ovn-nbctl ls-add sw0
+check ovn-nbctl lr-add lr0
+check ovn-nbctl lr-add lr1
+check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24
+check ovn-nbctl lrp-add lr1 lr1-sw0 00:00:00:00:ff:02 20.0.0.1/24
+
+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
+
+# Connecting the same switch to a second logical router must fall back to
+# recompute: the two routers are merged into a single logical router group,
+# which is only computed by a full recompute.
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb lsp-add-router-port sw0 sw0-lr1 lr1-sw0
+check_engine_compute northd recompute
+
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+# Deleting one of them 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-lr1
+check_engine_compute northd recompute
+
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+OVN_CLEANUP_NORTHD
+AT_CLEANUP
+
+AT_SETUP([Router port incremental processing with ACLs])
+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 lsp-add sw0 vif0 \
+ -- lsp-set-addresses vif0 "00:00:00:00:00:01 10.0.0.4"
+
+# Stateful, stateless and logged ACLs directly on the switch.
+check ovn-nbctl acl-add sw0 from-lport 1002 'ip4 && tcp' allow-related
+check ovn-nbctl acl-add sw0 to-lport 1002 'ip4 && tcp' allow-related
+check ovn-nbctl acl-add sw0 from-lport 1001 'ip4 && icmp4' allow-stateless
+check ovn-nbctl --log --severity=alert acl-add sw0 to-lport 1004 'ip4 && udp'
drop
+
+# ... and a stateful ACL coming from a port group.
+check ovn-nbctl pg-add pg0 vif0
+check ovn-nbctl acl-add pg0 from-lport 1003 'ip4 && udp' allow-related
+check ovn-nbctl --wait=sb sync
+
+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 skip-conntrack flows for the router port (owned by the ls_stateful
+# lflow_ref) were generated.
+AT_CHECK([ovn-sbctl dump-flows sw0 | grep ls_in_pre_acl | \
+ grep -c 'inport == "sw0-lr0"'], [0], [1
+])
+AT_CHECK([ovn-sbctl dump-flows sw0 | grep ls_out_pre_acl | \
+ grep -c 'outport == "sw0-lr0"'], [0], [1
+])
+AT_CHECK([ovn-sbctl dump-flows sw0 | grep ls_in_pre_lb | \
+ grep -c 'inport == "sw0-lr0"'], [0], [1
+])
+AT_CHECK([ovn-sbctl dump-flows sw0 | grep ls_out_pre_lb | \
+ grep -c 'outport == "sw0-lr0"'], [0], [1
+])
+
+CHECK_NO_CHANGE_AFTER_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 incremental
+check_engine_compute lflow incremental
+
+AT_CHECK([ovn-sbctl dump-flows sw0 | grep -c 'sw0-lr0'], [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
@@ -12419,17 +12762,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