Hi Dmitrii, I didn't spot anything worth commenting on in this patch. I did mention on the cover letter my concerns about the deep nesting of loops in this patch, but from a functionality perspective it seems good. I'll wait to ack patches until I'm ready to ack the whole set.
On Thu, Jul 2, 2026 at 10:55 AM Dmitrii Shcherbakov via dev <[email protected]> wrote: > > Replace the single Advertised_Route per VIP (tracked_port = peer LRP, > a chassis-unbound patch port) with one row per (VIP IP, backend LSP). > The backend LSP comes from ip_port_mappings, so it is > chassis-resident wherever the backend is bound. > > Multiple LB listeners that share the same VIP IP and the same backend > LSP (e.g. two Services on different ports fronting the same pod) are > deduplicated by the existing SB unique index on (datapath, > logical_port, ip_prefix, tracked_port), so only one Advertised_Route > row is emitted per (VIP IP, backend LSP) pair. > > For LBs configured with ip_port_mappings, each backend LSP that > appears in the VIP backend list produces one Advertised_Route row > with tracked_port set to that LSP. Backends without ip_port_mappings > are skipped. When no backend has a mapping, one Advertised_Route > covers the VIP with tracked_port set to the peer LRP (the pre-patch > behavior). > > The per-backend split also means > dynamic-routing-redistribute-local-only now correctly restricts > kernel-route installation to chassis that host at least one backend > for the VIP. > > Each LB-derived row is tagged with external_ids:source=lb so the > controller can distinguish it from NAT, connected, or static routes > that may share the same ip_prefix. > > No SB schema changes are needed. > > Also document external_ids:source under Advertised_Route in > ovn-sb.xml, and clarify the "logical-switch-port" value in the > Service_Monitor.type column description. > > Signed-off-by: Dmitrii Shcherbakov <[email protected]> > --- > northd/en-advertised-route-sync.c | 154 +++++++++-- > ovn-sb.xml | 19 +- > tests/ovn-northd.at | 440 +++++++++++++++++++++++++++++- > 3 files changed, 590 insertions(+), 23 deletions(-) > > diff --git a/northd/en-advertised-route-sync.c > b/northd/en-advertised-route-sync.c > index 8e02fdec1..718e49bc9 100644 > --- a/northd/en-advertised-route-sync.c > +++ b/northd/en-advertised-route-sync.c > @@ -23,7 +23,11 @@ > #include "en-lr-stateful.h" > #include "lb.h" > #include "openvswitch/hmap.h" > +#include "openvswitch/vlog.h" > #include "ovn-util.h" > +#include "util.h" > + > +VLOG_DEFINE_THIS_MODULE(en_advertised_route_sync); > > struct ar_entry { > struct hmap_node hmap_node; > @@ -106,6 +110,8 @@ ar_entry_find(struct hmap *route_map, > tracked_port != route_e->tracked_port->sb) { > continue; > } > + } else if (route_e->tracked_port) { > + continue; > } > > return route_e; > @@ -205,6 +211,13 @@ add_redistribute_parsed_route(struct hmap > *parsed_routes_out, > /* Parse the prefix (the VIP/FIP). */ > struct in6_addr prefix; > if (!ip46_parse(ip_address, &prefix)) { > + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); > + VLOG_WARN_RL(&rl, "Failed to parse IP address '%s' for %s " > + "redistribute forwarding route on datapath %s", > + ip_address, > + source == ROUTE_SOURCE_LB ? "LB" : "NAT", > + advertising_od->nbr ? advertising_od->nbr->name > + : "<unknown>"); > return; > } > bool is_v6 = !IN6_IS_ADDR_V4MAPPED(&prefix); > @@ -218,6 +231,13 @@ add_redistribute_parsed_route(struct hmap > *parsed_routes_out, > nexthop_s = tracked_port->lrp_networks.ipv6_addrs[0].addr_s; > } > if (!nexthop_s) { > + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); > + VLOG_WARN_RL(&rl, "No %s address on tracked port %s for %s " > + "redistribute forwarding route (prefix %s)", > + is_v6 ? "IPv6" : "IPv4", > + tracked_port->key, > + source == ROUTE_SOURCE_LB ? "LB" : "NAT", > + ip_address); > return; > } > > @@ -258,7 +278,7 @@ add_redistribute_parsed_route(struct hmap > *parsed_routes_out, > * parsed_route on advertising_op->od for each NAT external IP whose > * nexthop is available from tracked_port (i.e. a peer LRP). This is the > * connected-neighbour redistribution case where the advertising LR > - * needs to forward to the peer's LR.*/ > + * needs to forward to the peer's LR. */ > static void > build_nat_route_for_port(const struct ovn_port *advertising_op, > const struct lr_nat_record *lr_nat, > @@ -399,6 +419,90 @@ build_nat_connected_routes( > } > } > > +/* For each LB attached to peer_lr_nbr, emit one Advertised_Route per > + * (VIP IP, backend LSP) pair, plus one forwarding parsed_route per VIP. > + * Multiple listeners sharing the same VIP IP and backend LSP are > + * deduplicated by ar_entry_find on the (datapath, logical_port, > + * ip_prefix, tracked_port) key. When no backend has an ip_port_mappings > + * entry, one Advertised_Route covers the VIP with fallback_tracked_port > + * in place of a per-backend LSP. The forwarding route is emitted once > + * per VIP regardless of backend count: the data-plane forwarding > + * decision is independent of which backend ends up serving the flow. */ > +static void > +build_lb_lr_routes(const struct ovn_port *advertising_op, > + const struct ovn_port *fallback_tracked_port, > + const struct nbrec_logical_router *peer_lr_nbr, > + const struct hmap *lb_datapaths_map, > + const struct hmap *ls_ports, > + struct hmap *routes, > + struct hmap *parsed_routes_out) > +{ > + const struct ovn_datapath *advertising_od = advertising_op->od; > + > + if (!peer_lr_nbr) { > + return; > + } > + > + for (size_t i = 0; i < peer_lr_nbr->n_load_balancer; i++) { > + const struct nbrec_load_balancer *nbrec_lb = > + peer_lr_nbr->load_balancer[i]; > + if (!smap_get_bool(&nbrec_lb->options, > + "dynamic-routing-advertise", true)) { > + continue; > + } > + const struct uuid *lb_uuid = &nbrec_lb->header_.uuid; > + const struct ovn_lb_datapaths *lb_dps = > + ovn_lb_datapaths_find(lb_datapaths_map, lb_uuid); > + if (!lb_dps) { > + continue; > + } > + const struct ovn_northd_lb *lb = lb_dps->lb; > + for (size_t v = 0; v < lb->n_vips; v++) { > + const struct ovn_lb_vip *vip = &lb->vips[v]; > + const struct ovn_northd_lb_vip *vip_nb = &lb->vips_nb[v]; > + > + if (parsed_routes_out) { > + add_redistribute_parsed_route( > + parsed_routes_out, advertising_od, advertising_op, > + fallback_tracked_port, vip->vip_str, ROUTE_SOURCE_LB, > + &nbrec_lb->header_); > + } > + > + bool emitted_any = false; > + for (size_t b = 0; b < vip_nb->n_backends; b++) { > + const char *lsp_name = vip_nb->backends_nb[b].logical_port; > + if (!lsp_name) { > + continue; > + } > + const struct ovn_port *backend_op = > + ovn_port_find(ls_ports, lsp_name); > + if (!backend_op) { > + continue; > + } > + /* The SB unique index on (datapath, logical_port, > + * ip_prefix, tracked_port) means only one route per > + * (VIP IP, backend LSP) pair can exist. Skip if an > + * entry was already added by a previous listener > + * sharing the same VIP IP and backend. */ > + if (ar_entry_find(routes, advertising_od->sdp->sb_dp, > + advertising_op->sb, vip->vip_str, > + backend_op->sb)) { > + emitted_any = true; > + continue; > + } > + ar_entry_add(routes, advertising_od, advertising_op, > + vip->vip_str, backend_op, ROUTE_SOURCE_LB); > + emitted_any = true; > + } > + if (!emitted_any) { > + ar_entry_add(routes, advertising_od, advertising_op, > + vip->vip_str, fallback_tracked_port, > + ROUTE_SOURCE_LB); > + } > + } > + } > +} > + > /* Own-LR entry point used by the own-LR (gateway-router/DGP) path, > * which doesn't currently route through a peer LR's LBs. Emits one > * Advertised_Route per IP in lb_ips with tracked_port as-is. > @@ -445,7 +549,8 @@ build_lb_route_for_port(const struct ovn_port > *advertising_op, > * LB VIPs too.*/ > static void > build_lb_connected_routes(const struct ovn_datapath *od, > - const struct lr_stateful_table *lr_stateful_table, > + const struct hmap *lb_datapaths_map, > + const struct hmap *ls_ports, > struct dynamic_routes_data *data) > { > const struct ovn_port *op; > @@ -463,13 +568,11 @@ build_lb_connected_routes(const struct ovn_datapath *od, > /* Track the peer datapath for any changes. */ > dynamic_routes_track_od(data, peer_od); > > - const struct lr_stateful_record *lr_stateful_rec; > /* This is directly connected LR peer. */ > if (peer_od->nbr) { > - lr_stateful_rec = lr_stateful_table_find_by_uuid( > - lr_stateful_table, peer_od->key); > - build_lb_route_for_port(op, op->peer, lr_stateful_rec->lb_ips, > - &data->routes, &data->parsed_routes); > + build_lb_lr_routes(op, op->peer, peer_od->nbr, > + lb_datapaths_map, ls_ports, > + &data->routes, &data->parsed_routes); > continue; > } > > @@ -482,11 +585,9 @@ build_lb_connected_routes(const struct ovn_datapath *od, > * function.*/ > continue; > } > - lr_stateful_rec = lr_stateful_table_find_by_uuid( > - lr_stateful_table, rp->peer->od->key); > - > - build_lb_route_for_port(op, rp->peer, lr_stateful_rec->lb_ips, > - &data->routes, &data->parsed_routes); > + build_lb_lr_routes(op, rp->peer, rp->peer->od->nbr, > + lb_datapaths_map, ls_ports, > + &data->routes, &data->parsed_routes); > /* Track the LR datapath on the other side of LS > * for any changes. */ > dynamic_routes_track_od(data, rp->peer->od); > @@ -780,7 +881,8 @@ en_dynamic_routes_run(struct engine_node *node, void > *data) > > build_lb_routes(od, lr_stateful_rec->lb_ips, > &dynamic_routes_data->routes); > - build_lb_connected_routes(od, &lr_stateful_data->table, > + build_lb_connected_routes(od, &northd_data->lb_datapaths_map, > + &northd_data->ls_ports, > dynamic_routes_data); > } > > @@ -989,9 +1091,6 @@ advertised_route_table_sync( > if (ar_entry_find(&sync_routes, route_e->od->sdp->sb_dp, > route_e->op->sb, > route_e->ip_prefix, tracked_pb)) { > - /* We could already have advertised route entry for LRP IP that > - * corresponds to "snat" when "connected-as-host" is combined > - * with "nat". Skip it. */ > continue; > } > ar_entry_add(&sync_routes, route_e->od, route_e->op, > @@ -1009,10 +1108,19 @@ advertised_route_table_sync( > sbrec_advertised_route_delete(sb_route); > continue; > } > - > - if (route_e->tracked_port && !sb_route->tracked_port) { > - sbrec_advertised_route_set_tracked_port( > - sb_route, route_e->tracked_port->sb); > + /* Reconcile external_ids:source so the controller can reliably > + * identify LB-derived rows. This matters after upgrades (rows > + * created before this marker existed) and when a non-LB route > + * reuses a key previously held by an LB-derived row. */ > + const char *cur_source = smap_get(&sb_route->external_ids, "source"); > + if (route_e->source == ROUTE_SOURCE_LB) { > + if (!cur_source || strcmp(cur_source, "lb")) { > + sbrec_advertised_route_update_external_ids_setkey( > + sb_route, "source", "lb"); > + } > + } else if (cur_source) { > + sbrec_advertised_route_update_external_ids_delkey( > + sb_route, "source"); > } > hmap_remove(&sync_routes, &route_e->hmap_node); > ar_entry_free(route_e); > @@ -1028,6 +1136,12 @@ advertised_route_table_sync( > sbrec_advertised_route_set_tracked_port(sr, > > route_e->tracked_port->sb); > } > + if (route_e->source == ROUTE_SOURCE_LB) { > + struct smap new_ids = SMAP_INITIALIZER(&new_ids); > + smap_add(&new_ids, "source", "lb"); > + sbrec_advertised_route_set_external_ids(sr, &new_ids); > + smap_destroy(&new_ids); > + } > ar_entry_free(route_e); > } > > diff --git a/ovn-sb.xml b/ovn-sb.xml > index 0d3ec6c2c..e70b83a6e 100644 > --- a/ovn-sb.xml > +++ b/ovn-sb.xml > @@ -5083,8 +5083,8 @@ tcp.flags = RST; > </p> > > <column name="type"> > - The type of the service. Supported values are "load-balancer" and > - "network-function". > + The type of the service. Supported values are "load-balancer", > + "network-function", and "logical-switch-port". > </column> > > <column name="ip"> > @@ -5463,6 +5463,21 @@ tcp.flags = RST; > > <column name="external_ids"> > See <em>External IDs</em> at the beginning of this document. > + > + <p> > + <code>ovn-northd</code> sets the following key on routes > + derived from Load_Balancer VIPs: > + </p> > + > + <dl> > + <dt><code>source</code></dt> > + <dd> > + Set to <code>lb</code> by <code>ovn-northd</code> on routes > + derived from Load_Balancer VIPs. Absent on all other routes. > + <code>ovn-controller</code> uses this key to identify routes > + eligible for Service_Monitor-based gating. > + </dd> > + </dl> > </column> > </table> > > diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at > index da67d54a7..5c921c8d7 100644 > --- a/tests/ovn-northd.at > +++ b/tests/ovn-northd.at > @@ -18330,6 +18330,444 @@ OVN_CLEANUP_NORTHD > AT_CLEANUP > ]) > > +OVN_FOR_EACH_NORTHD_NO_HV([ > +AT_SETUP([dynamic-routing - LB redistribute uses backend LSP as > tracked_port]) > +AT_KEYWORDS([dynamic-routing]) > +ovn_start > + > +# When the LB row's ip_port_mappings populate per-backend LSPs, northd > +# emits one Advertised_Route per (VIP, backend LSP) instead of a single > +# row using the peer LR's gateway LRP. That moves the chassis-locality > +# decision from a chassis-unbound patch port (the peer LRP) to the > +# actual backend port - the controller can then per-chassis install > +# the kernel route via dynamic-routing-redistribute-local-only=true, > +# and gate it on Service_Monitor.status. > + > +check ovn-nbctl lr-add lr0 > +check ovn-nbctl set Logical_Router lr0 \ > + options:dynamic-routing=true \ > + options:chassis=hv1 > +check ovn-nbctl lrp-add lr0 lr0-up 00:00:00:00:00:01 > +check ovn-nbctl lrp-set-options lr0-up dynamic-routing-redistribute=lb > +check ovn-nbctl ls-add up > +check ovn-nbctl lsp-add-router-port up up-lr0 lr0-up > + > +check ovn-nbctl lr-add lr1 > +check ovn-nbctl lrp-add lr1 lr1-up 00:00:00:00:00:02 10.0.0.1/24 > +check ovn-nbctl lsp-add-router-port up up-lr1 lr1-up > + > +# A backend logical switch hanging off lr1. The two backend LSPs are > +# what we want as tracked_port in Advertised_Route. > +check ovn-nbctl lrp-add lr1 lr1-be 00:00:00:00:00:03 192.168.1.1/24 > +check ovn-nbctl ls-add be > +check ovn-nbctl lsp-add-router-port be be-lr1 lr1-be > +check ovn-nbctl lsp-add be be-vm1 > +check ovn-nbctl lsp-set-addresses be-vm1 "00:00:00:00:01:01 192.168.1.10" > +check ovn-nbctl lsp-add be be-vm2 > +check ovn-nbctl lsp-set-addresses be-vm2 "00:00:00:00:01:02 192.168.1.11" > + > +check ovn-nbctl \ > + -- lb-add lb0 172.16.1.10:80 192.168.1.10:80,192.168.1.11:80 \ > + -- set Load_Balancer lb0 options:distributed=true \ > + ip_port_mappings:192.168.1.10="be-vm1:192.168.1.1" \ > + ip_port_mappings:192.168.1.11="be-vm2:192.168.1.1" \ > + -- lr-lb-add lr1 lb0 > +check ovn-nbctl --wait=sb sync > + > +datapath_lr0=$(fetch_column Datapath_Binding _uuid external_ids:name=lr0) > +pb_lr0_up=$(fetch_column Port_Binding _uuid logical_port=lr0-up) > +pb_be_vm1=$(fetch_column Port_Binding _uuid logical_port=be-vm1) > +pb_be_vm2=$(fetch_column Port_Binding _uuid logical_port=be-vm2) > + > +# Expect two Advertised_Route rows - one per backend LSP. > +check_row_count Advertised_Route 2 > +check_row_count Advertised_Route 1 \ > + ip_prefix="172.16.1.10" \ > + datapath=$datapath_lr0 \ > + logical_port=$pb_lr0_up \ > + tracked_port=$pb_be_vm1 > +check_row_count Advertised_Route 1 \ > + ip_prefix="172.16.1.10" \ > + datapath=$datapath_lr0 \ > + logical_port=$pb_lr0_up \ > + tracked_port=$pb_be_vm2 > + > +# Forwarding parsed_route is still emitted once - the data-plane > +# decision is independent of backend identity. > +ovn-sbctl lflow-list lr0 > lr0_flows > +AT_CHECK([grep -c 'lr_in_ip_routing.*172.16.1.10/32' lr0_flows], [0], [1 > +]) > + > +# Without ip_port_mappings, the fallback path emits one row with > +# tracked_port = peer LRP (covered by the existing test). Re-derive > +# that by clearing ip_port_mappings and re-syncing. > +check ovn-nbctl --wait=sb clear Load_Balancer lb0 ip_port_mappings > +pb_lr1_up=$(fetch_column Port_Binding _uuid logical_port=lr1-up) > +check_row_count Advertised_Route 1 > +check_row_count Advertised_Route 1 \ > + ip_prefix="172.16.1.10" \ > + datapath=$datapath_lr0 \ > + logical_port=$pb_lr0_up \ > + tracked_port=$pb_lr1_up > + > +OVN_CLEANUP_NORTHD > +AT_CLEANUP > +]) > + > +OVN_FOR_EACH_NORTHD_NO_HV([ > +AT_SETUP([dynamic-routing - LB redistribute SCTP per-backend route emission]) > +AT_KEYWORDS([dynamic-routing]) > +ovn_start > + > +# SCTP is outside the Service_Monitor.protocol enum (tcp/udp/icmp), so > +# the per-backend Advertised_Route rows must NOT carry service selector > +# columns. > + > +check ovn-nbctl lr-add lr0 > +check ovn-nbctl set Logical_Router lr0 \ > + options:dynamic-routing=true \ > + options:chassis=hv1 > +check ovn-nbctl lrp-add lr0 lr0-up 00:00:00:00:00:01 > +check ovn-nbctl lrp-set-options lr0-up dynamic-routing-redistribute=lb > +check ovn-nbctl ls-add up > +check ovn-nbctl lsp-add-router-port up up-lr0 lr0-up > + > +check ovn-nbctl lr-add lr1 > +check ovn-nbctl lrp-add lr1 lr1-up 00:00:00:00:00:02 10.0.0.1/24 > +check ovn-nbctl lsp-add-router-port up up-lr1 lr1-up > + > +check ovn-nbctl lrp-add lr1 lr1-be 00:00:00:00:00:03 192.168.1.1/24 > +check ovn-nbctl ls-add be > +check ovn-nbctl lsp-add-router-port be be-lr1 lr1-be > +check ovn-nbctl lsp-add be be-vm1 > +check ovn-nbctl lsp-set-addresses be-vm1 "00:00:00:00:01:01 192.168.1.10" > +check ovn-nbctl lsp-add be be-vm2 > +check ovn-nbctl lsp-set-addresses be-vm2 "00:00:00:00:01:02 192.168.1.11" > + > +check ovn-nbctl \ > + -- lb-add lb0 172.16.1.10:80 192.168.1.10:80,192.168.1.11:80 sctp \ > + -- set Load_Balancer lb0 options:distributed=true \ > + ip_port_mappings:192.168.1.10="be-vm1:192.168.1.1" \ > + ip_port_mappings:192.168.1.11="be-vm2:192.168.1.1" \ > + -- lr-lb-add lr1 lb0 > +check ovn-nbctl --wait=sb sync > + > +datapath_lr0=$(fetch_column Datapath_Binding _uuid external_ids:name=lr0) > +pb_be_vm1=$(fetch_column Port_Binding _uuid logical_port=be-vm1) > +pb_be_vm2=$(fetch_column Port_Binding _uuid logical_port=be-vm2) > + > +# Expect two Advertised_Route rows - one per backend LSP. > +check_row_count Advertised_Route 2 > +check_row_count Advertised_Route 1 \ > + ip_prefix="172.16.1.10" \ > + datapath=$datapath_lr0 \ > + tracked_port=$pb_be_vm1 > +check_row_count Advertised_Route 1 \ > + ip_prefix="172.16.1.10" \ > + datapath=$datapath_lr0 \ > + tracked_port=$pb_be_vm2 > + > +OVN_CLEANUP_NORTHD > +AT_CLEANUP > +]) > + > +OVN_FOR_EACH_NORTHD_NO_HV([ > +AT_SETUP([dynamic-routing - LB redistribute no ip_port_mappings fallback]) > +AT_KEYWORDS([dynamic-routing]) > +ovn_start > + > +# When an LB has no ip_port_mappings at all, the fallback path emits > +# one Advertised_Route per VIP with tracked_port set to the peer LRP. > + > +check ovn-nbctl lr-add lr0 > +check ovn-nbctl set Logical_Router lr0 \ > + options:dynamic-routing=true \ > + options:chassis=hv1 > +check ovn-nbctl lrp-add lr0 lr0-up 00:00:00:00:00:01 > +check ovn-nbctl lrp-set-options lr0-up dynamic-routing-redistribute=lb > +check ovn-nbctl ls-add up > +check ovn-nbctl lsp-add-router-port up up-lr0 lr0-up > + > +check ovn-nbctl lr-add lr1 > +check ovn-nbctl lrp-add lr1 lr1-up 00:00:00:00:00:02 10.0.0.1/24 > +check ovn-nbctl lsp-add-router-port up up-lr1 lr1-up > +check ovn-nbctl lrp-add lr1 lr1-be 00:00:00:00:00:03 192.168.1.1/24 > +check ovn-nbctl ls-add be > +check ovn-nbctl lsp-add-router-port be be-lr1 lr1-be > +check ovn-nbctl lsp-add be be-vm1 > +check ovn-nbctl lsp-set-addresses be-vm1 "00:00:00:00:01:01 192.168.1.10" > +check ovn-nbctl lsp-add be be-vm2 > +check ovn-nbctl lsp-set-addresses be-vm2 "00:00:00:00:01:02 192.168.1.11" > + > +# LB with no ip_port_mappings at all. > +check ovn-nbctl \ > + -- lb-add lb0 172.16.1.10:80 192.168.1.10:80,192.168.1.11:80 \ > + -- set Load_Balancer lb0 options:distributed=true \ > + -- lr-lb-add lr1 lb0 > +check ovn-nbctl --wait=sb sync > + > +datapath_lr0=$(fetch_column Datapath_Binding _uuid external_ids:name=lr0) > +pb_lr0_up=$(fetch_column Port_Binding _uuid logical_port=lr0-up) > +pb_lr1_up=$(fetch_column Port_Binding _uuid logical_port=lr1-up) > + > +# One Advertised_Route row with tracked_port = peer LRP (lr1-up), > +# not a per-backend row. > +check_row_count Advertised_Route 1 > +check_row_count Advertised_Route 1 \ > + ip_prefix="172.16.1.10" \ > + datapath=$datapath_lr0 \ > + logical_port=$pb_lr0_up \ > + tracked_port=$pb_lr1_up > + > +OVN_CLEANUP_NORTHD > +AT_CLEANUP > +]) > + > +OVN_FOR_EACH_NORTHD_NO_HV([ > +AT_SETUP([dynamic-routing - LB redistribute partial ip_port_mappings]) > +AT_KEYWORDS([dynamic-routing]) > +ovn_start > + > +# When only some backends have ip_port_mappings entries, only those > +# backends should produce per-backend Advertised_Route rows. > + > +check ovn-nbctl lr-add lr0 > +check ovn-nbctl set Logical_Router lr0 \ > + options:dynamic-routing=true \ > + options:chassis=hv1 > +check ovn-nbctl lrp-add lr0 lr0-up 00:00:00:00:00:01 > +check ovn-nbctl lrp-set-options lr0-up dynamic-routing-redistribute=lb > +check ovn-nbctl ls-add up > +check ovn-nbctl lsp-add-router-port up up-lr0 lr0-up > + > +check ovn-nbctl lr-add lr1 > +check ovn-nbctl lrp-add lr1 lr1-up 00:00:00:00:00:02 10.0.0.1/24 > +check ovn-nbctl lsp-add-router-port up up-lr1 lr1-up > + > +check ovn-nbctl lrp-add lr1 lr1-be 00:00:00:00:00:03 192.168.1.1/24 > +check ovn-nbctl ls-add be > +check ovn-nbctl lsp-add-router-port be be-lr1 lr1-be > +check ovn-nbctl lsp-add be be-vm1 > +check ovn-nbctl lsp-set-addresses be-vm1 "00:00:00:00:01:01 192.168.1.10" > +check ovn-nbctl lsp-add be be-vm2 > +check ovn-nbctl lsp-set-addresses be-vm2 "00:00:00:00:01:02 192.168.1.11" > + > +# Only map one backend initially. > +check ovn-nbctl \ > + -- lb-add lb0 172.16.1.10:80 192.168.1.10:80,192.168.1.11:80 \ > + -- set Load_Balancer lb0 options:distributed=true \ > + ip_port_mappings:192.168.1.10="be-vm1:192.168.1.1" \ > + -- lr-lb-add lr1 lb0 > +check ovn-nbctl --wait=sb sync > + > +datapath_lr0=$(fetch_column Datapath_Binding _uuid external_ids:name=lr0) > +pb_be_vm1=$(fetch_column Port_Binding _uuid logical_port=be-vm1) > +pb_be_vm2=$(fetch_column Port_Binding _uuid logical_port=be-vm2) > + > +# Only one Advertised_Route row - for the mapped backend. > +check_row_count Advertised_Route 1 > +check_row_count Advertised_Route 1 \ > + ip_prefix="172.16.1.10" \ > + datapath=$datapath_lr0 \ > + tracked_port=$pb_be_vm1 > + > +# Add the second mapping and re-sync. > +check ovn-nbctl --wait=sb set Load_Balancer lb0 \ > + ip_port_mappings:192.168.1.10="be-vm1:192.168.1.1" \ > + ip_port_mappings:192.168.1.11="be-vm2:192.168.1.1" > + > +# Now expect two rows - both backends mapped. > +check_row_count Advertised_Route 2 > +check_row_count Advertised_Route 1 \ > + ip_prefix="172.16.1.10" \ > + datapath=$datapath_lr0 \ > + tracked_port=$pb_be_vm1 > +check_row_count Advertised_Route 1 \ > + ip_prefix="172.16.1.10" \ > + datapath=$datapath_lr0 \ > + tracked_port=$pb_be_vm2 > + > +OVN_CLEANUP_NORTHD > +AT_CLEANUP > +]) > + > +OVN_FOR_EACH_NORTHD_NO_HV([ > +AT_SETUP([dynamic-routing - LB redistribute IPv6 per-backend route emission]) > +AT_KEYWORDS([dynamic-routing]) > +ovn_start > + > +# IPv6 variant of the per-backend test: one Advertised_Route row > +# is emitted per backend LSP. > + > +check ovn-nbctl lr-add lr0 > +check ovn-nbctl set Logical_Router lr0 \ > + options:dynamic-routing=true \ > + options:chassis=hv1 > +check ovn-nbctl lrp-add lr0 lr0-up 00:00:00:00:00:01 > +check ovn-nbctl lrp-set-options lr0-up dynamic-routing-redistribute=lb > +check ovn-nbctl ls-add up > +check ovn-nbctl lsp-add-router-port up up-lr0 lr0-up > + > +check ovn-nbctl lr-add lr1 > +check ovn-nbctl lrp-add lr1 lr1-up 00:00:00:00:00:02 2001:db8::1/64 > +check ovn-nbctl lsp-add-router-port up up-lr1 lr1-up > + > +check ovn-nbctl lrp-add lr1 lr1-be 00:00:00:00:00:03 2001:db8:1::1/64 > +check ovn-nbctl ls-add be > +check ovn-nbctl lsp-add-router-port be be-lr1 lr1-be > +check ovn-nbctl lsp-add be be-vm1 > +check ovn-nbctl lsp-set-addresses be-vm1 "00:00:00:00:01:01 2001:db8:1::10" > +check ovn-nbctl lsp-add be be-vm2 > +check ovn-nbctl lsp-set-addresses be-vm2 "00:00:00:00:01:02 2001:db8:1::11" > + > +check ovn-nbctl \ > + -- lb-add lb0 [[2001:db8:ffff::10]]:80 > [[2001:db8:1::10]]:80,[[2001:db8:1::11]]:80 \ > + -- set Load_Balancer lb0 options:distributed=true \ > + ip_port_mappings:\"[[2001:db8:1::10]]\"=\"be-vm1:[[2001:db8:1::1]]\" > \ > + ip_port_mappings:\"[[2001:db8:1::11]]\"=\"be-vm2:[[2001:db8:1::1]]\" > \ > + -- lr-lb-add lr1 lb0 > +check ovn-nbctl --wait=sb sync > + > +datapath_lr0=$(fetch_column Datapath_Binding _uuid external_ids:name=lr0) > +pb_be_vm1=$(fetch_column Port_Binding _uuid logical_port=be-vm1) > +pb_be_vm2=$(fetch_column Port_Binding _uuid logical_port=be-vm2) > + > +# Expect two Advertised_Route rows - one per backend LSP. > +check_row_count Advertised_Route 2 > +check_row_count Advertised_Route 1 \ > + ip_prefix="2001\:db8\:ffff\:\:10" \ > + datapath=$datapath_lr0 \ > + tracked_port=$pb_be_vm1 > +check_row_count Advertised_Route 1 \ > + ip_prefix="2001\:db8\:ffff\:\:10" \ > + datapath=$datapath_lr0 \ > + tracked_port=$pb_be_vm2 > + > +OVN_CLEANUP_NORTHD > +AT_CLEANUP > +]) > + > +OVN_FOR_EACH_NORTHD_NO_HV([ > +AT_SETUP([dynamic-routing - LB route source marker reconciled on existing > rows]) > +AT_KEYWORDS([dynamic-routing]) > +ovn_start > + > +# Regression test: northd must set external_ids:source=lb on LB-derived > +# Advertised_Route rows. The marker must persist when the row is reused > +# across recomputes (e.g. when adding a second backend), and northd must > +# restore it on existing rows that lack the marker (upgrade scenario). > + > +check ovn-nbctl lr-add lr0 > +check ovn-nbctl set Logical_Router lr0 \ > + options:dynamic-routing=true \ > + options:chassis=hv1 > +check ovn-nbctl lrp-add lr0 lr0-up 00:00:00:00:00:01 > +check ovn-nbctl lrp-set-options lr0-up dynamic-routing-redistribute=lb > +check ovn-nbctl ls-add up > +check ovn-nbctl lsp-add-router-port up up-lr0 lr0-up > + > +check ovn-nbctl lr-add lr1 > +check ovn-nbctl lrp-add lr1 lr1-up 00:00:00:00:00:02 10.0.0.1/24 > +check ovn-nbctl lsp-add-router-port up up-lr1 lr1-up > +check ovn-nbctl lrp-add lr1 lr1-be 00:00:00:00:00:03 192.168.1.1/24 > +check ovn-nbctl ls-add be > +check ovn-nbctl lsp-add-router-port be be-lr1 lr1-be > +check ovn-nbctl lsp-add be be-vm1 > +check ovn-nbctl lsp-set-addresses be-vm1 "00:00:00:00:01:01 192.168.1.10" > +check ovn-nbctl lsp-add be be-vm2 > +check ovn-nbctl lsp-set-addresses be-vm2 "00:00:00:00:01:02 192.168.1.11" > + > +# Start with one mapped backend. > +check ovn-nbctl \ > + -- lb-add lb0 172.16.1.10:80 192.168.1.10:80,192.168.1.11:80 \ > + -- set Load_Balancer lb0 options:distributed=true \ > + ip_port_mappings:192.168.1.10="be-vm1:192.168.1.1" \ > + -- lr-lb-add lr1 lb0 > +check ovn-nbctl --wait=sb sync > + > +# One row with source=lb. > +check_row_count Advertised_Route 1 > +AT_CHECK([ovn-sbctl --columns=external_ids find Advertised_Route \ > + ip_prefix="172.16.1.10" | grep -q 'source=lb']) > + > +# Add the second backend mapping -- this triggers a northd recompute > +# that reuses the existing row (same tracked_port=be-vm1) and adds > +# a new row for be-vm2. > +check ovn-nbctl --wait=sb set Load_Balancer lb0 \ > + ip_port_mappings:192.168.1.10="be-vm1:192.168.1.1" \ > + ip_port_mappings:192.168.1.11="be-vm2:192.168.1.1" > + > +# Both rows must carry source=lb. > +check_row_count Advertised_Route 2 > +AT_CHECK([ovn-sbctl --columns=external_ids find Advertised_Route \ > + ip_prefix="172.16.1.10" | grep -c 'source=lb'], [0], [2 > +]) > + > +# Simulate upgrade from a version that did not set source=lb: strip > +# the marker from existing rows and verify northd restores it via > +# the reconciliation branch on the next recompute. > +AT_CHECK([ > +for uuid in $(ovn-sbctl --columns=_uuid --no-headings --bare find \ > + Advertised_Route ip_prefix=172.16.1.10); do > + (ovn-sbctl remove Advertised_Route "$uuid" external_ids source \ > + 2>/dev/null) || true > +done > +], [0], [ignore], [ignore]) > + > +# The marker is gone. > +AT_CHECK([! ovn-sbctl --columns=external_ids find Advertised_Route \ > + ip_prefix="172.16.1.10" | grep -q 'source=lb']) > + > +# Trigger a northd recompute by modifying ip_port_mappings (same > +# backends, same VIP IP -- existing rows are reused and the > +# reconciliation branch must restore source=lb). > +check ovn-nbctl --wait=sb set Load_Balancer lb0 \ > + ip_port_mappings:192.168.1.10="be-vm1:192.168.1.1" \ > + ip_port_mappings:192.168.1.11="be-vm2:192.168.1.2" > + > +# The reconciliation branch must have restored source=lb on both rows. > +AT_CHECK([ovn-sbctl --columns=external_ids find Advertised_Route \ > + ip_prefix="172.16.1.10" | grep -c 'source=lb'], [0], [2 > +]) > + > +OVN_CLEANUP_NORTHD > +AT_CLEANUP > +]) > + > +OVN_FOR_EACH_NORTHD_NO_HV([ > +AT_SETUP([dynamic-routing - non-LB routes do not carry source=lb marker]) > +AT_KEYWORDS([dynamic-routing]) > +ovn_start > + > +# Regression test: routes from NAT redistribution must not carry > +# external_ids:source=lb, so the controller never applies Service_Monitor > +# gating to non-LB routes. > + > +check ovn-nbctl lr-add lr0 > +check ovn-nbctl set Logical_Router lr0 \ > + options:dynamic-routing=true \ > + options:chassis=hv1 > +check ovn-nbctl lrp-add lr0 lr0-up 00:00:00:00:00:01 > +check ovn-nbctl lrp-set-options lr0-up dynamic-routing-redistribute=nat > +check ovn-nbctl ls-add up > +check ovn-nbctl lsp-add-router-port up up-lr0 lr0-up > + > +check ovn-nbctl lr-add lr1 > +check ovn-nbctl lrp-add lr1 lr1-up 00:00:00:00:00:02 10.0.0.1/24 > +check ovn-nbctl lrp-set-gateway-chassis lr1-up hv1 > +check ovn-nbctl lsp-add-router-port up up-lr1 lr1-up > +check ovn-nbctl --add-route lr-nat-add lr1 dnat_and_snat 172.16.1.10 > 192.168.1.10 > +check ovn-nbctl --wait=sb sync > + > +# NAT-derived row must not have source=lb. > +check_row_count Advertised_Route 1 > +AT_CHECK([! ovn-sbctl --columns=external_ids find Advertised_Route \ > + ip_prefix="172.16.1.10" | grep -q 'source=lb']) > + > +OVN_CLEANUP_NORTHD > +AT_CLEANUP > +]) > + > OVN_FOR_EACH_NORTHD_NO_HV([ > AT_SETUP([dynamic-routing - LB forwarding route updates on nexthop change]) > AT_KEYWORDS([dynamic-routing]) > @@ -20022,7 +20460,7 @@ AT_CHECK([grep lr_in_admission lr1_lflows | grep -E > 'priority=5[[05]]' | grep '0 > 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. > +# cr-port options must clear 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 > ]) > -- > 2.53.0 > > > _______________________________________________ > dev mailing list > [email protected] > https://mail.openvswitch.org/mailman/listinfo/ovs-dev > _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
