On Wed, Sep 23, 2026 at 3:24 PM Jacob Tanenbaum <[email protected]> wrote:

> Currently ovn-controller controls the OpenFlow flows of the
> internal bridge br-int through the default service controller
> of the <bridge>.mgmt management port.  That connection is not
> visible to the bridge's fail-open/fail-secure state machine:
> ofproto_is_alive() only tracks primary controllers, so OVS
> cannot distinguish a live ovn-controller from a dead one.
>
> Register a primary OpenFlow controller on br-int pointing at a
> passive listener, punix:<rundir>/br-int.ovn-primary, and make
> the ofctrl, pinctrl, statctrl, and feature-discovery software
> connections dial that endpoint instead of the .mgmt port.
> Making the connection a primary controller lets the bridge's
> fail-open/fail-secure state machine and ofproto_is_alive()
> track the ovn-controller connection.
>
> The primary target is derived from the same endpoint
> br_int_remote_update() dials, so the value of
> external_ids:ovn-bridge-remote is honored.  A primary
> controller is a listener hosted by ovn-controller, which
> only applies to local unix: and punix: endpoints; for remote
> tcp: and ssl: endpoints the operator configures the primary
> listener separately (e.g. with "ovs-vsctl set-controller").
> Passive inet targets use a "[<port>][:<host>]" layout, so
> the active scheme cannot be swapped onto them.
>
> To avoid racing the socket creation, the first connection to
> the primary endpoint is deferred until ovs-vswitchd applies
> the commit that registers the controller, which is the change
> that creates the punix listener.  The Open_vSwitch next_cfg
> and cur_cfg protocol, the same one ovs-vsctl --wait relies
> on, is reused for this: when a new primary controller is
> registered, next_cfg is incremented in the same transaction,
> and all the software connections that dial the primary
> endpoint are held back until cur_cfg advances past the
> pre-increment value.  The wait is only armed when a new
> primary controller is inserted (local unix/punix endpoints);
> when a matching controller already exists, or the endpoint is
> remote (tcp/ssl), the connections dial immediately.
>
> Note that upgrading to this release adds the first primary
> controller to an existing br-int, which causes OVS to flush
> the OpenFlow tables; ovn-controller reinstalls them on the
> next run.  With fail_mode=secure, br-int traffic on that
> chassis is dropped for the reinstall window.
>
> Reported-at: https://redhat.atlassian.net/browse/FDP-3771
> Assisted-by: Qwen3.8-27B-FP8, OpenCode
> Signed-off-by: Jacob Tanenbaum <[email protected]>
>
> Assisted-by: Qwen3.8-27B-FP8, OpenCode
> Signed-off-by: Jacob Tanenbaum <[email protected]>
>
> ---
>     v3
>     * rebase issues with NEWS file
>     v2
>     * next_cfg incrementing introduced to ensure that no race condition
>       exists by attempting to connect to a socket that does not exists.
>


Some strange issues with the server CA certificate

Recheck-request: github-robot-_Build_and_Test




