Hi Dumitru

Thanks again for looking at this.

On Fri, Mar 27, 2026 at 12:38 PM Dumitru Ceara <[email protected]> wrote:

> On 3/26/26 5:16 PM, Xavier Simonart wrote:
> > If a server unexpectedly rebooted, OVS, when restarted, sets BFD
> > UP on bfd-enabled geneve tunnels.
> > However, if it takes time to restart OVN, an HA gw chassis
> > would attract the traffic while being unable to handle it
> > (as no flows), resulting in traffic loss.
> >
> > This is fixed by re-using ovs flow-restore-wait.
> > If set, OVS waits (prevents upcalls, ignores bfd, ...) until reset.
> > Once OVS receives the notification of flow-restore-wait being false,
> > it restarts handling upcalls, bfd... and ignores any new change to
> > flow-restore-wait.
> >
> > Hence, on chassis hosting ha gateways, OVN toggles flow-restore-wait:
> > it set it to false, waits for ack from OVS and then sets it back to true.
> > If server reboots, OVS will see flow-restore-wait being true.
> >
> > OVN also sets external_ids->ovn-managed-flow-restore-wait when setting
> > flow-restore-wait. When set, it tells that OVN once set
> flow-restore-wait.
> >
> > "ovs-ctl restart" also uses flow-restore-wait: when called, it saves the
> > flows, stops "ovs-vswitchd", sets "flow-restore-wait" to true, restarts
> > "ovs-vswitchd", restores the flows and finally removes
> "flow-restore-wait".
> > So OVS will wait either for "ovs-ctl restart" to remove
> "flow-restore-wait"
> > or for OVN to set "flow-restore-wait" to false.
> >
> > Reported-at: https://issues.redhat.com/browse/FDP-3075
> > Signed-off-by: Xavier Simonart <[email protected]>
> >
> > ---
> > -v2 : - Updated based on Mark's feedback (commit message, comments).
> >       - Avoid setting flow-restore-wait for computes.
> >       - Add external_ids->ovn-managed-flow-restore-wait.
> >       - Updated test: add test for compute update + nits (variable name
> changes)
> > -v3 : - Fix test failing on CI (using wrong host socket)
> >       - Address nits from Dumitru + few more nits (related to comments).
> >       - Increased tolerated loss (and add comment) to avoid flakiness.
> >       - Rebased.
> > ---
>
> Hi Xavier,
>
> Thanks for the v3!
>
> >  controller/bfd.c                |   5 +-
> >  controller/bfd.h                |   4 +-
> >  controller/ovn-controller.8.xml |  11 +
> >  controller/ovn-controller.c     | 173 +++++++++-
> >  tests/multinode-macros.at       |  46 +++
> >  tests/multinode.at              | 548 ++++++++++++++++++++++----------
> >  6 files changed, 612 insertions(+), 175 deletions(-)
> >
> > diff --git a/controller/bfd.c b/controller/bfd.c
> > index 3b0c3f6da..56bfa4936 100644
> > --- a/controller/bfd.c
> > +++ b/controller/bfd.c
> > @@ -117,13 +117,14 @@ bfd_calculate_active_tunnels(const struct
> ovsrec_bridge *br_int,
> >   *
> >   * If 'our_chassis' is C5 then this function returns empty bfd set.
> >   */
> > -void
> > +bool
> >  bfd_calculate_chassis(
> >      const struct sbrec_chassis *our_chassis,
> >      const struct sbrec_ha_chassis_group_table *ha_chassis_grp_table,
> >      struct sset *bfd_chassis)
> >  {
> >      const struct sbrec_ha_chassis_group *ha_chassis_grp;
> > +    bool chassis_is_ha_gw = false;
> >      SBREC_HA_CHASSIS_GROUP_TABLE_FOR_EACH (ha_chassis_grp,
> >                                             ha_chassis_grp_table) {
> >          bool is_ha_chassis = false;
> > @@ -143,6 +144,7 @@ bfd_calculate_chassis(
> >              sset_add(&grp_chassis, ha_ch->chassis->name);
> >              if (our_chassis == ha_ch->chassis) {
> >                  is_ha_chassis = true;
> > +                chassis_is_ha_gw = true;
> >                  bfd_setup_required = true;
> >              }
> >          }
> > @@ -178,6 +180,7 @@ bfd_calculate_chassis(
> >          }
> >          sset_destroy(&grp_chassis);
> >      }
> > +    return chassis_is_ha_gw;
> >  }
> >
> >  void
> > diff --git a/controller/bfd.h b/controller/bfd.h
> > index f8fece5a5..3e3384891 100644
> > --- a/controller/bfd.h
> > +++ b/controller/bfd.h
> > @@ -16,6 +16,8 @@
> >  #ifndef OVN_BFD_H
> >  #define OVN_BFD_H 1
> >
> > +#include <stdbool.h>
> > +
> >  struct hmap;
> >  struct ovsdb_idl;
> >  struct ovsdb_idl_index;
> > @@ -36,7 +38,7 @@ void bfd_run(const struct ovsrec_interface_table *,
> >               const struct sbrec_sb_global_table *,
> >               const struct ovsrec_open_vswitch_table *);
> >
> > -void bfd_calculate_chassis(
> > +bool bfd_calculate_chassis(
> >      const struct sbrec_chassis *,
> >      const struct sbrec_ha_chassis_group_table *,
> >      struct sset *);
> > diff --git a/controller/ovn-controller.8.xml
> b/controller/ovn-controller.8.xml
> > index 57e7cf5dd..33281a4d6 100644
> > --- a/controller/ovn-controller.8.xml
> > +++ b/controller/ovn-controller.8.xml
> > @@ -531,6 +531,17 @@
> >          65535.
> >        </dd>
> >
> > +      <dt>
> > +        <code>external_ids:ovn-managed-flow-restore-wait</code> in the
> > +        <code>Open_vSwitch</code> table
> > +      </dt>
> > +      <dd>
> > +        When set to true, this key indicates that
> <code>ovn-controller</code>
> > +        has set the <code>other_config:flow-restore-wait</code> option.
> > +        The key is set when <code>ovn-controller</code> enables
> > +        flow-restore-wait and removed when it clears it.
> > +      </dd>
> > +
> >        <dt>
> >          <code>external_ids:ct-zone-*</code> in the <code>Bridge</code>
> table
> >        </dt>
> > diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
> > index 4161fe2b3..da43051ed 100644
> > --- a/controller/ovn-controller.c
> > +++ b/controller/ovn-controller.c
> > @@ -211,6 +211,152 @@ static char *get_file_system_id(void)
> >      free(filename);
> >      return ret;
> >  }
> > +
> > +/* Set/unset flow-restore-wait, and inc ovs next_cfg if false
> > + * When set to true, also sets ovn-managed-flow-restore-wait to true to
> > + * indicate ownership */
> > +static void
> > +set_flow_restore_wait(struct ovsdb_idl_txn *ovs_idl_txn,
> > +                      const struct ovsrec_open_vswitch *cfg,
> > +                      const struct smap *other_config,
> > +                      const bool val, bool ovn_managed)
> > +{
> > +    struct smap new_config;
> > +    smap_clone(&new_config, other_config);
> > +    smap_replace(&new_config, "flow-restore-wait", val ? "true":
> "false");
> > +    ovsrec_open_vswitch_set_other_config(cfg, &new_config);
> > +    if (val) {
> > +        ovsrec_open_vswitch_update_external_ids_setkey(
> > +            cfg, "ovn-managed-flow-restore-wait", "true");
> > +    } else if (ovn_managed) {
> > +        ovsrec_open_vswitch_update_external_ids_delkey(
> > +            cfg, "ovn-managed-flow-restore-wait");
> > +    }
> > +    ovsdb_idl_txn_increment(ovs_idl_txn, &cfg->header_,
> > +                            &ovsrec_open_vswitch_col_next_cfg, true);
> > +    smap_destroy(&new_config);
> > +}
> > +
> > +static void
> > +manage_flow_restore_wait(struct ovsdb_idl_txn *ovs_idl_txn,
> > +                         const struct ovsrec_open_vswitch *cfg,
> > +                         uint64_t ofctrl_cur_cfg, uint64_t ovs_next_cfg,
> > +                         int ovs_txn_status, bool is_ha_gw)
> > +{
> > +    enum flow_restore_wait_state {
> > +        FRW_INIT,              /* Initial state */
> > +        FRW_WAIT_TXN_COMPLETE, /* Sent false, waiting txn to complete */
> > +        FRW_TXN_SUCCESS,       /* Txn completed. Waiting for OVS Ack. */
> > +        FRW_DONE               /* Everything completed */
> > +    };
> > +
> > +    static int64_t frw_next_cfg;
> > +    static enum flow_restore_wait_state frw_state;
> > +    static bool ofctrl_was_connected = false;
> > +
> > +    bool ofctrl_connected = ofctrl_is_connected();
> > +
> > +    if (!ovs_idl_txn || !cfg) {
> > +        return;
> > +    }
> > +
> > +    /* If OVS is stopped/started, make sure flow-restore-wait is
> toggled. */
> > +    if (ofctrl_connected && !ofctrl_was_connected) {
> > +        frw_state = FRW_INIT;
> > +    }
> > +    ofctrl_was_connected = ofctrl_connected;
> > +
> > +    if (!ofctrl_connected) {
> > +        return;
> > +    }
> > +
> > +    bool frw = smap_get_bool(&cfg->other_config, "flow-restore-wait",
> false);
> > +    bool ovn_managed_once = smap_get_bool(&cfg->external_ids,
> > +
> "ovn-managed-flow-restore-wait",
> > +                                          false);
> > +
> > +    if (frw && !ovn_managed_once) {
> > +        /* frw has been set by ovs-ctl. Do not touch. */
> > +        return;
> > +    }
> > +
> > +    if (!is_ha_gw) {
> > +        if (frw) {
> > +            /* frw has once been set by OVN. We are now not an HA
> chassis
> > +             * anymore, unset it. */
> > +            set_flow_restore_wait(ovs_idl_txn, cfg, &cfg->other_config,
> > +                                  false, ovn_managed_once);
> > +        }
> > +        /* else we are not an HA chassis and frw is false. Ignore it. */
> > +        return;
> > +    }
> > +
> > +    switch (frw_state) {
> > +    case FRW_INIT:
> > +        if (ofctrl_cur_cfg > 0) {
> > +            set_flow_restore_wait(ovs_idl_txn, cfg, &cfg->other_config,
> > +                                  false, ovn_managed_once);
> > +            frw_state = FRW_WAIT_TXN_COMPLETE;
> > +            VLOG_INFO("Setting flow-restore-wait=false "
> > +                      "(cur_cfg=%"PRIu64")", ofctrl_cur_cfg);
> > +        }
> > +        break;
> > +
> > +    case FRW_WAIT_TXN_COMPLETE:
> > +        /* if (ovs_idl_txn != NULL), the transaction completed.
> > +         * When the transaction completed, it either failed
> > +         * (ovs_txn_status == 0) or succeeded (ovs_txn_status != 0). */
> > +        if (ovs_txn_status == 0) {
> > +            /* Previous transaction failed. */
> > +            set_flow_restore_wait(ovs_idl_txn, cfg, &cfg->other_config,
> > +                                  false, ovn_managed_once);
> > +            break;
> > +        }
> > +        /* txn succeeded, get next_cfg */
> > +        frw_next_cfg = ovs_next_cfg;
> > +        frw_state = FRW_TXN_SUCCESS;
> > +        /* fall through */
>
> I also asked a helpful assistant :) (gemini-3) to review this code and
> it complained here with:
>
> "I think there is a potential issue here when `ovs_txn_status` is `-1`.
> In `main()`, `manage_flow_restore_wait` is called only when
> `ovs_idl_txn` is NOT NULL.
>
> If `ovs_txn_status` was `-1` in the previous iteration, it means we were
> waiting for a transaction.
>
> If `ovs_idl_txn` is now NOT NULL, it means `ovsdb_idl_loop_run()`
> processed the completion of that transaction.
>
> However, `ovs_txn_status` still holds `-1` because it's only updated at
> the end of the loop by `ovsdb_idl_loop_commit_and_wait()`.
>
> The current logic treats `-1` as success (since it's not `0`). If the
> transaction actually failed, we might move to `FRW_TXN_SUCCESS`
> prematurely.
>
> Should we wait for `ovs_txn_status` to be something other than `-1`? Or
> can we reliably determine success/failure from the IDL state?"
> "
>
> This does sound legit to me, ovs_txn_status may still be -1 because when
> we tried to commit frw=false we couldn't immediately asses whether the
> transaction would succeed or not (in most cases that's what happens) but
> then the transaction failed for some reason on the server side.
>
> At this point, in the new loop, "ovs_idl_txn" is not NULL because we can
> try a new transaction (nothing is in flight anymore) but we assume the
> previous one succeeded.
>
> Then we fall through..
>
> > +
> > +    case FRW_TXN_SUCCESS:
> > +        if (ovs_next_cfg < frw_next_cfg) {
> > +            /* DB was reset, next_cfg went backwards. */
> > +            VLOG_INFO("OVS DB reset (next_cfg %"PRId64" -> %"PRIu64"), "
> > +                      "resetting state",
> > +                      frw_next_cfg, ovs_next_cfg);
> > +            set_flow_restore_wait(ovs_idl_txn, cfg, &cfg->other_config,
> > +                                  false, ovn_managed_once);
> > +            frw_state = FRW_WAIT_TXN_COMPLETE;
> > +            break;
> > +        }
> > +
>
> frw is still "true" because we failed to set it to "false" (the
> transaction failed server side).
>
> > +        if (!frw) {
> > +            if (cfg->cur_cfg >= frw_next_cfg) {
> > +                set_flow_restore_wait(ovs_idl_txn, cfg,
> &cfg->other_config,
> > +                                      true, ovn_managed_once);
> > +                frw_state = FRW_DONE;
> > +                VLOG_INFO("Setting flow-restore-wait=true");
> > +            }
> > +        } else {
>
> So we enter here where we assume "another task already set it to true".
>
> But it was actually just never changed.
>
> Is this analysis correct?  Do we need a way to get the ovs_txn_status in
> the beginning of the current loop iteration?
>
I do not think so because of how  ovsdb_idl_loop_runs.
Loop 1
----------
- We try to set frw to false
- We commit (at the end of the loop, ovsdb_idl_loop_commit_and_wait)
- We have txn_status = -1 (in_progress).

Loop 2
----------
- The txn fails in ovsdb_idl_loop_run.
- Looking at ovsdb_idl_loop_run:
  - We call ovsdb_idl_run (which gets the status)
  - The txn failed, so commiting_txn != NULL.
  - We do not ovsdb_idl_try_commit_loop_txn (which would set committing_txn
to NULL) as txn failed.
  - So loop->open_txn is NULL in this loop
  - We do not see the txn as completed in this loop.

End of the loop 2 (ovsdb_idl_loop_commit_and_wait)
- txn_status is set to 0.
- loop->committing_txn is set to NULL

Loop 3
----------
- We have a txn
- We still have status == 0, so in FRW_WAIT_TXN_COMPLETE we handle the
failure.

So, it looks OK to me

>
> > +            /* The transaction to false succeeded but frw is true.
> > +             * So, another task already set it to true. */
> > +            frw_state = FRW_DONE;
> > +            VLOG_INFO("flow-restore-wait was already true");
> > +        }
> > +        break;
> > +    case FRW_DONE:
> > +        if (!frw) {
> > +            /* frw has been removed (e.g. by ovs-ctl restart) or is
> false
> > +             * (e.g. txn failed.) */
> > +            set_flow_restore_wait(ovs_idl_txn, cfg, &cfg->other_config,
> > +                                  false, ovn_managed_once);
> > +            frw_state = FRW_WAIT_TXN_COMPLETE;
> > +            VLOG_INFO("OVS frw cleared, restarting flow-restore-wait
> sequence "
> > +                      "(cur_cfg=%"PRIu64")", ofctrl_cur_cfg);
> > +        }
> > +        break;
> > +    }
> > +}
> > +
> >  /* Only set monitor conditions on tables that are available in the
> >   * server schema.
> >   */
> > @@ -3381,6 +3527,7 @@ en_mac_cache_cleanup(void *data)
> >
> >  struct ed_type_bfd_chassis {
> >      struct sset bfd_chassis;
> > +    bool is_ha_gw;
> >  };
> >
> >  static void *
> > @@ -3409,8 +3556,9 @@ en_bfd_chassis_run(struct engine_node *node, void
> *data OVS_UNUSED)
> >          = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
> >
> >      sset_clear(&bfd_chassis->bfd_chassis);
> > -    bfd_calculate_chassis(chassis, ha_chassis_grp_table,
> > -                          &bfd_chassis->bfd_chassis);
> > +    bfd_chassis->is_ha_gw = bfd_calculate_chassis(chassis,
> > +                                                  ha_chassis_grp_table,
> > +
> &bfd_chassis->bfd_chassis);
> >      return EN_UPDATED;
> >  }
> >
> > @@ -7117,6 +7265,7 @@ main(int argc, char *argv[])
> >      struct unixctl_server *unixctl;
> >      struct ovn_exit_args exit_args = {0};
> >      struct br_int_remote br_int_remote = {0};
> > +    static uint64_t next_cfg = 0;
> >      int retval;
> >
> >      /* Read from system-id-override file once on startup. */
> > @@ -7444,6 +7593,7 @@ main(int argc, char *argv[])
> >
> >      /* Main loop. */
> >      int ovnsb_txn_status = 1;
> > +    int ovs_txn_status = 1;
> >      bool sb_monitor_all = false;
> >      struct tracked_acl_ids *tracked_acl_ids = NULL;
> >      while (!exit_args.exiting) {
> > @@ -7545,6 +7695,11 @@ main(int argc, char *argv[])
> >          pinctrl_update_swconn(br_int_remote.target,
> >                                br_int_remote.probe_interval);
> >
> > +        if (cfg && ovs_idl_txn && ovs_txn_status == -1) {
> > +            /* txn was in progress and is now completed */
> > +            next_cfg = cfg->next_cfg;
> > +        }
> > +
> >          /* Enable ACL matching for double tagged traffic. */
> >          if (ovs_idl_txn && cfg) {
> >              int vlan_limit = smap_get_int(
> > @@ -7894,6 +8049,13 @@ main(int argc, char *argv[])
> >                      stopwatch_start(OFCTRL_SEQNO_RUN_STOPWATCH_NAME,
> >                                      time_msec());
> >                      ofctrl_seqno_run(ofctrl_get_cur_cfg());
> > +                    if (ovs_idl_txn && bfd_chassis_data) {
> > +                        manage_flow_restore_wait(ovs_idl_txn, cfg,
> > +                                                 ofctrl_get_cur_cfg(),
> > +                                                 next_cfg,
> ovs_txn_status,
> > +
>  bfd_chassis_data->is_ha_gw);
> > +                    }
> > +
> >                      stopwatch_stop(OFCTRL_SEQNO_RUN_STOPWATCH_NAME,
> >                                     time_msec());
> >                      stopwatch_start(IF_STATUS_MGR_RUN_STOPWATCH_NAME,
> > @@ -7993,7 +8155,7 @@ main(int argc, char *argv[])
> >              OVS_NOT_REACHED();
> >          }
> >
> > -        int ovs_txn_status =
> ovsdb_idl_loop_commit_and_wait(&ovs_idl_loop);
> > +        ovs_txn_status = ovsdb_idl_loop_commit_and_wait(&ovs_idl_loop);
> >          if (!ovs_txn_status) {
> >              /* The transaction failed. */
> >              vif_plug_clear_deleted(
> > @@ -8012,6 +8174,9 @@ main(int argc, char *argv[])
> >                      &vif_plug_deleted_iface_ids);
> >              vif_plug_finish_changed(
> >                      &vif_plug_changed_iface_ids);
> > +            if (cfg) {
> > +                next_cfg = cfg->next_cfg;
> > +            }
> >          } else if (ovs_txn_status == -1) {
> >              /* The commit is still in progress */
> >          } else {
> > @@ -8085,7 +8250,7 @@ loop_done:
> >              }
> >
> >              ovsdb_idl_loop_commit_and_wait(&ovnsb_idl_loop);
> > -            int ovs_txn_status =
> ovsdb_idl_loop_commit_and_wait(&ovs_idl_loop);
> > +            ovs_txn_status =
> ovsdb_idl_loop_commit_and_wait(&ovs_idl_loop);
> >              if (!ovs_txn_status) {
> >                  /* The transaction failed. */
> >                  vif_plug_clear_deleted(
> > diff --git a/tests/multinode-macros.at b/tests/multinode-macros.at
> > index 4a74d5127..596de4c3a 100644
> > --- a/tests/multinode-macros.at
> > +++ b/tests/multinode-macros.at
> > @@ -41,6 +41,28 @@ m4_define([M_START_TCPDUMP],
> >      ]
> >  )
> >
> > +m4_define([M_START_TCPDUMPS_RECURSIVE_], [
> > +     m4_if(m4_eval($# > 3), [1], [dnl
> > +        names="$names $3"
> > +        echo "Running podman exec $1 tcpdump -l $2 >$3.tcpdump
> 2>$3.stderr"
> > +        podman exec $1 tcpdump -l $2 >$3.tcpdump 2>$3.stderr &
> > +        echo "podman exec $1 ps -ef | grep -v grep | grep tcpdump &&
> podman exec $1 killall tcpdump" >> cleanup
> > +        M_START_TCPDUMPS_RECURSIVE_(m4_shift(m4_shift(m4_shift($@))))
> > +        ])
> > +    ]
> > +)
> > +
> > +# Start Multiple tcpdump. Useful to speed up when many tcpdump
> > +# must be started as waiting for "listening" takes usually 1 second.
> > +m4_define([M_START_TCPDUMPS],
> > +    [
> > +     names=""
> > +     M_START_TCPDUMPS_RECURSIVE_($@)
> > +     for name in $names; do
> > +         OVS_WAIT_UNTIL([grep -q "listening" ${name}.stderr])
> > +     done
> > +    ]
> > +)
> >
> >  # M_FORMAT_CT([ip-addr])
> >  #
> > @@ -480,6 +502,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 bc1eb7a7c..c2587b68a 100644
> > --- a/tests/multinode.at
> > +++ b/tests/multinode.at
> > @@ -2986,42 +2986,42 @@ AT_CLEANUP
> >
> >  AT_SETUP([HA: Check for missing garp on leader when BFD goes back up])
> >  # Network topology
> > -#
> ┌────────────────────────────────────────────────────────────────────────────────────────────────────────┐
> > -#    │
>                                       │
> > -#    │    ┌───────────────────┐    ┌───────────────────┐
> ┌───────────────────┐    ┌───────────────────┐    │
> > -#    │    │   ovn-chassis-1   │    │  ovn-gw-1         │    │
> ovn-gw-2         │    │  ovn-chassis-2    │    │
> > -#    │    └─────────┬─────────┘    └───────────────────┘
> └───────────────────┘    └───────────────────┘    │
> > -#    │    ┌─────────┴─────────┐
>                                        │
> > -#    │    │       inside1     │
>                                        │
> > -#    │    │   192.168.1.1/24  │
>                                        │
> > -#    │    └─────────┬─────────┘
>                                        │
> > -#    │    ┌─────────┴─────────┐
>                                        │
> > -#    │    │       inside      │
>                                        │
> > -#    │    └─────────┬─────────┘
>                                        │
> > -#    │    ┌─────────┴─────────┐
>                                        │
> > -#    │    │    192.168.1.254  │
>                                        │
> > -#    │    │         R1        │
>                                        │
> > -#    │    │    192.168.0.254  │
>                                        │
> > -#    │    └─────────┬─────────┘
>                                        │
> > -#    │
> └------eth1---------------┬--------eth1-----------┐
>                │
> > -#    │                             ┌──────────┴────────┐
> ┌─────────┴─────────┐                             │
> > -#    │                             │    192.168.1.254  │    │
>  192.168.1.254   │                             │
> > -#    │                             │         R1        │    │
>  R1        │                             │
> > -#    │                             │    192.168.0.254  │    │
>  192.168.0.254   │                             │
> > -#    │                             └─────────┬─────────┘
> └─────────┬─────────┘                             │
> > -#    │                                       │
> │              ┌───────────────────┐    │
> > -#    │                             ┌─────────┴─────────┐
> ┌─────────┴─────────┐    │    192.168.0.1    │    │
> > -#    │                             │       outside     │    │
>  outside     │    │        ext1       │    │
> > -#    │                             └─────────┬─────────┘
> └─────────┬─────────┘    └─────────┬─────────┘    │
> > -#    │                             ┌─────────┴─────────┐
> ┌─────────┴─────────┐    ┌─────────┴─────────┐    │
> > -#    │                             │    ln-outside     │    │
> ln-outside     │    │       ln-ext1     │    │
> > -#    │                             └─────────┬─────────┘
> └─────────┬─────────┘    └─────────┬─────────┘    │
> > -#    │                             ┌─────────┴─────────┐
> ┌─────────┴─────────┐    ┌─────────┴─────────┐    │
> > -#    │                             │       br-ex       │    │
>  br-ex       │    │       br-ex       │    │
> > -#    │                             └─────────┬─────────┘
> └─────────┬─────────┘    └─────────┬─────────┘    │
> > -#    │
>  └---------eth2-----------┴-------eth2-------------┘              │
> > -#    │
>                                       │
> > -#
> └────────────────────────────────────────────────────────────────────────────────────────────────────────┘
> > +#
> ┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
> > +#    │
>                                                               │
> > +#    │   ┌───────────────────┐    ┌───────────────────┐
> ┌───────────────────┐    ┌───────────────────┐    ┌───────────────────┐    │
> > +#    │   │   ovn-chassis-1   │    │   ovn-chassis-2   │    │  ovn-gw-1
>        │    │  ovn-gw-2         │    │  ovn-chassis-3    │    │
> > +#    │   └─────────┬─────────┘    └─────────┬─────────┘
> └───────────────────┘    └───────────────────┘    └───────────────────┘    │
> > +#    │   ┌─────────┴─────────┐    ┌─────────┴─────────┐
>                                                                │
> > +#    │   │       inside1     │    │       inside2     │
>                                                                │
> > +#    │   │   192.168.1.1/24  │    │   192.168.1.2/24  │
>                                                                │
> > +#    │   └─────────┬─────────┘    └─────────┬─────────┘
>                                                                │
> > +#    │           ┌─┴────────────────────────┴─┐
>                                                                │
> > +#    │           │           inside           │
>                                                                │
> > +#    │           └──────────────┬─────────────┘
>                                                                │
> > +#    │                ┌─────────┴─────────┐
>                                                                │
> > +#    │                │    192.168.1.254  │
>                                                                │
> > +#    │                │         R1        │
>                                                                │
> > +#    │                │    192.168.0.254  │
>                                                                │
> > +#    │                └─────────┬─────────┘
>                                                                │
> > +#    │
> └------eth1---------------------------┬--------eth1-----------┐
>                            │
> > +#    │
>  ┌──────────┴────────┐    ┌─────────┴─────────┐
>  │
> > +#    │                                                     │
> 192.168.1.254  │    │   192.168.1.254   │                             │
> > +#    │                                                     │
>  R1        │    │         R1        │                             │
> > +#    │                                                     │
> 192.168.0.254  │    │   192.168.0.254   │                             │
> > +#    │
>  └─────────┬─────────┘    └─────────┬─────────┘
>  │
> > +#    │                                                               │
>                       │              ┌───────────────────┐    │
> > +#    │
>  ┌─────────┴─────────┐    ┌─────────┴─────────┐    │    192.168.0.1    │
> │
> > +#    │                                                     │
>  outside     │    │       outside     │    │        ext1       │    │
> > +#    │
>  └─────────┬─────────┘    └─────────┬─────────┘    └─────────┬─────────┘
> │
> > +#    │
>  ┌─────────┴─────────┐    ┌─────────┴─────────┐    ┌─────────┴─────────┐
> │
> > +#    │                                                     │
> ln-outside     │    │    ln-outside     │    │       ln-ext1     │    │
> > +#    │
>  └─────────┬─────────┘    └─────────┬─────────┘    └─────────┬─────────┘
> │
> > +#    │
>  ┌─────────┴─────────┐    ┌─────────┴─────────┐    ┌─────────┴─────────┐
> │
> > +#    │                                                     │
>  br-ex       │    │       br-ex       │    │       br-ex       │    │
> > +#    │
>  └─────────┬─────────┘    └─────────┬─────────┘    └─────────┬─────────┘
> │
> > +#    │
>  └---------eth2-----------┴-------eth2-------------┘              │
> > +#    │
>                                                               │
> > +#
> └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
> >
> >  # The goal of this test is the check that GARP are properly generated
> by higest priority traffic when
> >  # BFD goes down, and back up, and this whether the BFD event is due
> either to some bfd packet lost
> > @@ -3030,6 +3030,12 @@ AT_SETUP([HA: Check for missing garp on leader
> when BFD goes back up])
> >  # So gw3 should in this test neither send garp or receive packets.
> >  #
> >  # Enable vconn so we can check the GARP from a log perspective.
> > +on_exit "podman exec ovn-gw-1 ovn-appctl vlog/set info"
> > +on_exit "podman exec ovn-gw-1 ovn-appctl vlog/enable-rate-limit"
> > +on_exit "podman exec ovn-gw-2 ovn-appctl vlog/set info"
> > +on_exit "podman exec ovn-gw-2 ovn-appctl vlog/enable-rate-limit"
> > +on_exit "podman exec ovn-gw-3 ovn-appctl vlog/set info"
> > +on_exit "podman exec ovn-gw-3 ovn-appctl vlog/enable-rate-limit"
> >  m_as ovn-gw-1 ovn-appctl vlog/set vconn:dbg
> >  m_as ovn-gw-2 ovn-appctl vlog/set vconn:dbg
> >  m_as ovn-gw-3 ovn-appctl vlog/set vconn:dbg
> > @@ -3037,12 +3043,17 @@ 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.
> >  cleanup_multinode_resources
> >
> >  ip_ch1=$(m_as ovn-chassis-1 ip a show dev eth1 | grep "inet " | awk
> '{print $2}'| cut -d '/' -f1)
> > +ip_ch2=$(m_as ovn-chassis-2 ip a show dev eth1 | grep "inet " | awk
> '{print $2}'| cut -d '/' -f1)
> >  ip_gw1=$(m_as ovn-gw-1 ip a show dev eth1 | grep "inet " | awk '{print
> $2}'| cut -d '/' -f1)
> >  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)
> > @@ -3050,25 +3061,35 @@ ip_gw3=$(m_as ovn-gw-3 ip a show dev eth1 | grep
> "inet " | awk '{print $2}'| cut
> >  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)
> >
> >  m_as ovn-chassis-1 ip link del hv1-vif1-p
> > -m_as ovn-chassis-2 ip link del ext1-p
> > +m_as ovn-chassis-2 ip link del hv2-vif1-p
> > +m_as ovn-chassis-3 ip link del ext1-p
> >
> >  OVS_WAIT_UNTIL([m_as ovn-chassis-1 ip link show | grep -q genev_sys])
> >  OVS_WAIT_UNTIL([m_as ovn-chassis-2 ip link show | grep -q genev_sys])
> > +OVS_WAIT_UNTIL([m_as ovn-chassis-3 ip link show | grep -q genev_sys])
> >  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
> >  check multinode_nbctl lsp-add inside inside1 -- lsp-set-addresses
> inside1 "f0:00:c0:a8:01:01 192.168.1.1"
> > +check multinode_nbctl lsp-add inside inside2 -- lsp-set-addresses
> inside2 "f0:00:c0:a8:01:02 192.168.1.2"
> >  check multinode_nbctl lsp-add ext ext1 -- lsp-set-addresses ext1
> "00:00:c0:a8:00:01 192.168.0.1"
> >
> >  multinode_nbctl create Logical_Router name=R1
> > @@ -3100,12 +3121,14 @@ m_as ovn-gw-3 ovs-vsctl remove open .
> external_ids garp-max-timeout-sec
> >
> >  m_as ovn-chassis-1 ovs-vsctl set open .
> external-ids:ovn-bridge-mappings=public:br-ex
> >  m_as ovn-chassis-2 ovs-vsctl set open .
> external-ids:ovn-bridge-mappings=public:br-ex
> > +m_as ovn-chassis-3 ovs-vsctl set open .
> external-ids:ovn-bridge-mappings=public:br-ex
> >  m_as ovn-gw-1 ovs-vsctl set open .
> external-ids:ovn-bridge-mappings=public:br-ex
> >  m_as ovn-gw-2 ovs-vsctl set open .
> external-ids:ovn-bridge-mappings=public:br-ex
> >  m_as ovn-gw-3 ovs-vsctl set open .
> external-ids:ovn-bridge-mappings=public:br-ex
> >
> >  m_as ovn-chassis-1 /data/create_fake_vm.sh inside1 hv1-vif1
> f0:00:c0:a8:01:01 1500 192.168.1.1 24 192.168.1.254 2000::1/64 2000::a
> > -m_as ovn-chassis-2 /data/create_fake_vm.sh ext1 ext1 00:00:c0:a8:00:01
> 1500 192.168.0.1 24 192.168.0.254 1000::3/64 1000::a
> > +m_as ovn-chassis-2 /data/create_fake_vm.sh inside2 hv2-vif1
> f0:00:c0:a8:01:02 1500 192.168.1.2 24 192.168.1.254 2000::2/64 2000::a
> > +m_as ovn-chassis-3 /data/create_fake_vm.sh ext1 ext1 00:00:c0:a8:00:01
> 1500 192.168.0.1 24 192.168.0.254 1000::3/64 1000::a
> >
> >  # There should be one ha_chassis_group with the name "R1_outside"
> >  m_check_row_count HA_Chassis_Group 1 name=R1_outside
> > @@ -3160,53 +3183,67 @@ for chassis in $from_ch1_to_gw1
> $from_ch1_to_gw2; do
> >      wait_bfd_enabled ovn-chassis-1 $chassis
> >  done
> >
> > +# check BFD enablement on tunnel ports from ovn-chassis-2 ###########
> > +for chassis in $from_ch2_to_gw1 $from_ch2_to_gw2; do
> > +    echo "checking ovn-chassis-2 -> $chassis"
> > +    wait_bfd_enabled ovn-chassis-2 $chassis
> > +done
> > +
> >  # Make sure there is no nft table left. Do not use nft directly as
> might not be installed in container.
> >  gw1_pid=$(podman inspect -f '{{.State.Pid}}' ovn-gw-1)
> >  nsenter --net=/proc/$gw1_pid/ns/net nft list tables | grep ovn-test &&
> nsenter --net=/proc/$gw1_pid/ns/net nft delete table ip ovn-test
> > -on_exit "nsenter --net=/proc/$gw1_pid/ns/net nft list tables | grep
> ovn-test && nsenter --net=/proc/$gw1_pid/ns/net nft delete table ip
> ovn-test"
> > +on_exit "if [[ -d "/proc/$gw1_pid" ]]; then nsenter
> --net=/proc/$gw1_pid/ns/net nft list tables | grep ovn-test && nsenter
> --net=/proc/$gw1_pid/ns/net nft delete table ip ovn-test; fi"
> >
> > -for chassis in $from_gw1_to_gw2 $from_gw1_to_gw3 $from_gw1_to_ch1; do
> > +for chassis in $from_gw1_to_gw2 $from_gw1_to_gw3 $from_gw1_to_ch1
> $from_gw1_to_ch2; do
> >      wait_bfd_up ovn-gw-1 $chassis
> >  done
> > -for chassis in $from_gw2_to_gw1 $from_gw2_to_gw3 $from_gw2_to_ch1; do
> > +for chassis in $from_gw2_to_gw1 $from_gw2_to_gw3 $from_gw2_to_ch1
> $from_gw2_to_ch2; do
> >      wait_bfd_up ovn-gw-2 $chassis
> >  done
> >  for chassis in $from_ch1_to_gw1 $from_ch1_to_gw2; do
> >      wait_bfd_up ovn-chassis-1 $chassis
> >  done
> > +for chassis in $from_ch2_to_gw1 $from_ch2_to_gw2; do
> > +    wait_bfd_up ovn-chassis-2 $chassis
> > +done
> >
> >  m_wait_row_count Port_Binding 1 logical_port=cr-R1_outside
> chassis=$gw1_chassis
> >  check multinode_nbctl --wait=hv sync
> >
> >  start_tcpdump() {
> >      echo "$(date +%H:%M:%S.%03N) Starting tcpdump"
> > -    M_START_TCPDUMP([ovn-chassis-1], [-neei hv1-vif1-p], [ch1])
> > -    M_START_TCPDUMP([ovn-chassis-2], [-neei eth2], [ch2])
> > -    M_START_TCPDUMP([ovn-gw-1], [-neei eth2], [gw1])
> > -    M_START_TCPDUMP([ovn-gw-1], [-neei eth2 -Q out], [gw1_out])
> > -    M_START_TCPDUMP([ovn-gw-2], [-neei eth2], [gw2])
> > -    M_START_TCPDUMP([ovn-gw-2], [-neei eth2 -Q out], [gw2_out])
> > -    M_START_TCPDUMP([ovn-gw-3], [-neei eth2], [gw3])
> > -    M_START_TCPDUMP([ovn-gw-3], [-neei eth2 -Q out], [gw3_out])
> > +    M_START_TCPDUMPS([ovn-chassis-1], [-neei hv1-vif1-p], [ch1],
> > +                    [ovn-chassis-2], [-neei hv2-vif1-p], [ch2],
> > +                    [ovn-chassis-3], [-neei eth2], [ch3],
> > +                    [ovn-gw-1], [-neei eth2], [gw1],
> > +                    [ovn-gw-1], [-neei eth2 -Q out], [gw1_out],
> > +                    [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-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() {
> >      echo "$(date +%H:%M:%S.%03N) Stopping tcpdump"
> > -    m_kill 'ovn-gw-1 ovn-gw-2 ovn-gw-3 ovn-chassis-1 ovn-chassis-2'
> tcpdump
> > +    m_kill 'ovn-gw-1 ovn-gw-2 ovn-gw-3 ovn-chassis-1 ovn-chassis-2
> ovn-chassis-3' tcpdump
> >  }
> >
> > -# Send packets from chassis2 (ext1) to chassis1
> > +# Send packets from ovn-chassis-3 (ext1) to ovn-chassis-1
> >  send_background_packets() {
> >      echo "$(date +%H:%M:%S.%03N) Sending packets in Background"
> >      start_tcpdump
> > -    M_NS_DAEMONIZE([ovn-chassis-2], [ext1], [ping -f -i 0.1
> 192.168.1.1], [ping.pid])
> > +    M_NS_DAEMONIZE([ovn-chassis-3], [ext1], [ping -f -i 0.1
> 192.168.1.1], [ping.pid])
> >  }
> >
> >  stop_sending_background_packets() {
> >      echo "$(date +%H:%M:%S.%03N) Stopping Background process"
> >      m_as ovn-chassis-1 ps -ef | grep -v grep | grep -q ping && \
> >          m_as ovn-chassis-1 echo "Stopping ping on ovn-chassis-1" &&
> killall ping
> > -    m_as ovn-chassis-2 ps -ef | grep -v grep | grep -q ping && \
> > +    m_as ovn-chassis-3 ps -ef | grep -v grep | grep -q ping && \
> >          m_as ovn-chassis-2 echo "Stopping ping on ovn-chassis-2" &&
> killall ping
> >      stop_tcpdump
> >  }
> > @@ -3216,8 +3253,8 @@ check_for_new_garps() {
> >      expecting_garp=$2
> >      n_new_garps=$(cat ${hv}_out.tcpdump | grep -c "f0:00:c0:a8:00:fe >
> Broadcast, ethertype ARP (0x0806), length 42: Request who-has 192.168.0.254
> tell 192.168.0.254, length 28")
> >
> > -    if [ "$expecting_garp" == "true" ]; then
> > -        AS_BOX([$(date +%H:%M:%S.%03N) Waiting/checking for garp from
> $hv - Starting with $n_new_garps])
> > +    if [[ "$expecting_garp" == "true" ]]; then
> > +        echo "$(date +%H:%M:%S.%03N) Waiting/checking for garp from $hv
> - Starting with $n_new_garps"
> >          OVS_WAIT_UNTIL([
> >              n_garps=$n_new_garps
> >              n_new_garps=$(cat ${hv}_out.tcpdump | grep -c
> "f0:00:c0:a8:00:fe > Broadcast, ethertype ARP (0x0806), length 42: Request
> who-has 192.168.0.254 tell 192.168.0.254, length 28")
> > @@ -3225,7 +3262,7 @@ check_for_new_garps() {
> >              test "$n_garps" -ne "$n_new_garps"
> >          ])
> >      else
> > -        AS_BOX([$(date +%H:%M:%S.%03N) Checking no garp from ${hv}])
> > +        echo "$(date +%H:%M:%S.%03N) Checking no garp from ${hv}"
> >          # Waiting a few seconds to get a chance to see unexpected garps.
> >          sleep 3
> >          n_garps=$(cat ${hv}_out.tcpdump | grep -c "f0:00:c0:a8:00:fe >
> Broadcast, ethertype ARP (0x0806), length 42: Request who-has 192.168.0.254
> tell 192.168.0.254, length 28")
> > @@ -3241,8 +3278,8 @@ check_for_new_echo_pkts() {
> >      n_new_echo_req=$(cat ${hv}.tcpdump | grep -c "$mac_src > $mac_dst,
> ethertype IPv4 (0x0800), length 98: 192.168.0.1 > 192.168.1.1: ICMP echo
> request")
> >      n_new_echo_rep=$(cat ${hv}.tcpdump | grep -c "$mac_dst > $mac_src,
> ethertype IPv4 (0x0800), length 98: 192.168.1.1 > 192.168.0.1: ICMP echo
> reply")
> >
> > -    if [ "$expecting_pkts" == "true" ]; then
> > -        AS_BOX([$(date +%H:%M:%S.%03N) Waiting/checking for echo pkts
> through ${hv}])
> > +    if [[ "$expecting_pkts" == "true" ]]; then
> > +        echo "$(date +%H:%M:%S.%03N) Waiting/checking for echo pkts
> through ${hv}"
> >          echo "Starting with $n_new_echo_req requests and
> $n_new_echo_rep replies so far on ${hv}."
> >          OVS_WAIT_UNTIL([
> >              n_echo_req=$n_new_echo_req
> > @@ -3253,7 +3290,7 @@ check_for_new_echo_pkts() {
> >              test "$n_echo_req" -ne "$n_new_echo_req" && test
> "$n_echo_rep" -ne "$n_new_echo_rep"
> >          ])
> >      else
> > -        AS_BOX([$(date +%H:%M:%S.%03N) Checking no pkts from ${hv}])
> > +        echo "$(date +%H:%M:%S.%03N) Checking no pkts from ${hv}"
> >          # Waiting a few seconds to get a chance to see unexpected pkts.
> >          sleep 3
> >          n_echo_req=$(cat ${hv}.tcpdump | grep -c "$mac_src > $mac_dst,
> ethertype IPv4 (0x0800), length 98: 192.168.0.1 > 192.168.1.1: ICMP echo
> request")
> > @@ -3271,22 +3308,44 @@ dump_statistics() {
> >      ch1_rep=$(grep -c "ICMP echo reply" ch1.tcpdump)
> >      ch2_req=$(grep -c "ICMP echo request" ch2.tcpdump)
> >      ch2_rep=$(grep -c "ICMP echo reply" ch2.tcpdump)
> > +    ch3_req=$(grep -c "ICMP echo request" ch3.tcpdump)
> > +    ch3_rep=$(grep -c "ICMP echo reply" ch3.tcpdump)
> >      gw1_req=$(grep -c "ICMP echo request" gw1.tcpdump)
> >      gw1_rep=$(grep -c "ICMP echo reply" gw1.tcpdump)
> >      gw2_req=$(grep -c "ICMP echo request" gw2.tcpdump)
> >      gw2_rep=$(grep -c "ICMP echo reply" gw2.tcpdump)
> >      gw3_req=$(grep -c "ICMP echo request" gw3.tcpdump)
> >      gw3_rep=$(grep -c "ICMP echo reply" gw3.tcpdump)
> > -    echo "$n1 claims in gw1, $n2 in gw2 and $n3 on gw3"
> > -    echo "ch2_request=$ch2_req gw1_request=$gw1_req
> gw2_request=$gw2_req gw3_request=$gw3_req ch1_request=$ch1_req
> ch1_reply=$ch1_rep gw1_reply=$gw1_rep gw2_reply=$gw2_rep gw3_reply=$gw3_rep
> ch2_reply=$ch2_rep"
> > +    echo "$n1 claims in gw1, $n2 in gw2 and $n3 on gw3" >&2
> > +    echo "ch3_req=$ch3_req gw_req=($gw1_req + $gw2_req +$gw3_req)
> ch1_req=$ch1_req ch1_rep=$ch1_rep gw_rep=($gw1_rep + $gw2_rep + $gw3_rep)
> ch3_rep=$ch3_rep ch2=($ch2_req+$ch2_rep)" >&2
> > +    echo "$((ch3_req - ch3_rep))"
> >  }
> >
> > -check_migration_between_gw1_and_gw2() {
> > -    action=$1
> > -    send_background_packets
> > +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 add "${port}_l" type veth peer name "${port}_c"
> > +    ip link set "${port}_l" up
> > +    ip link set "${port}_c" netns $pid
> > +    ip netns exec $pid ip link set dev "${port}_c" name "$interface"
> > +    ip netns exec $pid ip link set "$interface" up
> > +    if [[ -n "$address" ]]; then
> > +        ip netns exec $pid 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.
> > -    AS_BOX([$(date +%H:%M:%S.%03N) Waiting all garps sent by gw1])
> > +    echo $(date +%H:%M:%S.%03N) Waiting all garps sent by gw1
> >      n_new_garps=$(cat gw1_out.tcpdump | grep -c "f0:00:c0:a8:00:fe >
> Broadcast, ethertype ARP (0x0806), length 42: Request who-has 192.168.0.254
> tell 192.168.0.254, length 28")
> >      OVS_WAIT_UNTIL([
> >          n_garps=$n_new_garps
> > @@ -3302,130 +3361,269 @@ check_migration_between_gw1_and_gw2() {
> >      check_for_new_echo_pkts gw2 "00:00:c0:a8:00:01" "f0:00:c0:a8:00:fe"
> "false"
> >      check_for_new_echo_pkts gw3 "00:00:c0:a8:00:01" "f0:00:c0:a8:00:fe"
> "false"
> >
> > +    # All packets should go through gw1, and none through gw2 or gw3.
> > +    check_packets "true" "false" "false" "true"
> >      flap_count_gw_1=$(m_as ovn-gw-1 ovs-vsctl get interface
> $from_gw1_to_gw2 bfd_status | sed 's/.*flap_count=\"\([[0-9]]*\).*/\1/g')
> >      flap_count_gw_2=$(m_as ovn-gw-2 ovs-vsctl get interface
> $from_gw2_to_gw1 bfd_status | sed 's/.*flap_count=\"\([[0-9]]*\).*/\1/g')
> > +}
> >
> > -    if [ test "$action" == "stop_bfd" ]; then
> > -        AS_BOX([$(date +%H:%M:%S.%03N) Blocking bfd on gw1 (from
> $ip_gw1 to $ip_gw2)])
> > -        nsenter --net=/proc/$gw1_pid/ns/net nft add table ip ovn-test
> > -        nsenter --net=/proc/$gw1_pid/ns/net nft 'add chain ip ovn-test
> INPUT { type filter hook input priority 0; policy accept; }'
> > -        # Drop BFD from gw-1 to gw-2: geneve port (6081), inner port
> 3784 (0xec8), Session state Up, Init, Down.
> > -        nsenter --net=/proc/$gw1_pid/ns/net nft add rule ip ovn-test
> INPUT ip daddr $ip_gw1 ip saddr $ip_gw2 udp dport 6081 '@th,416,16 ==
> 0x0ec8 @th,472,8 == 0xc0  counter drop'
> > -        nsenter --net=/proc/$gw1_pid/ns/net nft add rule ip ovn-test
> INPUT ip daddr $ip_gw1 ip saddr $ip_gw2 udp dport 6081 '@th,416,16 ==
> 0x0ec8 @th,472,8 == 0x80  counter drop'
> > -        nsenter --net=/proc/$gw1_pid/ns/net nft add rule ip ovn-test
> INPUT ip daddr $ip_gw1 ip saddr $ip_gw2 udp dport 6081 '@th,416,16 ==
> 0x0ec8 @th,472,8 == 0x40  counter drop'
> > -
> > -        # We do not check that packets go through gw2 as BFD between
> chassis-2 and gw1 is still up
> > -    fi
> > -
> > -    if [ test "$action" == "kill_gw2" ]; then
> > -        AS_BOX([$(date +%H:%M:%S.%03N) Killing gw2 ovn-controller])
> > -        on_exit 'm_as ovn-gw-2 /usr/share/openvswitch/scripts/ovs-ctl
> status ||
> > -                 m_as ovn-gw-2 /usr/share/openvswitch/scripts/ovs-ctl
> start --system-id=ovn-gw-2'
> > -        on_exit 'm_as ovn-gw-2 /usr/share/ovn/scripts/ovn-ctl
> status_controller ||
> > -                 m_as ovn-gw-2 /usr/share/ovn/scripts/ovn-ctl
> start_controller ${CONTROLLER_SSL_ARGS}'
> > -
> > -        m_as ovn-gw-2 kill -9 $(m_as ovn-gw-2 cat
> /run/ovn/ovn-controller.pid)
> > -        m_as ovn-gw-2 kill -9 $(m_as ovn-gw-2 cat
> /run/openvswitch/ovs-vswitchd.pid)
> > -        m_as ovn-gw-2 kill -9 $(m_as ovn-gw-2 cat
> /run/openvswitch/ovsdb-server.pid)
> > -        # Also delete datapath (flows)
> > -        m_as ovn-gw-2 ovs-dpctl del-dp system@ovs-system
> > -    fi
> > -
> > -    if [ test "$action" == "kill_gw1" ]; then
> > -        AS_BOX([$(date +%H:%M:%S.%03N) Killing gw1 ovn-controller])
> > -        on_exit 'm_as ovn-gw-1 /usr/share/openvswitch/scripts/ovs-ctl
> status ||
> > -                 m_as ovn-gw-1 /usr/share/openvswitch/scripts/ovs-ctl
> start --system-id=ovn-gw-1'
> > -        on_exit 'm_as ovn-gw-1 /usr/share/ovn/scripts/ovn-ctl
> status_controller ||
> > -                 m_as ovn-gw-1 /usr/share/ovn/scripts/ovn-ctl
> start_controller ${CONTROLLER_SSL_ARGS}'
> > -
> > -        m_as ovn-gw-1 kill -9 $(m_as ovn-gw-1 cat
> /run/ovn/ovn-controller.pid)
> > -        m_as ovn-gw-1 kill -9 $(m_as ovn-gw-1 cat
> /run/openvswitch/ovs-vswitchd.pid)
> > -        m_as ovn-gw-1 kill -9 $(m_as ovn-gw-1 cat
> /run/openvswitch/ovsdb-server.pid)
> > -        # Also delete datapath (flows)
> > -        m_as ovn-gw-1 ovs-dpctl del-dp system@ovs-system
> > -    fi
> > +check_loss_after_flap()
> > +{
> > +    dead=$1
> > +    max_expected_loss=$2
> >
> > -    if [ test "$action" == "kill_gw2" ]; then
> > -        AS_BOX([$(date +%H:%M:%S.%03N) Waiting for flap count between
> gw1 and gw2 to increase])
> > +    if [[ "$dead" == "gw2" ]]; then
> > +        echo "$(date +%H:%M:%S.%03N) Waiting for flap count between gw1
> and gw2 to increase"
> >          OVS_WAIT_UNTIL([
> >              new_flap_count=$(m_as ovn-gw-1 ovs-vsctl get interfac
> $from_gw1_to_gw2 bfd_status | sed 's/.*flap_count=\"\([[0-9]]*\).*/\1/g')
> >              echo "Comparing $new_flap_count versus $flap_count_gw_1"
> >              test "$new_flap_count" -gt "$((flap_count_gw_1))"
> >          ])
> >      else
> > -        AS_BOX([$(date +%H:%M:%S.%03N) Waiting for flap count between
> gw2 and gw1 to increase])
> > +        echo "$(date +%H:%M:%S.%03N) Waiting for flap count between gw2
> and gw1 to increase])"
> >          OVS_WAIT_UNTIL([
> >              new_flap_count=$(m_as ovn-gw-2 ovs-vsctl get interfac
> $from_gw2_to_gw1 bfd_status | sed 's/.*flap_count=\"\([[0-9]]*\).*/\1/g')
> >              echo "Comparing $new_flap_count versus $flap_count_gw_2"
> >              test "$new_flap_count" -gt "$((flap_count_gw_2))"
> >          ])
> > -
> >      fi
> > -    AS_BOX([$(date +%H:%M:%S.%03N) Flapped!])
> >
> > +    echo "$(date +%H:%M:%S.%03N) Flapped!"
> >      # Wait a few more second for the fight.
> > +    sleep 4
> > +
> > +    echo "$(date +%H:%M:%S.%03N) Statistics after flapping"
> > +    lost=$(dump_statistics)
> > +    echo "===> $lost packet lost while handling migration"
> > +    AT_CHECK([test "$lost" -le "$max_expected_loss"])
> > +}
> > +
> > +final_check()
> > +{
> > +    action=$1
> > +    lost=$2
> > +    max_expected_loss_after_restoration=$3
> > +
> > +    # Wait a little more to get packets while network is restored
> >      sleep 2
> > -    AS_BOX([$(date +%H:%M:%S.%03N) Statistics after flapping])
> > -    dump_statistics
> > -
> > -    if [ test "$action" == "stop_bfd" ]; then
> > -        # gw1 still alive and gw2 tried to claim => gw1 should restart
> generating garps.
> > -        check_for_new_garps gw1 "true"
> > -        check_for_new_garps gw2 "false"
> > -        check_for_new_garps gw3 "false"
> > -        check_for_new_echo_pkts gw1 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe
> "true"
> > -        check_for_new_echo_pkts gw2 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe
> "false"
> > -        check_for_new_echo_pkts gw3 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe
> "false"
> > -        check_for_new_echo_pkts ch1 f0:00:c0:a8:01:fe f0:00:c0:a8:01:01
> "true"
> > -        AS_BOX([$(date +%H:%M:%S.%03N) Unblocking bfd on gw1])
> > -        nsenter --net=/proc/$gw1_pid/ns/net nft -a list ruleset
> > -        nsenter --net=/proc/$gw1_pid/ns/net nft delete table ip ovn-test
> > -    fi
> > +    echo "$(date +%H:%M:%S.%03N) Statistics after network restored
> (after $action)"
> > +    new_lost=$(dump_statistics)
> > +    echo "===> $((new_lost - lost)) packets lost during network
> restoration"
> > +    AT_CHECK([test "$((new_lost - lost))" -le
> "$max_expected_loss_after_restoration"])
> > +    stop_sending_background_packets
> > +}
> >
> > -    if [ test "$action" == "kill_gw2" ]; then
> > -        # gw1 still alive, but gw2 did not try to claim => gw1 should
> not generate new garps.
> > -        check_for_new_garps gw1 "false"
> > -        check_for_new_garps gw2 "false"
> > -        check_for_new_garps gw3 "false"
> > -        check_for_new_echo_pkts gw1 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe
> "true"
> > -        check_for_new_echo_pkts gw2 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe
> "false"
> > -        check_for_new_echo_pkts gw3 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe
> "false"
> > -        check_for_new_echo_pkts ch1 f0:00:c0:a8:01:fe f0:00:c0:a8:01:01
> "true"
> > -        AS_BOX([$(date +%H:%M:%S.%03N) Restarting gw2 ovn-vswitchd])
> > -        m_as ovn-gw-2 /usr/share/openvswitch/scripts/ovs-ctl start
> --system-id=ovn-gw-2
> > -
> > -        AS_BOX([$(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}
> > -    fi
> > +check_garps()
> > +{
> > +    check_for_new_garps gw1 "$1"
> > +    check_for_new_garps gw2 "$2"
> > +    check_for_new_garps gw3 "$3"
> > +}
> >
> > -    if [ test "$action" == "kill_gw1" ]; then
> > -        # gw1 died => gw2 should generate garps.
> > -        check_for_new_garps gw1 "false"
> > -        check_for_new_garps gw2 "true"
> > -        check_for_new_garps gw3 "false"
> > -        check_for_new_echo_pkts gw1 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe
> "false"
> > -        check_for_new_echo_pkts gw2 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe
> "true"
> > -        check_for_new_echo_pkts gw3 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe
> "false"
> > -        check_for_new_echo_pkts ch1 f0:00:c0:a8:01:fe f0:00:c0:a8:01:01
> "true"
> > -        AS_BOX([$(date +%H:%M:%S.%03N) Restarting gw1 ovn-vswitchd])
> > -        m_as ovn-gw-1 /usr/share/openvswitch/scripts/ovs-ctl start
> --system-id=ovn-gw-1
> > -
> > -        AS_BOX([$(date +%H:%M:%S.%03N) Restarting gw1 ovn-controller])
> > -        m_as ovn-gw-1 /usr/share/ovn/scripts/ovn-ctl start_controller
> ${CONTROLLER_SSL_ARGS}
> > -    fi
> > +check_packets()
> > +{
> > +    check_for_new_echo_pkts gw1 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe "$1"
> > +    check_for_new_echo_pkts gw2 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe "$2"
> > +    check_for_new_echo_pkts gw3 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe "$3"
> > +    check_for_new_echo_pkts ch1 f0:00:c0:a8:01:fe f0:00:c0:a8:01:01 "$4"
> > +}
> > +
> > +check_migration_between_gw1_and_gw2_bfd_stop()
> > +{
> > +    AS_BOX([$(date +%H:%M:%S.%03N) Testing migration after bfd_stop])
> > +    max_expected_max_expected_loss1=$1
> > +    max_expected_max_expected_loss2=$2
> > +    prepare
> > +
> > +    echo "$(date +%H:%M:%S.%03N) Blocking bfd on gw1 (from $ip_gw1 to
> $ip_gw2)"
> > +    nsenter --net=/proc/$gw1_pid/ns/net nft add table ip ovn-test
> > +    nsenter --net=/proc/$gw1_pid/ns/net nft 'add chain ip ovn-test
> INPUT { type filter hook input priority 0; policy accept; }'
> > +    # Drop BFD from gw-1 to gw-2: geneve port (6081), inner port 3784
> (0xec8), Session state Up, Init, Down.
> > +    nsenter --net=/proc/$gw1_pid/ns/net nft add rule ip ovn-test INPUT
> ip daddr $ip_gw1 ip saddr $ip_gw2 udp dport 6081 '@th,416,16 == 0x0ec8
> @th,472,8 == 0xc0  counter drop'
> > +    nsenter --net=/proc/$gw1_pid/ns/net nft add rule ip ovn-test INPUT
> ip daddr $ip_gw1 ip saddr $ip_gw2 udp dport 6081 '@th,416,16 == 0x0ec8
> @th,472,8 == 0x80  counter drop'
> > +    nsenter --net=/proc/$gw1_pid/ns/net nft add rule ip ovn-test INPUT
> ip daddr $ip_gw1 ip saddr $ip_gw2 udp dport 6081 '@th,416,16 == 0x0ec8
> @th,472,8 == 0x40  counter drop'
> > +
> > +    check_loss_after_flap "gw1" $max_expected_max_expected_loss1
> > +
> > +    # gw1 still alive and gw2 tried to claim => gw1 should restart
> generating garps.
> > +    check_garps "true" "false" "false"
> > +    check_packets "true" "false" "false" "true"
> > +
> > +    echo "$(date +%H:%M:%S.%03N) Unblocking bfd on gw1"
> > +    nsenter --net=/proc/$gw1_pid/ns/net nft -a list ruleset
> > +    nsenter --net=/proc/$gw1_pid/ns/net nft delete table ip ovn-test
> >
> >      # The network is now restored => packets should go through gw1 and
> reach chassis-1.
> > -    check_for_new_echo_pkts gw1 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe
> "true"
> > -    check_for_new_echo_pkts gw2 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe
> "false"
> > -    check_for_new_echo_pkts gw3 00:00:c0:a8:00:01 f0:00:c0:a8:00:fe
> "false"
> > -    check_for_new_echo_pkts ch1 f0:00:c0:a8:01:fe f0:00:c0:a8:01:01
> "true"
> > -    AS_BOX([$(date +%H:%M:%S.%03N) Statistics after network restored])
> > -    dump_statistics
> > -    stop_sending_background_packets
> > +    check_packets "true" "false" "false" "true"
> > +    final_check "bfd_stop" $lost $max_expected_max_expected_loss2
> > +}
> > +
> > +check_migration_between_gw1_and_gw2_kill_gw2() {
> > +    AS_BOX([$(date +%H:%M:%S.%03N) Check migration after killing gw2
> ovn-controller & vswitchd])
> > +    max_expected_loss1=$1
> > +    max_expected_loss2=$2
> > +    prepare
> > +
> > +    on_exit 'm_as ovn-gw-2 /usr/share/openvswitch/scripts/ovs-ctl
> status ||
> > +             m_as ovn-gw-2 /usr/share/openvswitch/scripts/ovs-ctl start
> --system-id=ovn-gw-2'
> > +    on_exit 'm_as ovn-gw-2 /usr/share/ovn/scripts/ovn-ctl
> status_controller ||
> > +             m_as ovn-gw-2 /usr/share/ovn/scripts/ovn-ctl
> start_controller ${CONTROLLER_SSL_ARGS}'
> > +
> > +    m_as ovn-gw-2 kill -9 $(m_as ovn-gw-2 cat
> /run/ovn/ovn-controller.pid)
> > +    m_as ovn-gw-2 kill -9 $(m_as ovn-gw-2 cat
> /run/openvswitch/ovs-vswitchd.pid)
> > +    m_as ovn-gw-2 kill -9 $(m_as ovn-gw-2 cat
> /run/openvswitch/ovsdb-server.pid)
> > +    m_as ovn-gw-2 ovs-dpctl del-dp system@ovs-system
> > +
> > +    check_loss_after_flap "gw2" $max_expected_loss1
> > +
> > +    # gw1 still alive, but gw2 did not try to claim => gw1 should not
> generate new garps.
> > +    check_garps "false" "false" "false"
> > +    check_packets "true" "fals" "false" "true"
> > +
> > +    echo "$(date +%H:%M:%S.%03N) Restarting gw2 ovn-vswitchd]"
> > +    m_as ovn-gw-2 /usr/share/openvswitch/scripts/ovs-ctl start
> --system-id=ovn-gw-2
> > +
> > +    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}
> > +
> > +    # The network is now restored => packets should go through gw1 and
> reach chassis-1.
> > +    check_packets "true" "false" "false" "true"
> > +    final_check "kill_gw2" $lost $max_expected_loss2
> > +}
> > +
> > +check_migration_between_gw1_and_gw2_update_ovs() {
> > +    AS_BOX([$(date +%H:%M:%S.%03N) Check migration after restarting gw1
> ovs-vswitchd ("update")])
> > +    max_expected_loss1=$1
> > +    max_expected_loss2=$2
> > +    prepare
> > +
> > +    m_as ovn-gw-1 /usr/share/openvswitch/scripts/ovs-ctl restart
> --system-id=ovn-gw-1
> > +
> > +    check_loss_after_flap "gw1" $max_expected_loss1
> > +
> > +    # The network is now restored => packets should go through gw1 and
> reach chassis-1.
> > +    check_packets "true" "false" "false" "true"
> > +    final_check "ovs_update" $lost $max_expected_loss2
> > +}
> > +
> > +check_migration_between_gw1_and_gw2_kill_gw1() {
> > +    AS_BOX([$(date +%H:%M:%S.%03N) Killing gw1 ovn-controller and
> ovs-vswitchd])
> > +    max_expected_loss1=$1
> > +    max_expected_loss2=$2
> > +    prepare
> > +
> > +    on_exit 'm_as ovn-gw-1 /usr/share/openvswitch/scripts/ovs-ctl
> status ||
> > +             m_as ovn-gw-1 /usr/share/openvswitch/scripts/ovs-ctl start
> --system-id=ovn-gw-1'
> > +    on_exit 'm_as ovn-gw-1 /usr/share/ovn/scripts/ovn-ctl
> status_controller ||
> > +             m_as ovn-gw-1 /usr/share/ovn/scripts/ovn-ctl
> start_controller ${CONTROLLER_SSL_ARGS}'
> > +
> > +    m_as ovn-gw-1 kill -9 $(m_as ovn-gw-1 cat
> /run/ovn/ovn-controller.pid)
> > +    m_as ovn-gw-1 kill -9 $(m_as ovn-gw-1 cat
> /run/openvswitch/ovs-vswitchd.pid)
> > +    m_as ovn-gw-1 kill -9 $(m_as ovn-gw-1 cat
> /run/openvswitch/ovsdb-server.pid)
> > +    # Also delete datapath (flows)
> > +    m_as ovn-gw-1 ovs-dpctl del-dp system@ovs-system
> > +
> > +    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"
> > +    echo "$(date +%H:%M:%S.%03N) Restarting gw1 ovn-vswitchd after
> killing gw1"
> > +    m_as ovn-gw-1 /usr/share/openvswitch/scripts/ovs-ctl start
> --system-id=ovn-gw-1
> > +
> > +    # 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
> killing gw1"
> > +    m_as ovn-gw-1 /usr/share/ovn/scripts/ovn-ctl start_controller
> ${CONTROLLER_SSL_ARGS}
> > +
> > +    # The network is now restored => packets should go through gw1 and
> reach chassis-1.
> > +    check_packets "true" "false" "false" "true"
> > +    final_check "kill_gw1" $lost $max_expected_loss2
> > +}
> > +
> > +check_migration_between_gw1_and_gw2_reboot_gw1() {
> > +    ip_gw1_eth1=$(podman exec ovn-gw-1 ip -brief address show eth1 |
> awk '{print $3}' | cut -d/ -f1)
> > +    cidr=$(podman exec ovn-gw-1 ip -brief address show eth1 | awk
> '{print $3}' | cut -d/ -f2)
> > +    AS_BOX([$(date +%H:%M:%S.%03N) Rebooting ovn-gw-1 with
> $ip_gw1_eth1/$cidr])
> > +    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 (added by fake
> > +    # multinode) got deleted. Add them back.
> > +    add_port br-ovn eth1 $ip_gw1_eth1/$cidr
> > +    add_port br-ovn-ext eth2
> > +    M_START_TCPDUMPS([ovn-gw-1], [-neei eth2], [gw1], [ovn-gw-1],
> [-neei eth1], [gw1_eth1], [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"
> > +
> > +    echo "$(date +%H:%M:%S.%03N) Restarting gw1 ovn-vswitchd after
> rebooting gw1"
> > +    m_as ovn-gw-1 /usr/share/openvswitch/scripts/ovs-ctl start
> --system-id=ovn-gw-1
> > +
> > +    # 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}
> > +
> > +    # The network is now restored => packets should go through gw1 and
> reach chassis-1.
> > +    check_packets "true" "false" "false" "true"
> > +    final_check "kill_gw1" $lost $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')
> > +    echo "$(date +%H:%M:%S.%03N) Restarting ovn-chassis-1 ovn-vswitchd."
> > +    m_as ovn-chassis-1 /usr/share/openvswitch/scripts/ovs-ctl start
> --system-id=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))"
> > +    ])
> > +
> > +    wait_bfd_up ovn-chassis-1 $from_ch1_to_gw1
> > +
> > +    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}
> > +
> > +    # Wait a long time to catch losses
> > +    sleep 5
> > +    final_check "compute" 0 $max_expected_loss
> >  }
> >
> >  start_tcpdump
> > -AS_BOX([$(date +%H:%M:%S.%03N) Sending packet from hv1-vif1(inside1) to
> ext1])
> > +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],
> >  [0], [dnl
> >  3 packets transmitted, 3 received, 0% packet loss, time 0ms
> > @@ -3433,7 +3631,7 @@ M_NS_CHECK_EXEC([ovn-chassis-1], [hv1-vif1], [ping
> -c3 -q -i 0.1 192.168.0.1 | F
> >  stop_tcpdump
> >
> >  # It should have gone through gw1 and not gw2
> > -AS_BOX([$(date +%H:%M:%S.%03N) Checking it went through gw1 and not
> gw2])
> > +echo "$(date +%H:%M:%S.%03N) Checking it went through gw1 and not gw2"
> >  AT_CHECK([cat gw2.tcpdump | grep "ICMP echo"], [1], [dnl
> >  ])
> >
> > @@ -3446,17 +3644,29 @@ f0:00:c0:a8:00:fe > 00:00:c0:a8:00:01, ethertype
> IPv4 (0x0800), length 98: 192.1
> >  00:00:c0:a8:00:01 > f0:00:c0:a8:00:fe, ethertype IPv4 (0x0800), length
> 98: 192.168.0.1 > 192.168.1.1: ICMP echo reply,
> >  ])
> >
> > -# We stop bfd between gw1 & gw2, but keep gw1 & gw2 running.
> > -check_migration_between_gw1_and_gw2 "stop_bfd"
> > +# We stop bfd between gw1 & gw2, but keep gw1 & gw2 running. We should
> not lose packets.
> > +check_migration_between_gw1_and_gw2_bfd_stop 1 1
> >
> >  # We simulate death of gw2. It should not have any effect.
> > -check_migration_between_gw1_and_gw2 "kill_gw2"
> > +check_migration_between_gw1_and_gw2_kill_gw2 1 1
> > +
> > +# 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 will
> start handling packets.
> > +# There will be packet losses as gw2 will usually see BFD from gw1 up
> (and hence relase port) before gw1 sees
> > +# BFD up (and claim port).
> > +check_migration_between_gw1_and_gw2_update_ovs 20 1
> > +
> > +# We simulate restart of both OVS & OVN gw1. gw2 should take over.
> > +check_migration_between_gw1_and_gw2_kill_gw1 40 20
> >
> >  # We simulate death of gw1. gw2 should take over.
> > -check_migration_between_gw1_and_gw2 "kill_gw1"
> > +check_migration_between_gw1_and_gw2_reboot_gw1 40 20
> > +
> > +# We simulate restart of ovn-chassis-1. We expect for ~4 sec losses as
> we wait for bfd up before starting
> > +# ovn-controller (1 sec to restart ovs, 2 sec for bfd to go up, 1 sec
> to detect it).
> > +check_compute_restart 40
> >
> >  AT_CLEANUP
> > -])
> >
> >  AT_SETUP([ovn multinode bgp L2 EVPN])
> >  check_fake_multinode_setup
>
> Regards,
> Dumitru
>
Thanks
Xavier
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to