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

Reply via email to