> ---
>  NEWS                            |  16 +++
>  controller/ovn-controller.8.xml |  21 +++-
>  controller/ovn-controller.c     | 208 ++++++++++++++++++++++++++++++--
>  ovn-architecture.7.xml          |  19 +++
>  tests/ovn-controller.at         |  80 +++++++++++-
>  5 files changed, 329 insertions(+), 15 deletions(-)
>
> diff --git a/NEWS b/NEWS
> index f1c56dd14..67060cbbf 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -1,5 +1,21 @@
>  Post v26.09.0
>  --------------
> +   - ovn-controller now controls the OpenFlow flows of the br-int
> +     integration bridge through a primary OpenFlow controller that it
> +     registers on the bridge (target
> "punix:<run_dir>/br-int.ovn-primary"),
> +     instead of the service controller of the br-int.mgmt management
> +     port.  This lets the bridge's fail-open/fail-secure state machine
> +     track the ovn-controller connection.  When
> +     external_ids:ovn-bridge-remote is a local unix: or punix: target, the
> +     primary controller listens on the same socket ovn-controller dials;
> +     for a remote tcp: or ssl: target the operator must configure the
> +     primary controller separately, e.g. with "ovs-vsctl
> +     set-controller br-int ptcp:<port>".
> +   - Note that upgrading to this release adds the first primary
> +     controller to an existing br-int, which causes OVS to flush the
> +     OpenFlow flow, group, and meter tables.  ovn-controller reinstalls
> +     them on its next run; with fail_mode=secure, br-int traffic on the
> +     chassis is dropped for that window.
>     - Added a new "options:ttl" key on the NB DNS table to make the TTL of
>       DNS replies from OVN's native DNS resolver configurable per row.
>     - Removed implementations of the commit_ecmp_nh, chk_ecmp_nh, and
> diff --git a/controller/ovn-controller.8.xml
> b/controller/ovn-controller.8.xml
> index 3c33654ff..311b51b21 100644
> --- a/controller/ovn-controller.8.xml
> +++ b/controller/ovn-controller.8.xml
> @@ -417,7 +417,26 @@
>        <dd>
>          <p>
>            Connection to the OVN management bridge in OvS. It defaults to
> -          <code>unix:<var>br-int</var>.mgmt</code> when not specified.
> +          <code>unix:<var>br-int</var>.ovn-primary</code> when not
> specified.
> +          This is the primary OpenFlow controller connection for the
> +          integration bridge; the usual
> +          <code>unix:<var>br-int</var>.mgmt</code> management socket
> remains
> +          available to tools such as <code>ovs-ofctl</code>(8) and
> +          <code>ovs-appctl</code>(8).
> +        </p>
> +        <p>
> +          When this option is unset, or is a
> +          <code>unix:</code>/<code>punix:</code> target, the connection is
> +          registered on the bridge as a <code>primary</code> OpenFlow
> +          controller whose target is the passive (listening)
> +          <code>punix:</code> form of this endpoint, so that OVS
> +          fail-open/fail-secure tracks the <code>ovn-controller</code>
> +          connection.  For <code>tcp:</code> or <code>ssl:</code> targets
> the
> +          listener is not hosted by <code>ovn-controller</code>; the
> primary
> +          OpenFlow controller (listener) must be configured separately,
> e.g.
> +          with <code>ovs-vsctl set-controller</code>(8).  In that case OVS
> +          fail-secure tracks that configured controller rather than one
> created
> +          by <code>ovn-controller</code>.
>          </p>
>        </dd>
>        <dt><code>external_ids:ovn-bridge-remote-probe-interval</code></dt>
> diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
> index c601f89dc..275d0ead8 100644
> --- a/controller/ovn-controller.c
> +++ b/controller/ovn-controller.c
> @@ -145,6 +145,7 @@ static unixctl_cb_func debug_delay_nb_cfg_report;
>  #define OVS_NB_CFG_TS_NAME "ovn-nb-cfg-ts"
>  #define OVS_NB_CFG_SB_TS_NAME "ovn-nb-cfg-sb-ts"
>  #define OVS_STARTUP_TS_NAME "ovn-startup-ts"
> +#define BR_INT_PRIMARY_CTRL_ID "ovn-br-int-primary"
>
>  struct br_int_remote {
>      char *target;
> @@ -655,7 +656,131 @@ get_br_datapath(const struct ovsrec_open_vswitch
> *cfg,
>      return NULL;
>  }
>
> -static void
> +/* Returns the primary Controller target ovn-controller should register
> for
> + * the integration bridge, or NULL if it should not register one.
> 'remote' is
> + * the value of external_ids:ovn-bridge-remote, or NULL when unset (in
> which
> + * case the default local "unix:<rundir>/<bridge>.ovn-primary" endpoint is
> + * used).
> + *
> + * The primary Controller must be a listener that ovn-controller itself
> hosts
> + * on the same endpoint its software connection dials, so that OVS's
> + * fail-open/fail-secure state machine (ofproto_is_alive()) can observe
> the
> + * ovn-controller connection.  That is only possible for local endpoints:
> + *   - unix:/punix: (and the default) are local sockets ovn-controller can
> + *     listen on, so the passive "punix:<path>" form is registered.
> + *   - tcp:/ssl: (and ptcp:/pssl:) are remote endpoints; the listener is
> not
> + *     hosted by ovn-controller (it dials the peer).  The operator
> configures
> + *     the primary controller (listener) separately, e.g. with "ovs-vsctl
> + *     set-controller", so nothing is registered here.  Registering a
> local
> + *     stand-in would be wrong (and, for inet targets, the passive form
> uses a
> + *     "[<port>][:<host>]" layout that does not match "host:port").
> + */
> +static char *
> +br_int_primary_target(const char *remote, const struct ovsrec_bridge
> *br_int)
> +{
> +    if (!remote) {
> +        return xasprintf("punix:%s/%s.ovn-primary", ovs_rundir(),
> +                         br_int->name);
> +    }
> +    if (!strncmp(remote, "unix:", 5) || !strncmp(remote, "punix:", 6)) {
> +        return xasprintf("punix:%s", strchr(remote, ':') + 1);
> +    }
> +    return NULL;
> +}
> +
> +/* Ensure the integration bridge has a primary OpenFlow controller that
> + * points at the passive listener ovn-controller connects through (see
> + * br_int_remote_update()).  Making that connection a *primary* controller
> + * (as opposed to the service controller that OVS always adds internally
> for
> + * the <bridge>.mgmt port) causes the bridge's OpenFlow
> fail-open/fail-secure
> + * state machine and ofproto_is_alive() to track the ovn-controller
> + * connection.  A primary controller is registered only for local
> + * (unix/punix) endpoints, for which ovn-controller hosts the listener;
> for
> + * remote (tcp/ssl) endpoints the operator configures the primary
> controller
> + * separately.  This runs regardless of whether the bridge was just
> created
> + * or pre-existed (e.g. created by the distribution's startup scripts).
> + *
> + * When a new row is inserted, Open_vSwitch.next_cfg is incremented in the
> + * same transaction.  This marks the commit that introduces the
> controller,
> + * so the caller can defer the first connection to the primary endpoint
> until
> + * ovs-vswitchd has applied that commit (and thereby created the punix
> + * listener).  Returns true if a new row was inserted (and next_cfg
> + * incremented), false when no row is inserted: either no primary target
> + * applies (remote tcp/ssl endpoint) or a matching primary controller
> already
> + * exists, in which case ovs-vswitchd has already created the punix
> listener
> + * and no wait is needed.
> + */
> +static bool
> +process_br_int_primary_controller(struct ovsdb_idl_txn *ovs_idl_txn,
> +                                  const struct ovsrec_bridge *br_int,
> +                                  const struct ovsrec_open_vswitch *cfg)
> +{
> +    char *primary_target =
> +        br_int_primary_target(
> +            smap_get(&cfg->external_ids, "ovn-bridge-remote"), br_int);
> +
> +    /* Drop any primary controller registered by a previous run that no
> +     * longer matches the configured endpoint (e.g. because
> +     * external_ids:ovn-bridge-remote was changed or removed, or the run
> +     * directory moved).  Only rows created by ovn-controller -- tagged
> with
> +     * BR_INT_PRIMARY_CTRL_ID in their external_ids -- are managed;
> primary
> +     * controllers configured by the operator (e.g. "ovs-vsctl
> +     * set-controller") are left untouched. */
> +    for (size_t i = 0; i < br_int->n_controller; i++) {
> +        const struct ovsrec_controller *c = br_int->controller[i];
> +        /* c->type may be unset (NULL) for controllers configured by the
> +         * operator, so identify rows managed by ovn-controller by the
> +         * external_ids tag, which is safe to read, rather than by type.
> */
> +        if (c
> +            && smap_get_bool(&c->external_ids, BR_INT_PRIMARY_CTRL_ID,
> false)
> +            && (!primary_target
> +                || strcmp(c->target, primary_target))) {
> +            ovsrec_bridge_update_controller_delvalue(br_int, c);
> +            ovsrec_controller_delete(c);
> +        }
> +    }
> +    if (!primary_target) {
> +        return false;
> +    }
> +
> +    /* A primary controller for the configured endpoint already exists;
> keep
> +     * it.  This also covers a matching row created before this release
> +     * tagged it, so it is not duplicated. */
> +    for (size_t i = 0; i < br_int->n_controller; i++) {
> +        const struct ovsrec_controller *c = br_int->controller[i];
> +        if (c && !strcmp(c->target, primary_target)
> +            && c->type && !strcmp(c->type, "primary")) {
> +            free(primary_target);
> +            return false;
> +        }
> +    }
> +
> +    struct ovsrec_controller *primary =
> ovsrec_controller_insert(ovs_idl_txn);
> +    ovsrec_controller_set_target(primary, primary_target);
> +    ovsrec_controller_set_type(primary, "primary");
> +    const struct smap ext_ids =
> +        SMAP_CONST1(&ext_ids, BR_INT_PRIMARY_CTRL_ID, "true");
> +    ovsrec_controller_set_external_ids(primary, &ext_ids);
> +    free(primary_target);
> +    ovsrec_bridge_update_controller_addvalue(br_int, primary);
> +
> +    /* Bump next_cfg in the same transaction as the insert so that
> +     * ovs-vswitchd's cur_cfg ack unambiguously covers the commit that
> +     * introduces this controller.  ovn-controller defers the first
> +     * connection to the primary endpoint until that ack, which guarantees
> +     * the punix listener has been created before it is dialed. */
> +    ovsdb_idl_txn_increment(ovs_idl_txn, &cfg->header_,
> +                            &ovsrec_open_vswitch_col_next_cfg, false);
> +    return true;
> +}
> +
> +/* Returns true when a new primary Controller row is inserted into
> + * 'ovs_idl_txn' (which also increments Open_vSwitch next_cfg in the same
> + * commit).  The caller captures the pre-increment next_cfg value and
> waits
> + * until ovs-vswitchd's cur_cfg advances past it before connecting to the
> + * primary endpoint, guaranteeing the punix listener has been created.
> Returns
> + * false when no row is inserted. */
> +static bool
>  process_br_int(struct ovsdb_idl_txn *ovs_idl_txn,
>                 const struct ovsrec_bridge_table *bridge_table,
>                 const struct ovsrec_open_vswitch_table *ovs_table,
> @@ -663,6 +788,7 @@ process_br_int(struct ovsdb_idl_txn *ovs_idl_txn,
>                 const struct ovsrec_datapath **br_int_dp)
>  {
>      const struct ovsrec_bridge *br_int = get_br_int(bridge_table,
> ovs_table);
> +    bool primary_ctl_inserted = false;
>
>      ovs_assert(br_int_);
>      if (ovs_idl_txn) {
> @@ -699,6 +825,8 @@ process_br_int(struct ovsdb_idl_txn *ovs_idl_txn,
>                  ovsrec_bridge_set_fail_mode(br_int, "secure");
>                  VLOG_WARN("Integration bridge fail-mode changed to
> 'secure'.");
>              }
> +            primary_ctl_inserted = process_br_int_primary_controller(
> +                ovs_idl_txn, br_int, cfg);
>              if (br_int_dp) {
>                  *br_int_dp = get_br_datapath(cfg, datapath_type);
>                  if (!(*br_int_dp)) {
> @@ -711,6 +839,7 @@ process_br_int(struct ovsdb_idl_txn *ovs_idl_txn,
>          }
>      }
>      *br_int_ = br_int;
> +    return primary_ctl_inserted;
>  }
>
>  static void
> @@ -983,6 +1112,8 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
>      ovsdb_idl_add_column(ovs_idl, &ovsrec_open_vswitch_col_other_config);
>      ovsdb_idl_add_column(ovs_idl, &ovsrec_open_vswitch_col_bridges);
>      ovsdb_idl_add_column(ovs_idl, &ovsrec_open_vswitch_col_datapaths);
> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_open_vswitch_col_cur_cfg);
> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_open_vswitch_col_next_cfg);
>      ovsdb_idl_add_table(ovs_idl, &ovsrec_table_interface);
>      ovsdb_idl_add_table(ovs_idl, &ovsrec_table_port);
>      ovsdb_idl_add_table(ovs_idl, &ovsrec_table_bridge);
> @@ -992,6 +1123,11 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
>      ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_flow_tables);
>      ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_other_config);
>      ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_external_ids);
> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_controller);
> +    ovsdb_idl_add_table(ovs_idl, &ovsrec_table_controller);
> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_controller_col_target);
> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_controller_col_type);
> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_controller_col_external_ids);
>      ovsdb_idl_add_table(ovs_idl, &ovsrec_table_flow_table);
>      ovsdb_idl_add_column(ovs_idl, &ovsrec_flow_table_col_prefixes);
>      ovsdb_idl_add_table(ovs_idl, &ovsrec_table_ssl);
> @@ -7820,7 +7956,7 @@ br_int_remote_update(struct br_int_remote *remote,
>              smap_get(&cfg->external_ids, "ovn-bridge-remote");
>      char *target = ext_target
>              ? xstrdup(ext_target)
> -            : xasprintf("unix:%s/%s.mgmt", ovs_rundir(), br_int->name);
> +            : xasprintf("unix:%s/%s.ovn-primary", ovs_rundir(),
> br_int->name);
>
>      if (!remote->target || strcmp(remote->target, target)) {
>          free(remote->target);
> @@ -8182,6 +8318,17 @@ main(int argc, char *argv[])
>      VLOG_INFO("OVN internal version is : [%s]", ovn_version);
>
>      /* Main loop. */
> +    /* Set when process_br_int_primary_controller() inserts a new primary
> +     * controller (and bumps Open_vSwitch next_cfg in the same commit).
> +     * While set, the software connections to the primary endpoint are
> held
> +     * back until ovs-vswitchd has applied that commit, which is the
> change
> +     * that creates the punix listener.  The wait is cleared once cur_cfg
> +     * advances past the pre-increment next_cfg value captured when the
> +     * controller was inserted -- the same ack protocol ovs-vsctl --wait
> +     * relies on.  It is only set for local (unix/punix) endpoints, where
> +     * ovn-controller registers and hosts the primary listener. */
> +    bool primary_ctl_wait = false;
> +    int64_t primary_ctl_base = 0;
>      int ovnsb_txn_status = 1;
>      struct tracked_acl_ids *tracked_acl_ids = NULL;
>      while (!exit_args.exiting) {
> @@ -8273,15 +8420,17 @@ main(int argc, char *argv[])
>          const struct ovsrec_datapath *br_int_dp = NULL;
>          const struct ovsrec_open_vswitch *cfg =
>              ovsrec_open_vswitch_table_first(ovs_table);
> -        process_br_int(ovs_idl_txn, bridge_table, ovs_table, &br_int,
> -                       ovsrec_server_has_datapath_table(ovs_idl_loop.idl)
> -                       ? &br_int_dp
> -                       : NULL);
> -        br_int_remote_update(&br_int_remote, br_int, ovs_table);
> -        statctrl_update_swconn(br_int_remote.target,
> -                               br_int_remote.probe_interval);
> -        pinctrl_update_swconn(br_int_remote.target,
> -                              br_int_remote.probe_interval);
> +
> +        /* ovs-vswitchd has applied the commit that introduced the primary
> +         * controller (cur_cfg advanced past the pre-increment next_cfg
> that
> +         * was in force when the controller was inserted).  The punix
> +         * listener has been created, so the software connections may dial
> +         * it.  On a reconnection this check still holds, because cur_cfg
> +         * only increases and was at most primary_ctl_base when it was
> +         * captured. */
> +        if (primary_ctl_wait && cfg && cfg->cur_cfg > primary_ctl_base) {
> +            primary_ctl_wait = false;
> +        }
>
>          /* Enable ACL matching for double tagged traffic. */
>          if (ovs_idl_txn && cfg) {
> @@ -8311,6 +8460,37 @@ main(int argc, char *argv[])
>              }
>          }
>
> +        /* If a new primary controller was just registered, record the
> +         * pre-increment next_cfg so the software connections can be held
> +         * back until ovs-vswitchd applies the commit that creates the
> +         * punix listener. */
> +        bool inserted_primary_ctl = process_br_int(
> +            ovs_idl_txn, bridge_table, ovs_table, &br_int,
> +            ovsrec_server_has_datapath_table(ovs_idl_loop.idl)
> +            ? &br_int_dp
> +            : NULL);
> +        if (inserted_primary_ctl && cfg) {
> +            primary_ctl_wait = true;
> +            primary_ctl_base = cfg->next_cfg;
> +        }
> +
> +        /* All the software connections dial the endpoint ovn-controller
> +         * manages.  While the punix listener has not been confirmed
> created
> +         * (primary_ctl_wait), pass NULL so they are held back (or
> +         * disconnected) instead of dialing.  On the first pass
> +         * br_int_remote.target is still NULL, so no connection is dialed
> +         * before it is set. */
> +        const char *swconn_target = primary_ctl_wait
> +            ? NULL
> +            : br_int_remote.target;
> +        if (br_int && swconn_target) {
> +            statctrl_update_swconn(swconn_target,
> +                                   br_int_remote.probe_interval);
> +            pinctrl_update_swconn(swconn_target,
> +                                  br_int_remote.probe_interval);
> +        }
> +        br_int_remote_update(&br_int_remote, br_int, ovs_table);
> +
>          static bool chassis_idx_stored = false;
>          if (ovs_idl_txn && !chassis_idx_stored) {
>              store_chassis_index_if_needed(ovs_table);
> @@ -8349,11 +8529,13 @@ main(int argc, char *argv[])
>
>              /* If any OVS feature support changed, force a full recompute.
>               * 'br_int_dp' is valid only if an OVS transaction is
> possible.
> +             * While the primary listener has not been confirmed created,
> +             * pass NULL to disconnect instead of dialing it.
>               */
>              if (ovs_idl_txn
>                  && ovs_feature_support_run(br_int_dp ?
>                                             &br_int_dp->capabilities :
> NULL,
> -                                           br_int_remote.target,
> +                                           swconn_target,
>                                             br_int_remote.probe_interval))
> {
>                  VLOG_INFO("OVS feature set changed, force recompute.");
>                  engine_set_force_recompute();
> @@ -8369,7 +8551,7 @@ main(int argc, char *argv[])
>
>              if (br_int) {
>                  ct_zones_data = engine_get_data(&en_ct_zones);
> -                if (ofctrl_run(br_int_remote.target,
> +                if (ofctrl_run(swconn_target,
>                                 br_int_remote.probe_interval,
>                                 ct_zones_data ? &ct_zones_data->ctx.pending
>                                               : NULL,
> diff --git a/ovn-architecture.7.xml b/ovn-architecture.7.xml
> index 00728ee9b..3022be9f2 100644
> --- a/ovn-architecture.7.xml
> +++ b/ovn-architecture.7.xml
> @@ -381,6 +381,25 @@
>        Settings</code> in <code>ovs-vsctl</code>(8) for more information.
>      </dd>
>
> +    <dt>a <code>primary</code> <code>Controller</code></dt>
> +    <dd>
> +      <code>ovn-controller</code> connects to the integration bridge
> through a
> +      <code>Controller</code> row of type <code>primary</code> rather than
> +      through the service controller that OVS always adds internally for
> the
> +      <code>br-int.mgmt</code> port.  The row's target is the passive
> listener
> +      <code>punix:<var>run_dir</var>/br-int.ovn-primary</code> and the
> +      <code>ovn-controller</code> OpenFlow connections dial the
> corresponding
> +      <code>unix:</code> form of the same path.  Because the connection
> is a
> +      <em>primary</em> controller, the bridge's OpenFlow
> fail-open/fail-secure
> +      state machine and <code>ofproto_is_alive()</code> track the
> +      <code>ovn-controller</code> connection.  When
> +      <code>external_ids:ovn-bridge-remote</code> names a local
> +      <code>unix:</code> or <code>punix:</code> target, the listener is
> +      created on that path instead; for a remote <code>tcp:</code> or
> +      <code>ssl:</code> target the operator configures the primary
> controller
> +      separately, e.g. with <code>ovs-vsctl set-controller</code>(8).
> +    </dd>
> +
>      <dt><code>other-config:disable-in-band=true</code></dt>
>      <dd>
>        Suppresses in-band control flows for the integration bridge.  It
> would be
> diff --git a/tests/ovn-controller.at b/tests/ovn-controller.at
> index a7b79fc67..0b0080a0a 100644
> --- a/tests/ovn-controller.at
> +++ b/tests/ovn-controller.at
> @@ -3319,6 +3319,83 @@ OVS_WAIT_UNTIL([grep -q 'tcp:127.0.0.1:1235:
> connected' hv1/ovn-controller.log])
>  OVN_CLEANUP([hv1])
>  AT_CLEANUP
>
> +AT_SETUP([ovn-controller - br-int primary controller])
> +AT_KEYWORDS([ovn])
> +ovn_start
> +
> +net_add n1
> +sim_add hv1
> +ovs-vsctl add-br br-phys
> +ovn_attach n1 br-phys 192.168.0.20
> +
> +# ovn-controller registers itself as a primary OpenFlow controller of the
> +# integration bridge, so OVS fail-open/fail-secure tracks the
> +# ovn-controller connection.  This holds whether the bridge was just
> created
> +# or pre-existed (ovn_attach pre-creates br-int here).
> +OVS_WAIT_UNTIL([test x$(ovs-vsctl --columns=type --bare list Controller)
> = "xprimary"])
> +AT_CHECK([ovs-vsctl --columns=type --bare list Controller], [0], [primary
> +])
> +# The bridge keeps its fail-mode=secure setting.
> +AT_CHECK([ovs-vsctl --columns=fail_mode --bare list bridge br-int], [0],
> [secure
> +])
> +
> +# ovn-controller connects through the primary (not the default .mgmt)
> socket.
> +OVS_WAIT_UNTIL([grep -q 'connecting to switch:
> "unix:.*br-int\.ovn-primary"' hv1/ovn-controller.log])
> +OVS_WAIT_UNTIL([grep -q 'br-int\.ovn-primary: connected'
> hv1/ovn-controller.log])
> +
> +OVN_CLEANUP([hv1])
> +AT_CLEANUP
> +
> +AT_SETUP([ovn-controller - br-int primary controller with
> ovn-bridge-remote])
> +AT_KEYWORDS([ovn])
> +ovn_start
> +
> +net_add n1
> +sim_add hv1
> +ovs-vsctl add-br br-phys
> +ovn_attach n1 br-phys 192.168.0.20
> +
> +# With external_ids:ovn-bridge-remote unset, ovn-controller registers the
> +# default primary controller on the default local listener.
> +OVS_WAIT_UNTIL([test x$(ovs-vsctl --columns=target --bare find Controller
> type=primary) = "xpunix:$ovs_base/hv1/br-int.ovn-primary"])
> +
> +# A local unix: override: the primary controller moves to the passive
> form of
> +# the overridden path, and the previous (stale) primary is removed.
> +check ovs-vsctl set open .
> external_ids:ovn-bridge-remote=unix:$ovs_base/hv1/br-int.ovn-override
> +OVS_WAIT_UNTIL([test x$(ovs-vsctl --columns=target --bare find Controller
> type=primary) = "xpunix:$ovs_base/hv1/br-int.ovn-override"])
> +
> +# Remove the local override: ovn-controller restores the default primary.
> +check ovs-vsctl remove open . external_ids ovn-bridge-remote
> +OVS_WAIT_UNTIL([test x$(ovs-vsctl --columns=target --bare find Controller
> type=primary) = "xpunix:$ovs_base/hv1/br-int.ovn-primary"])
> +
> +# A remote tcp: override: the operator configures the primary listener
> +# (separately, since ovn-controller cannot host a remote listener) and
> then
> +# points the override at it.  ovn-controller dials the endpoint, removes
> the
> +# stale local primary, and registers no primary controller of its own; the
> +# operator-configured controller (which has no type) is left alone.
> +check ovs-vsctl set-controller br-int ptcp:1276
> +# "set-controller" wiped the controllers; with the override unset,
> +# ovn-controller re-registers the default primary.
> +OVS_WAIT_UNTIL([test x$(ovs-vsctl --columns=target --bare find Controller
> type=primary) = "xpunix:$ovs_base/hv1/br-int.ovn-primary"])
> +check ovs-vsctl set open . external_ids:ovn-bridge-remote=tcp:
> 127.0.0.1:1276
> +OVS_WAIT_UNTIL([test x$(ovs-vsctl --columns=target --bare find
> Controller) = "xptcp:1276"])
> +OVS_WAIT_UNTIL([grep -q 'tcp:127.0.0.1:1276: connected'
> hv1/ovn-controller.log])
> +
> +# Removing the override: ovn-controller registers the default primary
> again,
> +# keeping the operator-configured controller, and the swconn reconnects
> +# through the default socket.
> +check ovs-vsctl remove open . external_ids ovn-bridge-remote
> +OVS_WAIT_UNTIL([test x$(ovs-vsctl --columns=target --bare find Controller
> type=primary) = "xpunix:$ovs_base/hv1/br-int.ovn-primary"])
> +OVS_WAIT_UNTIL([test $(grep -c 'ofctrl: connecting to switch:
> "unix:'$ovs_base'/hv1/br-int.ovn-primary"' hv1/ovn-controller.log) -ge 2])
> +# Both controllers remain: the operator-configured one and the default
> +# primary.  "find --bare" separates rows with a blank line, so count the
> +# non-empty lines.
> +OVS_WAIT_UNTIL([test 2 = $(ovs-vsctl --columns=target --bare find
> Controller | grep -c .)])
> +OVS_WAIT_UNTIL([ovs-vsctl --columns=target --bare find Controller | grep
> -qx ptcp:1276])
> +
> +OVN_CLEANUP([hv1])
> +AT_CLEANUP
> +
>  AT_SETUP([ovn-controller - br-int flow table prefixes])
>  AT_KEYWORDS([ovn-controller prefixes])
>  ovn_start
> @@ -3664,8 +3741,9 @@ AT_CHECK([ovsdb-client --bare dump unix:db.sock
> Open_vSwitch Open_vSwitch], [],
>  Open_vSwitch table
>  ])
>  check ovs-vsctl --no-wait init
> -OVS_WAIT_FOR_OUTPUT([ovs-vsctl show | tail -n +2], [], [dnl
> +OVS_WAIT_FOR_OUTPUT_UNQUOTED([ovs-vsctl show | tail -n +2], [], [dnl
>      Bridge br-int
> +        Controller "punix:$(pwd)/br-int.ovn-primary"
>          fail_mode: secure
>          datapath_type: system
>          Port br-int
> --
> 2.55.0
>
>
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to