Hi Ales Thanks for the review and the comments
On Fri, Aug 14, 2026 at 10:13 AM Ales Musil <[email protected]> wrote: > > > On Fri, Aug 7, 2026 at 7:21 PM Xavier Simonart via dev < > [email protected]> wrote: > >> When an HA gateway chassis reboots, tunnel ports should be automatically >> removed. >> Without this, OVS might restore stale tunnel ports from the database which >> can interfere with HA failover (e.g. sets BFD up while no openflow flows >> are installed). >> >> Mark tunnel ports as transient (other_config:transient=true) when the >> chassis is a member of an HA chassis group. >> OVS automatically removes transient ports at system boot, when starting >> ovsdb-server service. >> >> Reported-at: https://redhat.atlassian.net/browse/FDP-3866 >> >> Signed-off-by: Xavier Simonart <[email protected]> >> --- >> > > Hi Xavier, > > thank you for the patch. I have a few comments down below. > > >> NEWS | 6 + >> controller/encaps.c | 22 +++- >> controller/encaps.h | 3 +- >> controller/ovn-controller.c | 33 +++++- >> tests/multinode-macros.at | 24 ++++ >> tests/multinode.at | 230 +++++++++++++++++++++++++++++++++--- >> tests/ovn-controller.at | 57 +++++++++ >> 7 files changed, 354 insertions(+), 21 deletions(-) >> >> diff --git a/NEWS b/NEWS >> index 44f117807..3765e870c 100644 >> --- a/NEWS >> +++ b/NEWS >> @@ -94,6 +94,12 @@ Post v26.03.0 >> - Added a new "ovn-debug lflow-pipeline-oftable-start-list" command >> that >> prints the starting OpenFlow table number of the logical ingress and >> egress pipelines. >> + - Mark tunnel ports as transient (other_config:transient=true) when >> the >> + local chassis is a member of an HA chassis group. >> + OVS removes transient ports on ovsdb-server restart, preventing >> stale >> + tunnel ports from interfering with BFD and HA failover after a >> gateway >> + chassis reboots. >> + >> >> OVN v26.03.0 - xxx xx xxxx >> -------------------------- >> diff --git a/controller/encaps.c b/controller/encaps.c >> index 048e85c38..8bd6b31d1 100644 >> --- a/controller/encaps.c >> +++ b/controller/encaps.c >> @@ -39,6 +39,7 @@ encaps_register_ovs_idl(struct ovsdb_idl *ovs_idl) >> ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_name); >> ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_interfaces); >> ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_external_ids); >> + ovsdb_idl_add_column(ovs_idl, &ovsrec_port_col_other_config); >> ovsdb_idl_add_table(ovs_idl, &ovsrec_table_interface); >> ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_name); >> ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_type); >> @@ -63,6 +64,7 @@ struct tunnel_ctx { >> const struct ovsrec_open_vswitch_table *ovs_table; >> const struct ovsrec_bridge *br_int; >> const struct sbrec_chassis *this_chassis; >> + bool is_ha_chassis_member; >> }; >> >> struct tunnel_node { >> @@ -209,6 +211,10 @@ tunnel_add(struct tunnel_ctx *tc, >> { >> struct smap options = SMAP_INITIALIZER(&options); >> struct smap other_config = SMAP_INITIALIZER(&other_config); >> + struct smap port_other_config = SMAP_INITIALIZER(&port_other_config); >> + if (tc->is_ha_chassis_member) { >> + smap_add(&port_other_config, "transient", "true"); >> + } >> smap_add(&options, "remote_ip", encap->ip); >> smap_add(&options, "local_ip", local_ip); >> smap_add(&options, "key", "flow"); >> @@ -292,9 +298,9 @@ tunnel_add(struct tunnel_ctx *tc, >> } >> >> /* If there's an existing tunnel record that does not need any >> change, >> - * keep it. Otherwise, create a new record (if there was an existing >> - * record, the new record will supplant it and encaps_run() will >> delete >> - * it). */ >> + * (except maybe the transient flag) keep it. Otherwise, create a >> new >> + * record (if there was an existing record, the new record will >> + * supplant it and encaps_run() will delete it). */ >> struct tunnel_node *tunnel = shash_find_data(&tc->tunnel, >> tunnel_entry_id); >> bool old_id_format = false; >> @@ -306,6 +312,10 @@ tunnel_add(struct tunnel_ctx *tc, >> && tunnel->port->n_interfaces == 1 >> && !strcmp(tunnel->port->interfaces[0]->type, encap->type) >> && smap_equal(&tunnel->port->interfaces[0]->options, &options)) { >> + if (!smap_equal(&tunnel->port->other_config, >> &port_other_config)) { >> + ovsrec_port_set_other_config(tunnel->port, >> &port_other_config); >> + } >> > > Could we use setkey/delkey instead e.g.? > > if (tc->is_ha_chassis_member) { > ovsrec_port_update_other_config_setkey(tunnel->port, > "transient", "true"); > } else if (smap_get(&tunnel->port->other_config, "transient")) { > ovsrec_port_update_other_config_delkey(tunnel->port, > "transient"); > } > Yes. Will do in v2. > > >> + >> if (old_id_format) { >> /* We must be upgrading from an older version. We can reuse >> the >> * existing tunnel, but needs to update the tunnel's ID to >> the new >> @@ -345,6 +355,7 @@ tunnel_add(struct tunnel_ctx *tc, >> ovsrec_port_set_interfaces(port, &iface, 1); >> const struct smap id = SMAP_CONST1(&id, OVN_TUNNEL_ID, >> tunnel_entry_id); >> ovsrec_port_set_external_ids(port, &id); >> + ovsrec_port_set_other_config(port, &port_other_config); >> > > If we use the setkey/delkey we can use SMAP_CONST1 here. > > >> >> ovsrec_bridge_update_ports_addvalue(tc->br_int, port); >> >> @@ -355,6 +366,7 @@ exit: >> free(tunnel_entry_id_old); >> smap_destroy(&options); >> smap_destroy(&other_config); >> + smap_destroy(&port_other_config); >> } >> >> static bool >> @@ -734,7 +746,8 @@ encaps_run(struct ovsdb_idl_txn *ovs_idl_txn, >> const struct sbrec_sb_global *sbg, >> const struct ovsrec_open_vswitch_table *ovs_table, >> const struct sset *transport_zones, >> - const struct ovsrec_bridge_table *bridge_table) >> + const struct ovsrec_bridge_table *bridge_table, >> + bool is_ha_chassis_member) >> { >> if (!ovs_idl_txn || !ovnsb_idl_txn || !br_int) { >> return; >> @@ -781,6 +794,7 @@ encaps_run(struct ovsdb_idl_txn *ovs_idl_txn, >> .br_int = br_int, >> .this_chassis = this_chassis, >> .ovs_table = ovs_table, >> + .is_ha_chassis_member = is_ha_chassis_member, >> }; >> >> tc.ovs_txn = ovs_idl_txn; >> diff --git a/controller/encaps.h b/controller/encaps.h >> index 0257d08c1..f29a128eb 100644 >> --- a/controller/encaps.h >> +++ b/controller/encaps.h >> @@ -49,7 +49,8 @@ void encaps_run(struct ovsdb_idl_txn *ovs_idl_txn, >> const struct sbrec_sb_global *, >> const struct ovsrec_open_vswitch_table *, >> const struct sset *transport_zones, >> - const struct ovsrec_bridge_table *bridge_table); >> + const struct ovsrec_bridge_table *bridge_table, >> + bool is_ha_chassis_member); >> >> bool is_flow_based_tunnels_enabled( >> const struct ovsrec_open_vswitch_table *ovs_table, >> diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c >> index ea9952a19..a70eaf73c 100644 >> --- a/controller/ovn-controller.c >> +++ b/controller/ovn-controller.c >> @@ -1043,6 +1043,33 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl) >> * track that column which should be addressed in the future. */ >> } >> >> +static bool >> +chassis_is_ha_member(const struct sbrec_chassis *chassis, >> + const struct sbrec_ha_chassis_group_table >> *ha_grp_table) >> +{ >> + static bool is_ha_member = false; >> > > This variable is not needed at all. > > >> + const struct sbrec_ha_chassis_group *grp; >> + >> + if (!sbrec_ha_chassis_group_table_track_get_first(ha_grp_table)) { >> + return is_ha_member; >> + } >> > > This if shouldn't be needed, the loop wouldn't run if it's empty. > > >> + >> + SBREC_HA_CHASSIS_GROUP_TABLE_FOR_EACH (grp, ha_grp_table) { >> + /* If there is only one member, then it is not HA */ >> + if (grp->n_ha_chassis < 2) { >> + continue; >> + } >> + for (size_t i = 0; i < grp->n_ha_chassis; i++) { >> + if (grp->ha_chassis[i]->chassis == chassis) { >> + is_ha_member = true; >> + return true; >> + } >> + } >> + } >> + is_ha_member = false; >> + return false; >> +} >> + >> struct ed_type_ofctrl_is_connected { >> bool connected; >> }; >> @@ -8034,13 +8061,17 @@ main(int argc, char *argv[]) >> const struct sbrec_sb_global *sbg = >> sbrec_sb_global_first(ovnsb_idl_loop.idl); >> if (chassis && sbg && ovs_feature_set_discovered()) { >> + bool is_ha_chassis_member = chassis_is_ha_member( >> + chassis, >> + >> sbrec_ha_chassis_group_table_get(ovnsb_idl_loop.idl)); >> > > Do we risk a problem if we would use the en_bfd_chassis node? > On the first engine run we wouldn't have this info > This is why I did not use the node initially. > , but we could > flag all tunnels transient temporarily in case of a restart > between the engine runs WDYT? > This sounded like a workaround (add the flag, then remove it in many cases). However, this comment helped me look back into it, and we can - use en_bfd_chassis_node when available - when not available (i.e. restart) use bfd_calculate_chassis to check if chassis is ha member. Will do in v2 > > >> encaps_run(ovs_idl_txn, ovnsb_idl_txn, br_int, >> >> sbrec_chassis_table_get(ovnsb_idl_loop.idl), >> chassis, >> sbg, >> ovs_table, >> &transport_zones, >> - bridge_table); >> + bridge_table, >> + is_ha_chassis_member); >> >> ovn_netlink_notifiers_run(); >> >> diff --git a/tests/multinode-macros.at b/tests/multinode-macros.at >> index ade4d167c..88677e364 100644 >> --- a/tests/multinode-macros.at >> +++ b/tests/multinode-macros.at >> @@ -510,6 +510,30 @@ m_is_fedora() { >> m_central_as grep -qi fedora /etc/os-release >> } >> >> +# Run ovs-vsctl using Host socket >> +host_ovs_vsctl() { >> + # Discover host OVS socket on first call >> + if [[ -z "$HOST_OVS_SOCK" ]]; then >> + for sock in /run/openvswitch/db.sock >> /var/run/openvswitch/db.sock /usr/local/var/run/openvswitch/db.sock; do >> + if [[ -S "$sock" ]]; then >> + HOST_OVS_SOCK=$sock >> + break >> + fi >> + done >> + # Fallback on unusual prefix: discover from running process >> + if [[ -z "$HOST_OVS_SOCK" ]]; then >> + HOST_OVS_SOCK=$(ps aux | grep '[o]vsdb-server' | grep -oP >> 'punix:\K[^, ]+' | while read s; do >> + [[ -S "$s" ]] && [[ "$s" != *"$OVS_RUNDIR"* ]] && echo >> "$s" && break >> + done) >> + fi >> + if [[ -z "$HOST_OVS_SOCK" ]]; then >> + echo "ERROR: Could not find host OVS socket" >&2 >> + AT_FAIL_IF([:]) >> + fi >> + fi >> + ovs-vsctl --db=unix:$HOST_OVS_SOCK "$@" >> +} >> + >> # M_START_L4_SERVER([fake_node], [namespace], [ip_addr], [port], >> [reply_string], [pidfile]) >> # >> # Helper to properly start l4 server in inside 'fake_node''s namespace'. >> diff --git a/tests/multinode.at b/tests/multinode.at >> index c69ab3cb4..1ea2ec08e 100644 >> --- a/tests/multinode.at >> +++ b/tests/multinode.at >> @@ -3093,6 +3093,10 @@ m_as ovn-gw-1 ovn-appctl vlog/disable-rate-limit >> m_as ovn-gw-2 ovn-appctl vlog/disable-rate-limit >> m_as ovn-gw-3 ovn-appctl vlog/disable-rate-limit >> >> +# Decrease revalidation time on ovs switch simulating ToR. >> +check host_ovs_vsctl set Open_vSwitch . other_config:max-revalidator=100 >> +on_exit "check host_ovs_vsctl remove Open_vSwitch . other_config >> max-revalidator" >> + >> check_fake_multinode_setup >> >> # Delete the multinode NB and OVS resources before starting the test. >> @@ -3104,18 +3108,38 @@ ip_gw1=$(m_as ovn-gw-1 ip a show dev eth1 | grep >> "inet " | awk '{print $2}'| cut >> ip_gw2=$(m_as ovn-gw-2 ip a show dev eth1 | grep "inet " | awk '{print >> $2}'| cut -d '/' -f1) >> ip_gw3=$(m_as ovn-gw-3 ip a show dev eth1 | grep "inet " | awk '{print >> $2}'| cut -d '/' -f1) >> >> -from_gw1_to_gw2=$(m_as ovn-gw-1 ovs-vsctl --bare --columns=name find >> interface options:remote_ip=$ip_gw2) >> -from_gw1_to_gw3=$(m_as ovn-gw-1 ovs-vsctl --bare --columns=name find >> interface options:remote_ip=$ip_gw3) >> -from_gw1_to_ch1=$(m_as ovn-gw-1 ovs-vsctl --bare --columns=name find >> interface options:remote_ip=$ip_ch1) >> -from_gw1_to_ch2=$(m_as ovn-gw-1 ovs-vsctl --bare --columns=name find >> interface options:remote_ip=$ip_ch2) >> -from_gw2_to_gw1=$(m_as ovn-gw-2 ovs-vsctl --bare --columns=name find >> interface options:remote_ip=$ip_gw1) >> -from_gw2_to_gw3=$(m_as ovn-gw-2 ovs-vsctl --bare --columns=name find >> interface options:remote_ip=$ip_gw3) >> -from_gw2_to_ch1=$(m_as ovn-gw-2 ovs-vsctl --bare --columns=name find >> interface options:remote_ip=$ip_ch1) >> -from_gw2_to_ch2=$(m_as ovn-gw-2 ovs-vsctl --bare --columns=name find >> interface options:remote_ip=$ip_ch2) >> -from_ch1_to_gw1=$(m_as ovn-chassis-1 ovs-vsctl --bare --columns=name >> find interface options:remote_ip=$ip_gw1) >> -from_ch1_to_gw2=$(m_as ovn-chassis-1 ovs-vsctl --bare --columns=name >> find interface options:remote_ip=$ip_gw2) >> -from_ch2_to_gw1=$(m_as ovn-chassis-2 ovs-vsctl --bare --columns=name >> find interface options:remote_ip=$ip_gw1) >> -from_ch2_to_gw2=$(m_as ovn-chassis-2 ovs-vsctl --bare --columns=name >> find interface options:remote_ip=$ip_gw2) >> +get_geneve_names_gw1() >> +{ >> + from_gw1_to_gw2=$(m_as ovn-gw-1 ovs-vsctl --bare --columns=name find >> interface options:remote_ip=$ip_gw2) >> + from_gw1_to_gw3=$(m_as ovn-gw-1 ovs-vsctl --bare --columns=name find >> interface options:remote_ip=$ip_gw3) >> + from_gw1_to_ch1=$(m_as ovn-gw-1 ovs-vsctl --bare --columns=name find >> interface options:remote_ip=$ip_ch1) >> + from_gw1_to_ch2=$(m_as ovn-gw-1 ovs-vsctl --bare --columns=name find >> interface options:remote_ip=$ip_ch2) >> +} >> + >> +get_geneve_names_gw2() >> +{ >> + from_gw2_to_gw1=$(m_as ovn-gw-2 ovs-vsctl --bare --columns=name find >> interface options:remote_ip=$ip_gw1) >> + from_gw2_to_gw3=$(m_as ovn-gw-2 ovs-vsctl --bare --columns=name find >> interface options:remote_ip=$ip_gw3) >> + from_gw2_to_ch1=$(m_as ovn-gw-2 ovs-vsctl --bare --columns=name find >> interface options:remote_ip=$ip_ch1) >> + from_gw2_to_ch2=$(m_as ovn-gw-2 ovs-vsctl --bare --columns=name find >> interface options:remote_ip=$ip_ch2) >> +} >> + >> +get_geneve_names_chassis1() >> +{ >> + from_ch1_to_gw1=$(m_as ovn-chassis-1 ovs-vsctl --bare --columns=name >> find interface options:remote_ip=$ip_gw1) >> + from_ch1_to_gw2=$(m_as ovn-chassis-1 ovs-vsctl --bare --columns=name >> find interface options:remote_ip=$ip_gw2) >> +} >> + >> +get_geneve_names_chassis2() >> +{ >> + from_ch2_to_gw1=$(m_as ovn-chassis-2 ovs-vsctl --bare --columns=name >> find interface options:remote_ip=$ip_gw1) >> + from_ch2_to_gw2=$(m_as ovn-chassis-2 ovs-vsctl --bare --columns=name >> find interface options:remote_ip=$ip_gw2) >> +} >> + >> +get_geneve_names_gw1 >> +get_geneve_names_gw2 >> +get_geneve_names_chassis1 >> +get_geneve_names_chassis2 >> >> m_as ovn-chassis-1 ip link del hv1-vif1-p >> m_as ovn-chassis-2 ip link del hv2-vif1-p >> @@ -3128,6 +3152,10 @@ OVS_WAIT_UNTIL([m_as ovn-gw-1 ip link show | grep >> -q genev_sys]) >> OVS_WAIT_UNTIL([m_as ovn-gw-2 ip link show | grep -q genev_sys]) >> OVS_WAIT_UNTIL([m_as ovn-gw-3 ip link show | grep -q genev_sys]) >> >> +# Use "aggressive" bfd parameters >> +check multinode_nbctl set NB_Global . options:"bfd-min-rx"=500 >> +check multinode_nbctl set NB_Global . options:"bfd-min-tx"=100 >> + >> check multinode_nbctl ls-add inside >> check multinode_nbctl ls-add outside >> check multinode_nbctl ls-add ext >> @@ -3186,6 +3214,8 @@ gw1_chassis=$(m_fetch_column Chassis _uuid >> name=ovn-gw-1) >> gw2_chassis=$(m_fetch_column Chassis _uuid name=ovn-gw-2) >> gw3_chassis=$(m_fetch_column Chassis _uuid name=ovn-gw-3) >> >> +gw1_eth1_ip_mask=$(m_as ovn-gw-1 ip -4 -o addr show eth1 | awk '{print >> $4}') >> + >> wait_bfd_enabled() { >> chassis=$1 >> interface=$2 >> @@ -3263,7 +3293,11 @@ start_tcpdump() { >> [ovn-gw-2], [-neei eth2], [gw2], >> [ovn-gw-2], [-neei eth2 -Q out], [gw2_out], >> [ovn-gw-3], [-neei eth2], [gw3], >> - [ovn-gw-3], [-neei eth2 -Q out], [gw3_out]) >> + [ovn-gw-3], [-neei eth2 -Q out], [gw3_out], >> + [ovn-gw-1], [-neei eth1], [gw1_eth1], >> + [ovn-gw-2], [-neei eth1], [gw2_eth1], >> + [ovn-chassis-1], [-neei eth1], [ch1_eth1], >> + [ovn-chassis-2], [-neei eth1], [ch2_eth1]) >> } >> >> stop_tcpdump() { >> @@ -3275,7 +3309,7 @@ stop_tcpdump() { >> send_background_packets() { >> echo "$(date +%H:%M:%S.%03N) Sending packets in Background" >> start_tcpdump >> - M_NS_DAEMONIZE([ovn-chassis-3], [ext1], [ping -qf -i 0.1 >> 192.168.1.1], [ping.pid]) >> + M_NS_DAEMONIZE([ovn-chassis-3], [ext1], [ping -qf -i 0.01 >> 192.168.1.1], [ping.pid]) >> } >> >> stop_sending_background_packets() { >> @@ -3357,6 +3391,28 @@ dump_statistics() { >> echo "$((ch3_req - ch3_rep))" >> } >> >> +add_port() { >> + bridge=$1 >> + interface=$2 >> + address=$3 >> + echo "Adding $bridge $interface $address" >> + >> + pid=$(podman inspect -f '{{.State.Pid}}' ovn-gw-1) >> + ln -sf /proc/$pid/ns/net /var/run/netns/$pid >> + port=$(host_ovs_vsctl --data=bare --no-heading --columns=name find >> interface \ >> + external_ids:container_id=ovn-gw-1 >> external_ids:container_iface="$interface") >> + port="${port:0:13}" >> + ip link del "${port}_l" 2>/dev/null || true >> + ip link del "${port}_c" 2>/dev/null || true >> + check ip link add "${port}_l" type veth peer name "${port}_c" >> + ip link set "${port}_l" up >> + ip link set "${port}_c" netns $pid name "$interface" >> + podman exec ovn-gw-1 ip link set "$interface" up >> + if [[ -n "$address" ]]; then >> + podman exec ovn-gw-1 ip addr add "$address" dev "$interface" >> + fi >> +} >> + >> prepare() { >> send_background_packets >> # We make sure gw1 is leader since enough time that it generated all >> its garps. >> @@ -3505,6 +3561,7 @@ check_migration_between_gw1_and_gw2_kill_gw2() { >> >> echo "$(date +%H:%M:%S.%03N) Restarting gw2 ovn-controller" >> m_as ovn-gw-2 /usr/share/ovn/scripts/ovn-ctl start_controller >> ${CONTROLLER_SSL_ARGS} >> + get_geneve_names_gw2 >> >> # The network is now restored => packets should go through gw1 and >> reach chassis-1. >> check_packets "true" "false" "false" "true" >> @@ -3543,12 +3600,130 @@ check_migration_between_gw1_and_gw2_kill_gw1() { >> >> echo "$(date +%H:%M:%S.%03N) Restarting gw1 ovn-controller after >> killing gw1" >> m_as ovn-gw-1 /usr/share/ovn/scripts/ovn-ctl start_controller >> ${CONTROLLER_SSL_ARGS} >> + get_geneve_names_gw1 >> >> # The network is now restored => packets should go through gw1 and >> reach chassis-1. >> check_packets "true" "false" "false" "true" >> final_check "kill_gw1" $max_expected_loss2 >> } >> >> +check_migration_between_gw1_and_gw2_reboot_gw1() { >> + AS_BOX([$(date +%H:%M:%S.%03N) Rebooting ovn-gw-1]) >> + max_expected_loss1=$1 >> + max_expected_loss2=$2 >> + prepare >> + >> + podman stop -t 0 ovn-gw-1 >> + (exec 3>&- 4>&- 5>&- 6>&-; podman start ovn-gw-1) >> + >> + # As ovn-gw-1 got stopped and restarted, its ports might get >> deleted. Add them back. >> + add_port br-ovn-ext eth2 >> + add_port br-ovn eth1 $gw1_eth1_ip_mask >> + >> + M_START_TCPDUMPS([ovn-gw-1], [-neei eth2], [gw1], [ovn-gw-1], [-neei >> eth2 -Q out], [gw1_out]) >> + check_loss_after_flap "gw1" $max_expected_loss1 >> + >> + # gw1 died => gw2 should generate garps. >> + check_garps "false" "true" "false" >> + check_packets "false" "true" "false" "true" >> + >> + start_openvswitch ovn-gw-1 >> + M_START_TCPDUMPS([ovn-gw-1], [-neei eth1], [gw1_eth1]) >> + >> + # Wait some long time before restarting ovn-controller >> + sleep 10 >> + >> + # gw2 should still be handling packets as OVN not restarted on gw1 >> + check_packets "false" "true" "false" "true" >> + >> + echo "$(date +%H:%M:%S.%03N) Restarting gw1 ovn-controller after >> rebooting gw1" >> + m_as ovn-gw-1 /usr/share/ovn/scripts/ovn-ctl start_controller >> ${CONTROLLER_SSL_ARGS} >> + get_geneve_names_gw1 >> + >> + # The network is now restored => packets should go through gw1 and >> reach chassis-1. >> + check_packets "true" "false" "false" "true" >> + final_check "reboot_gw1" $max_expected_loss2 >> +} >> + >> +check_compute_restart() { >> + AS_BOX([$(date +%H:%M:%S.%03N) Killing ovn-chassis-1 ovn-controller >> and ovs-vswitchd]) >> + max_expected_loss=$1 >> + prepare >> + >> + # Kill ovn-chassis-1 >> + echo "$(date +%H:%M:%S.%03N) Killing chassis-1" >> + on_exit 'm_as ovn-chassis-1 /usr/share/openvswitch/scripts/ovs-ctl >> status || >> + m_as ovn-chassis-1 /usr/share/openvswitch/scripts/ovs-ctl >> start --system-id=ovn-chassis-1' >> + on_exit 'm_as ovn-chassis-1 /usr/share/ovn/scripts/ovn-ctl >> status_controller || >> + m_as ovn-chassis-1 /usr/share/ovn/scripts/ovn-ctl >> start_controller ${CONTROLLER_SSL_ARGS}' >> + >> + m_as ovn-chassis-1 kill -9 $(m_as ovn-chassis-1 cat >> /run/ovn/ovn-controller.pid) >> + m_as ovn-chassis-1 kill -9 $(m_as ovn-chassis-1 cat >> /run/openvswitch/ovs-vswitchd.pid) >> + m_as ovn-chassis-1 kill -9 $(m_as ovn-chassis-1 cat >> /run/openvswitch/ovsdb-server.pid) >> + >> + # Now restart chassis-1 >> + flap_count=$(m_as ovn-gw-2 ovs-vsctl get interfac $from_gw2_to_ch1 >> bfd_status | sed 's/.*flap_count=\"\([[0-9]]*\).*/\1/g') >> + start_openvswitch ovn-chassis-1 >> + >> + echo "$(date +%H:%M:%S.%03N) Waiting for flap count between gw-1 and >> chassis-1 to increase" >> + OVS_WAIT_UNTIL([ >> + new_flap_count=$(m_as ovn-gw-1 ovs-vsctl get interfac >> $from_gw1_to_ch1 bfd_status | sed 's/.*flap_count=\"\([[0-9]]*\).*/\1/g') >> + echo "Comparing $new_flap_count versus $flap_count" >> + test "$new_flap_count" -gt "$((flap_count))" >> + ]) >> + >> + echo "$(date +%H:%M:%S.%03N) Restarting ovn-chassis-1 >> ovn-controller." >> + m_as ovn-chassis-1 /usr/share/ovn/scripts/ovn-ctl start_controller >> ${CONTROLLER_SSL_ARGS} >> + get_geneve_names_chassis1 >> + >> + wait_bfd_up ovn-chassis-1 $from_ch1_to_gw1 >> + >> + # Wait a long time to catch losses >> + sleep 5 >> + lost=0 >> + final_check "compute" $max_expected_loss >> +} >> + >> +check_update_ovn_ovs() { >> + chassis=$1 >> + ovn=$2 >> + bfd=$3 >> + max_expected_loss=$4 >> + >> + AS_BOX([$(date +%H:%M:%S.%03N) Check migration after restarting >> $chassis ovs-vswitchd $ovn]) >> + prepare >> + lost=0 >> + >> + echo "$(date +%H:%M:%S.%03N) Restarting OVS $ovn on $chassis" >> + if [[ "$ovn" == "ovn" ]]; then >> + m_as $chassis /usr/share/ovn/scripts/ovn-ctl stop_controller >> ${CONTROLLER_SSL_ARGS} --restart >> + m_as $chassis /usr/share/openvswitch/scripts/ovs-ctl restart >> --system-id=$chassis --no-ovs-vswitchd >> + m_as $chassis /usr/share/openvswitch/scripts/ovs-ctl restart >> --system-id=$chassis --no-ovsdb-server >> + m_as $chassis /usr/share/ovn/scripts/ovn-ctl start_controller >> ${CONTROLLER_SSL_ARGS} >> + else >> + m_as $chassis /usr/share/openvswitch/scripts/ovs-ctl restart >> --system-id=$chassis >> + fi >> + >> + if [[ "$bfd" == "bfd" ]]; then >> + for c in $from_gw1_to_gw2 $from_gw1_to_gw3 $from_gw1_to_ch1 >> $from_gw1_to_ch2; do >> + wait_bfd_up ovn-gw-1 $c >> + done >> + for c in $from_ch1_to_gw1 $from_ch1_to_gw2; do >> + wait_bfd_up ovn-chassis-1 $c >> + done >> + fi >> + # The network is now restored => packets should go through gw1 and >> reach chassis-1. >> + # When packet loss is expected (e.g. in HA) packet might temporarily >> go >> + # through gw2. Ignore this. >> + if [[ "$max_expected_loss" -gt 0 ]]; then >> + check_packets "true" "ignore" "false" "true" >> + final_check "ovs_update" $max_expected_loss >> + else >> + check_packets "true" "false" "false" "true" >> + final_check "ovs_update" $max_expected_loss >> + fi >> +} >> + >> start_tcpdump >> echo "$(date +%H:%M:%S.%03N) Sending packet from hv1-vif1(inside1) to >> ext1" >> M_NS_CHECK_EXEC([ovn-chassis-1], [hv1-vif1], [ping -c3 -q -i 0.1 >> 192.168.0.1 | FORMAT_PING], >> @@ -3579,7 +3754,32 @@ check_migration_between_gw1_and_gw2_bfd_stop 1 1 >> check_migration_between_gw1_and_gw2_kill_gw2 1 1 >> >> # We simulate restart of both OVS & OVN gw1. gw2 should take over. >> -check_migration_between_gw1_and_gw2_kill_gw1 400 200 >> +# Expect around 1500 msec (mult x min_rx) + 1000 (for sending GARP) drop >> time when gw1 dies. >> +check_migration_between_gw1_and_gw2_kill_gw1 300 100 >> + >> +# We simulate death of gw1. gw2 should take over. >> +check_migration_between_gw1_and_gw2_reboot_gw1 300 100 >> + >> +# We simulate ovs update on gw1. When ovs is stopped, flows should still >> be handled by Kernel datapath. >> +# When OVS is restarted, BFD should go down immediately, and gw2 might >> start handling packets. >> +check_update_ovn_ovs ovn-gw-1 "" bfd 300 >> +check_update_ovn_ovs ovn-gw-1 ovn bfd 300 >> +check_update_ovn_ovs ovn-chassis-1 "" bfd 300 >> +check_update_ovn_ovs ovn-chassis-1 ovn bfd 300 >> + >> +# We simulate restart of ovn-chassis-1. We expect up to 3 sec loss. >> +# 1 sec for chassis-1 to send Down, 1 sec for chassis-1 to send Init and >> 1 sec for gw1 to send up. >> +check_compute_restart 300 >> + >> +# Now change config tp L3 GW >> +check multinode_nbctl clear Logical_Router_Port R1_outside >> gateway_chassis >> +check multinode_nbctl set Logical_Router R1 options:chassis=ovn-gw-1 >> +check multinode_nbctl --wait=hv sync >> +m_check_row_count HA_Chassis_Group 0 name=R1_outside >> +check_update_ovn_ovs ovn-gw-1 "" "" 0 >> +check_update_ovn_ovs ovn-chassis-1 "" "" 0 >> +check_update_ovn_ovs ovn-gw-1 ovn "" 0 >> +check_update_ovn_ovs ovn-chassis-1 ovn "" 0 >> >> AT_CLEANUP >> >> diff --git a/tests/ovn-controller.at b/tests/ovn-controller.at >> index e17ebea76..a7b79fc67 100644 >> --- a/tests/ovn-controller.at >> +++ b/tests/ovn-controller.at >> @@ -4239,3 +4239,60 @@ done >> OVN_CLEANUP([hv1]) >> AT_CLEANUP >> ]) >> + >> +OVN_FOR_EACH_NORTHD([ >> +AT_SETUP([ovn-controller - transient flag for HA chassis]) >> +AT_KEYWORDS([ovn]) >> +ovn_start >> + >> +net_add n1 >> +sim_add hv1 >> +as hv1 >> +check ovs-vsctl add-br br-phys >> +ovn_attach n1 br-phys 192.168.0.1 >> + >> +sim_add hv2 >> +as hv2 >> +check ovs-vsctl add-br br-phys >> +ovn_attach n1 br-phys 192.168.0.2 >> + >> +check ovn-nbctl ls-add ls1 >> +check ovn-nbctl lr-add lr1 >> +check ovn-nbctl lrp-add lr1 lr1-ls1 00:00:00:00:00:01 10.0.0.1/24 >> +check ovn-nbctl lsp-add ls1 ls1-lr1 -- lsp-set-type ls1-lr1 router \ >> + -- lsp-set-addresses ls1-lr1 router -- lsp-set-options ls1-lr1 >> router-port=lr1-ls1 >> + >> +OVS_WAIT_UNTIL([as hv1 ovs-vsctl get interface ovn-hv2-0 type]) >> +OVS_WAIT_UNTIL([as hv2 ovs-vsctl get interface ovn-hv1-0 type]) >> + >> +# Verify tunnel ports do not have transient flag set. >> +AT_CHECK([as hv1 ovs-vsctl --bare get port ovn-hv2-0 other_config], [0], >> [dnl >> +{} >> +]) >> +AT_CHECK([as hv2 ovs-vsctl --bare get port ovn-hv1-0 other_config], [0], >> [dnl >> +{} >> +]) >> + >> +check ovn-nbctl ha-chassis-group-add hagrp >> +check ovn-nbctl ha-chassis-group-add-chassis hagrp hv1 10 >> +check ovn-nbctl ha-chassis-group-add-chassis hagrp hv2 20 >> + >> +hagrp_uuid=$(ovn-nbctl --bare --columns _uuid find ha_chassis_group >> name=hagrp) >> +check ovn-nbctl set logical_router_port lr1-ls1 >> ha_chassis_group=$hagrp_uuid >> + >> +wait_row_count HA_Chassis_Group 1 name=hagrp >> +wait_row_count HA_Chassis 2 >> + >> +# Verify tunnel ports now have transient=true. >> +OVS_WAIT_UNTIL([test "$(as hv1 ovs-vsctl get port ovn-hv2-0 >> other_config:transient)" = '"true"']) >> +OVS_WAIT_UNTIL([test "$(as hv2 ovs-vsctl get port ovn-hv1-0 >> other_config:transient)" = '"true"']) >> + >> +check ovn-nbctl ha-chassis-group-remove-chassis hagrp hv2 >> + >> +# Verify transient flag is cleared on both HVs. >> +OVS_WAIT_UNTIL([test "$(as hv1 ovs-vsctl --bare get port ovn-hv2-0 >> other_config)" = '{}']) >> +OVS_WAIT_UNTIL([test "$(as hv2 ovs-vsctl --bare get port ovn-hv1-0 >> other_config)" = '{}']) >> + >> +OVN_CLEANUP([hv1],[hv2]) >> +AT_CLEANUP >> +]) >> -- >> 2.47.1 >> >> _______________________________________________ >> dev mailing list >> [email protected] >> https://mail.openvswitch.org/mailman/listinfo/ovs-dev >> >> > Regards, > Ales > Thanks Xavier _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
