On 1/24/26 6:04 PM, Lucas Vargas Dias via dev wrote:
> If logical router has more than one LRP as gateway router port
> and dynamic routing configured, dynamic-routing-port-name could be
> used to specify the LRP that will be used to dynamic routing.
> However, if all LRPs learning routes, routes from LRP without
> dynamic-routing-port-name must be flushed.
> This happens when LRPs are scheduled in the same chassis.
>
> Signed-off-by: Lucas Vargas Dias <[email protected]>
> ---
Hi Lucas,
Thanks for the patch!
> controller/route-exchange.c | 39 ++++++++++-
> tests/system-ovn.at | 126 ++++++++++++++++++++++++++++++++++++
> 2 files changed, 164 insertions(+), 1 deletion(-)
>
> diff --git a/controller/route-exchange.c b/controller/route-exchange.c
> index ae44ffe69..62f9a729b 100644
> --- a/controller/route-exchange.c
> +++ b/controller/route-exchange.c
> @@ -26,6 +26,7 @@
> #include "openvswitch/list.h"
>
> #include "lib/ovn-sb-idl.h"
> +#include "lib/uuidset.h"
>
> #include "binding.h"
> #include "ha-chassis.h"
> @@ -149,12 +150,33 @@ sb_sync_learned_routes(const struct vector
> *learned_routes,
> struct hmap sync_routes = HMAP_INITIALIZER(&sync_routes);
> const struct sbrec_learned_route *sb_route;
> struct route_entry *route_e;
> + struct uuidset uuid_set = UUIDSET_INITIALIZER(&uuid_set);
Maybe use a more explicit variable name instead of "uuid_set"?
> + bool has_dynamic_routing_port_name = false;
Nit: I'd reorder the declarations to maintain the reverse xmas tree.
We actually don't need the has_dynamic_routing_port_name variable. It's
equivalent to "!uuidset_is_empty(&uuid_set)".
>
> struct sbrec_learned_route *filter =
> sbrec_learned_route_index_init_row(sbrec_learned_route_by_datapath);
> sbrec_learned_route_index_set_datapath(filter, datapath);
> SBREC_LEARNED_ROUTE_FOR_EACH_EQUAL (sb_route, filter,
> sbrec_learned_route_by_datapath) {
> + route_add_entry(&sync_routes, sb_route, false);
> + const char *dynamic_routing_port_name =
> + smap_get(&sb_route->logical_port->options,
> + "dynamic-routing-port-name");
Nit: indentation; this should be one space to the left.
> + if (!dynamic_routing_port_name) {
> + const struct sbrec_port_binding *cr_pb =
> + lport_get_cr_port(sbrec_port_binding_by_name,
> + sb_route->logical_port, NULL);
> + if (cr_pb) {
> + dynamic_routing_port_name =
> + smap_get(&cr_pb->options, "dynamic-routing-port-name");
> + }
> + }
> +
> + if (dynamic_routing_port_name) {
> + uuidset_insert(&uuid_set, &sb_route->logical_port->header_.uuid);
> + has_dynamic_routing_port_name = true;
> + }
> +
> /* If the port is not local we don't care about it.
> * Some other ovn-controller will handle it.
> * We may not use smap_get since the value might be validly NULL. */
> @@ -162,10 +184,25 @@ sb_sync_learned_routes(const struct vector
> *learned_routes,
> sb_route->logical_port->logical_port)) {
> continue;
> }
> - route_add_entry(&sync_routes, sb_route, true);
> + route_e = route_lookup(&sync_routes, datapath,
> + sb_route->logical_port,
> + sb_route->ip_prefix,
> + sb_route->nexthop);
'route_e' is the one we just added in the beginning of the loop, right?
Why do we do this lookup here? Why not store a pointer to the one we
add? We'd have to change route_add_entry() then to return what it inserted.
> + if (route_e) {
> + route_e->stale = true;
> + }
> }
> sbrec_learned_route_index_destroy_row(filter);
>
> + if (has_dynamic_routing_port_name) {
> + HMAP_FOR_EACH_SAFE (route_e, hmap_node, &sync_routes) {
> + if (!uuidset_find(&uuid_set,
> + &route_e->sb_route->logical_port->header_.uuid)) {
> + route_e->stale = true;
So, at this point we also mark all SB.Learned_Routes that were learned
on a logical port that's a chassis-redirect port bound to a different
chassis as stale. And lower in this function we'll delete it if we
don't have it in the VRF's routing table as an extern_learned route.
But that other chassis might have the route in its VRF. ovn-controller
on that other chassis will wake up due to the SB update and recreate the
route.
And this will go on forever.
Or am I missing something?
Or was your intention actually to use the 'uuid_set' to store only
SB.Learned_Route records whose chassis-redirect logical_port is set
_and_ is local to the current chassis?
This scenario is impossible to test with a system-ovn.at test (we only
have one OVN chassis in those). Would it be possible to add a multinode
test for it? We have some BGP tests there.
The idea would be to also ensure that in such cases ovn-controllers
don't delete resources maintained by others.
> + }
> + }
> + }
> + uuidset_destroy(&uuid_set);
> struct re_nl_received_route_node *learned_route;
> VECTOR_FOR_EACH_PTR (learned_routes, learned_route) {
> char *ip_prefix = normalize_v46_prefix(&learned_route->prefix,
> diff --git a/tests/system-ovn.at b/tests/system-ovn.at
> index 6eaf38a4c..8b82cc3a0 100644
> --- a/tests/system-ovn.at
> +++ b/tests/system-ovn.at
> @@ -20531,3 +20531,129 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d
> /.*terminating with signal 15.*/d"])
> AT_CLEANUP
> ])
> +
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([dynamic-routing - BGP learned routes with router filter name and
> multiple DGPs])
> +
> +# This test validates that BGP learned routes work correctly:
> +# 1. Routes added to the VRF appear in Learned_Route table
> +# 2. Stopping ovn-controller remove learned routes
> +#
> +# Topology:
> +# +---------+
> +# | public |
> +# +----+----+
> +# |
> +# +----+---+
> +# | lr-frr | (in VRF 10)
> +# +----+---+
> +# |
> +# +------+-------+
> +# |local-bgp-port| (in VRF 10)
> +# +--------------+
> +
> +ovn_start
> +OVS_TRAFFIC_VSWITCHD_START()
> +ADD_BR([br-int])
> +ADD_BR([br-ex])
> +
> +check ovs-ofctl add-flow br-ex action=normal
> +
> +# Set external-ids in br-int needed for ovn-controller
> +check ovs-vsctl \
> + -- set Open_vSwitch . external-ids:system-id=hv1 \
> + -- set Open_vSwitch .
> external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
> + -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
> + -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
> + -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
> +
> +# Configure bridge mappings for localnet
> +check ovs-vsctl set Open_vSwitch .
> external-ids:ovn-bridge-mappings=phys:br-ex
> +
> +id=10
Nit: s/id/vrf.
We should also:
VRF_RESERVE([$vrf])
> +
> +# Start ovn-controller
> +start_daemon ovn-controller
> +
> +check ip link add vrf-$id type vrf table $id
> +on_exit "ip link del vrf-$id"
> +check ip link set vrf-$id up
> +
> +# Create public logical switch with localnet port
> +check ovn-nbctl ls-add public
> +check ovn-nbctl lsp-add-localnet-port public ln_port phys
> +
> +# Create lr-frr with dynamic routing in VRF $id
> +check ovn-nbctl lr-add lr-frr \
> + -- set Logical_Router lr-frr \
> + options:dynamic-routing=true \
> + options:dynamic-routing-vrf-id=$id \
> + options:dynamic-routing-redistribute=static
> +
> +check ovn-nbctl lrp-add lr-frr lrp-local-bgp-port 00:00:00:00:00:03
> 20.0.0.3/24 \
> + -- set Logical_Router_Port lrp-local-bgp-port
> options:dynamic-routing-maintain-vrf=true \
If maintain-vrf=true, why do we create the vrf ourselves above?
> + -- set Logical_Router_Port lrp-local-bgp-port
> options:routing-protocol-redirect=local-bgp-port
> +
> +check ovn-nbctl lrp-set-gateway-chassis lrp-local-bgp-port hv1
> +check ovn-nbctl lsp-add-router-port public public-lr-frr lrp-local-bgp-port
> +
> +check ovn-nbctl lrp-add lr-frr lrp-dgp-dummy 00:00:00:00:00:04 20.0.1.3/24
> +check ovn-nbctl lrp-set-gateway-chassis lrp-dgp-dummy hv1
> +check ovn-nbctl ls-add ls-dummy
> +check ovn-nbctl lsp-add-router-port ls-dummy lsp-dummy lrp-dgp-dummy
> +
> +
> +# Create local-bgp-port in VRF 10
> +check ovs-vsctl add-port br-int local-bgp-port \
> + -- set Interface local-bgp-port type=internal \
> + -- set Interface local-bgp-port external_ids:iface-id=local-bgp-port
> +
> +check ovn-nbctl lsp-add public local-bgp-port \
> + -- lsp-set-addresses local-bgp-port unknown
> +
> +# Configure local-bgp-port interface and add to VRF
> +check ip link set local-bgp-port master vrf-$id
> +check ip link set local-bgp-port address 00:00:00:00:00:03
> +check ip addr add dev local-bgp-port 20.0.0.3/24
> +check ip link set local-bgp-port up
> +
> +# Wait for everything to be ready
> +wait_for_ports_up
> +check ovn-nbctl --wait=hv sync
> +
If we remove the explicit vrf creation in the beginning of this test, we
should also wait here until ovn-controller created the VRF
(maintain-vrf=true), i.e.:
OVS_WAIT_WHILE([ip link | grep -q ovnvrf$vrf:.*UP])
> +# Check lrp-local-bgp-port has dynamic-routing option set.
> +check_row_count Port_Binding 1 logical_port=cr-lrp-local-bgp-port
> 'options:dynamic-routing=true'
> +
> +# Add static routes
> +check ovn-nbctl lr-route-add lr-frr 10.10.2.1 20.0.0.42 lrp-local-bgp-port
> +
> +# Verify advertised routes exist
> +AS_BOX([Advertised_Route])
> +wait_row_count Advertised_Route 1 ip_prefix=10.10.2.1
> +
> +# Add a route to the VRF (simulating BGP learning a route)
> +check ip route add 10.10.3.1 via 20.0.0.25 vrf vrf-$id proto zebra
> +
> +
Nit: missing space.
> +# Verify learned route appears in SB database
> +check_row_count Learned_Route 2 ip_prefix=10.10.3.1
> +
> +check ovn-nbctl --wait=hv set Logical_Router_Port lrp-local-bgp-port
> options:dynamic-routing-port-name=bgpvrf1002
> +
> +check_row_count Learned_Route 1 ip_prefix=10.10.3.1
> +
> +# Stop ovn-controller
> +OVN_CLEANUP_CONTROLLER([hv1], [], [], [lr-frr])
> +check ovn-nbctl --wait=sb sync
> +
> +# Verify routes are removed in SB database.
> +wait_row_count Learned_Route 0
> +
> +OVN_CLEANUP_NORTHD
> +
> +as
> +OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d
> +/failed to query port patch-.*/d
> +/.*terminating with signal 15.*/d"])
> +AT_CLEANUP
> +])
Regards,
Dumitru
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev