Hi Rosemarie
Thanks for the patch.
I am not sure the patch fully fixes the issue.
When a localnet port is added, I think that we have the following steps:
0. ovn-nbctl --wait=hv lsp-add ... ln-port
1. ovn-controller creates patch port in OVSDB.
2a. ovn-controller is notified about patch port creation.
2b. ovs-vswitchd is notified about port creation and assigns an ofport. It
writes the ofport back to OVSDB.
3. ovn-controller is notified about ofport creation. It computes flows
using that ofport and sends the flows plus a barrier to ovs-vswitchd.
4. ovn-controller sees barrier ack from ovs.
Before your patch, nbctl would return after step 1.
After the patch, it might return after step 2a.
I think it should only return after step 4, i.e., when all flows related to
the ln-port have been added.
WDYT?
I've created the following test which I think reproduces a possible race
condition:
OVN_FOR_EACH_NORTHD([
AT_SETUP([XXXX])
ovn_start
check ovn-nbctl ls-add ls1
check ovn-nbctl --wait=sb add Logical-Switch ls1 other_config
vlan-passthru=true
net_add n
sim_add hv1
as hv1
ovs-vsctl add-br br-phys
ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
ovn_attach n br-phys 192.168.0.1
check ovn-nbctl --wait=hv sync
check ovn-nbctl lsp-add ls1 lsp1
as hv1 ovs-vsctl --no-wait -- add-port br-int vif1 \
-- set Interface vif1 external_ids:iface-id=lsp1 \
-- set Interface vif1 type=internal
wait_for_ports_up
check ovn-nbctl --wait=hv sync
n_flows=$(ovs-ofctl dump-flows br-int | wc -l)
sleep_ovsdb hv1
ovn-appctl vlog/set dbg
check ovn-nbctl --wait=sb lsp-add-localnet-port ls1 ln1 phys
# Patch port should be created, but ovsdb sleeping ...
# Wait for ovs-vswitchd to handle flow addition (related to patch port and
unrelated to ofport(patch_port)).
echo "Waiting for $n_flows to increase"
OVS_WAIT_UNTIL([test "$(ovs-ofctl dump-flows br-int | wc -l)" -gt $n_flows])
# Now put ovs-vswitchd on pause, so it does not handle (yet) port creation
when reported by ovsdb.
sleep_ovs hv1
wake_up_ovsdb hv1
# ovn has now been notified about patch port creation, but ovs ofport is
not created yet as ovs is sleeping.
if ! ovn-nbctl --timeout=3 --wait=hv sync; then
# sync fails (as expected as previous lsp-add not full completed).
Resume ovs-vswitchd.
wake_up_ovs hv1
fi
check ovn-nbctl --wait=hv sync
# Sync succeeded. Pause ovn-controller as it should now have handled all
flow creations (as sync succeeded).
sleep_controller hv1
# Wake up ovs-vswitchd in case it was still stopped (so we can check flows).
wake_up_ovs hv1
ovs-vsctl wait-until Interface patch-br-int-to-ln1 "ofport>0"
ofport=$(ovs-vsctl --bare --columns ofport find Interface
name=patch-br-int-to-ln1)
AT_CHECK([ovs-ofctl dump-flows br-int table=OFTABLE_PHY_TO_LOG | grep -q
in_port=$ofport], [0], [])
# Finally wake-up controller for proper cleanup.
wake_up_controller hv1
OVN_CLEANUP([hv1])
AT_CLEANUP
])
WDYT?
Thanks
Xavier
On Wed, Sep 23, 2026 at 4:59 AM Rosemarie O'Riorden <[email protected]>
wrote:
> ovn-controller would continue with the next iteration before waiting
> for patch ports to finish updating.
>
> Thus when using --wait=hv for a localnet port operation, ovn-nbctl would
> not actually wait for patch ports. This sometimes led to failures in
> the "localnet port change and chassisredirect bridged redirect" test.
>
> To remedy this issue, functions performing patch port operations now
> report a status, and ovn-controller will not increment nb_cfg if they
> show not to be complete. This allows --wait=hv to actually wait as
> intended.
>
> Fixes: 84748d013155 ("ovn: Make it possible for CMS to detect when the OVN
> system is up-to-date.")
> Reported-at: https://issues.redhat.com/browse/FDP-4156
> Assisted-by: Claude Opus 5, Claude Code
> Signed-off-by: Rosemarie O'Riorden <[email protected]>
> ---
> controller/ovn-controller.c | 30 +++++++----
> controller/patch.c | 101 +++++++++++++++++++++++++-----------
> controller/patch.h | 2 +-
> tests/ovn.at | 21 +++++---
> 4 files changed, 105 insertions(+), 49 deletions(-)
>
> diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
> index c601f89dc..03d2fab53 100644
> --- a/controller/ovn-controller.c
> +++ b/controller/ovn-controller.c
> @@ -8485,15 +8485,17 @@ main(int argc, char *argv[])
> }
> }
>
> + bool patch_ports_synced = true;
> +
> runtime_data = engine_get_data(&en_runtime_data);
> if (runtime_data) {
> stopwatch_start(PATCH_RUN_STOPWATCH_NAME,
> time_msec());
> - patch_run(ovs_idl_txn,
> - sbrec_port_binding_by_type,
> + patch_ports_synced = patch_run(
> + ovs_idl_txn, sbrec_port_binding_by_type,
> ovsrec_bridge_table_get(ovs_idl_loop.idl),
>
> ovsrec_open_vswitch_table_get(ovs_idl_loop.idl),
> - ovsrec_port_by_name,
> - br_int, chassis,
> &runtime_data->local_datapaths);
> + ovsrec_port_by_name, br_int, chassis,
> + &runtime_data->local_datapaths);
> stopwatch_stop(PATCH_RUN_STOPWATCH_NAME,
> time_msec());
> if (vif_plug_provider_has_providers() &&
> ovs_idl_txn) {
> struct vif_plug_ctx_in vif_plug_ctx_in = {
> @@ -8617,13 +8619,19 @@ main(int argc, char *argv[])
> * eventual completion can be attributed to the
> * timestamp that corresponded to this exact nb_cfg
> * generation -- not whatever SB_Global value has
> - * moved on to by the time the barrier acks. */
> - struct nb_cfg_snap snap = get_nb_cfg(
> - sbrec_sb_global_table_get(ovnsb_idl_loop.idl),
> - ovnsb_cond_seqno, ovnsb_expected_cond_seqno);
> -
> ofctrl_stamped_seqno_update_create(ofctrl_seq_type_nb_cfg,
> - snap.nb_cfg,
> - snap.ts);
> + * moved on to by the time the barrier acks.
> + *
> + * Wait until the local OVS database matches the patch
> + * ports we need. Until then, the flows that use
> those
> + * ports can't be installed, so reporting nb_cfg would
> + * claim the configuration is applied before it is. */
> + if (patch_ports_synced) {
> + struct nb_cfg_snap snap = get_nb_cfg(
> + sbrec_sb_global_table_get(ovnsb_idl_loop.idl),
> + ovnsb_cond_seqno, ovnsb_expected_cond_seqno);
> + ofctrl_stamped_seqno_update_create(
> + ofctrl_seq_type_nb_cfg, snap.nb_cfg, snap.ts);
> + }
>
> struct local_binding_data *binding_data =
> runtime_data ? &runtime_data->lbinding_data :
> NULL;
> diff --git a/controller/patch.c b/controller/patch.c
> index 4fed6e375..7238b8222 100644
> --- a/controller/patch.c
> +++ b/controller/patch.c
> @@ -71,10 +71,13 @@ match_patch_port(const struct ovsrec_port *port, const
> char *peer)
>
> /* Creates a patch port in bridge 'src' named 'src_name', whose peer is
> * 'dst_name' in bridge 'dst'. Initializes the patch port's
> external-ids:'key'
> - * to 'key'.
> + * to 'key'. The port is only created if 'ovs_idl_txn' is non-NULL.
> *
> - * If such a patch port already exists, removes it from 'existing_ports'.
> */
> -static void
> + * If such a patch port already exists in 'src', removes it from
> + * 'existing_ports' and returns false.
> + *
> + * Otherwise, creates port (if 'ovs_idl_txn') and returns true. */
> +static bool
> create_patch_port(struct ovsdb_idl_txn *ovs_idl_txn,
> const char *key, const char *value,
> const struct ovsrec_bridge *src, const char *src_name,
> @@ -85,10 +88,14 @@ create_patch_port(struct ovsdb_idl_txn *ovs_idl_txn,
> if (match_patch_port(src->ports[i], dst_name)) {
> /* Patch port already exists on 'src'. */
> shash_find_and_delete(existing_ports, src->ports[i]->name);
> - return;
> + return false;
> }
> }
>
> + if (!ovs_idl_txn) {
> + goto exit;
> + }
> +
> ovsdb_idl_txn_add_comment(ovs_idl_txn,
> "ovn-controller: creating patch port '%s' from '%s' to '%s'",
> src_name, src->name, dst->name);
> @@ -97,6 +104,8 @@ create_patch_port(struct ovsdb_idl_txn *ovs_idl_txn,
> const struct smap port_ids = SMAP_CONST1(&port_ids, key, value);
> ovsport_create(ovs_idl_txn, src, src_name, "patch", &port_ids, NULL,
> &if_options, 0);
> +exit:
> + return true;
> }
>
> static void
> @@ -165,7 +174,12 @@ add_ovs_bridge_mappings(const struct
> ovsrec_open_vswitch_table *ovs_table,
> }
> }
>
> -static void
> +/* Adds the patch ports needed by the port bindings of type 'pb_type'
> that are
> + * local to this chassis.
> + *
> + * Returns true if all of those patch ports are already present in the
> + * database. */
> +static bool
> add_bridge_mappings_by_type(struct ovsdb_idl_txn *ovs_idl_txn,
> struct ovsdb_idl_index
> *sbrec_port_binding_by_type,
> const struct ovsrec_bridge *br_int,
> @@ -178,6 +192,8 @@ add_bridge_mappings_by_type(struct ovsdb_idl_txn
> *ovs_idl_txn,
> {
> struct sbrec_port_binding *target =
> sbrec_port_binding_index_init_row(sbrec_port_binding_by_type);
> + bool synced = true;
> +
> sbrec_port_binding_index_set_type(target, pb_type);
>
> const struct sbrec_port_binding *binding;
> @@ -225,20 +241,32 @@ add_bridge_mappings_by_type(struct ovsdb_idl_txn
> *ovs_idl_txn,
>
> char *name1 = patch_port_name(br_int->name,
> binding->logical_port);
> char *name2 = patch_port_name(binding->logical_port,
> br_int->name);
> - create_patch_port(ovs_idl_txn, patch_port_id,
> binding->logical_port,
> - br_int, name1, br_ln, name2, existing_ports);
> - create_patch_port(ovs_idl_txn, patch_port_id,
> binding->logical_port,
> - br_ln, name2, br_int, name1, existing_ports);
> + bool br_int_port_exists =
> + !create_patch_port(ovs_idl_txn, patch_port_id,
> + binding->logical_port,
> + br_int, name1, br_ln, name2,
> + existing_ports);
> + bool br_ln_port_exists =
> + !create_patch_port(ovs_idl_txn, patch_port_id,
> + binding->logical_port,
> + br_ln, name2, br_int, name1,
> + existing_ports);
> +
> + synced = synced && br_int_port_exists && br_ln_port_exists;
> free(name1);
> free(name2);
> }
> sbrec_port_binding_index_destroy_row(target);
> + return synced;
> }
>
> /* Obtains external-ids:ovn-bridge-mappings from OVSDB and adds patch
> ports for
> - * the local bridge mappings. Removes any patch ports for bridge
> mappings that
> - * already existed from 'existing_ports'. */
> -static void
> + * the local bridge mappings. Removes any patch ports, for bridge
> mappings
> + * that already existed, from 'existing_ports'.
> + *
> + * Returns true if all of the required patch ports are already present in
> the
> + * database. */
> +static bool
> add_bridge_mappings(struct ovsdb_idl_txn *ovs_idl_txn,
> struct ovsdb_idl_index *sbrec_port_binding_by_type,
> const struct ovsrec_bridge_table *bridge_table,
> @@ -253,10 +281,12 @@ add_bridge_mappings(struct ovsdb_idl_txn
> *ovs_idl_txn,
>
> add_ovs_bridge_mappings(ovs_table, bridge_table, &bridge_mappings);
>
> - add_bridge_mappings_by_type(ovs_idl_txn, sbrec_port_binding_by_type,
> - br_int, existing_ports, chassis,
> - &bridge_mappings, "l2gateway",
> - "ovn-l2gateway-port", local_datapaths,
> true);
> + bool l2gateway_synced =
> + add_bridge_mappings_by_type(ovs_idl_txn,
> sbrec_port_binding_by_type,
> + br_int, existing_ports, chassis,
> + &bridge_mappings, "l2gateway",
> + "ovn-l2gateway-port", local_datapaths,
> + true);
>
> /* Since having localnet ports that are not mapped on some chassis is
> a
> * supported configuration used to implement multisegment switches
> with
> @@ -264,11 +294,15 @@ add_bridge_mappings(struct ovsdb_idl_txn
> *ovs_idl_txn,
> * run but don't unnecessarily pollute the log file; pass
> * 'log_missing_bridge = false'.
> */
> - add_bridge_mappings_by_type(ovs_idl_txn, sbrec_port_binding_by_type,
> - br_int, existing_ports, NULL,
> - &bridge_mappings, "localnet",
> - "ovn-localnet-port", local_datapaths,
> false);
> + bool localnet_synced =
> + add_bridge_mappings_by_type(ovs_idl_txn,
> sbrec_port_binding_by_type,
> + br_int, existing_ports, NULL,
> + &bridge_mappings, "localnet",
> + "ovn-localnet-port", local_datapaths,
> + false);
> +
> shash_destroy(&bridge_mappings);
> + return l2gateway_synced && localnet_synced;
> }
>
> static const struct ovsrec_port *
> @@ -285,7 +319,14 @@ get_port(struct ovsdb_idl_index *ovsrec_port_by_name,
> const char *name)
> return port;
> }
>
> -void
> +/* Adds to the local OVS database the patch ports required by the localnet
> + * and l2gateway ports that are local to this chassis and removes the ones
> + * that are no longer needed. The database is only updated if
> 'ovs_idl_txn'
> + * is non-NULL.
> + *
> + * Returns true if the database already reflects the required set of patch
> + * ports. */
> +bool
> patch_run(struct ovsdb_idl_txn *ovs_idl_txn,
> struct ovsdb_idl_index *sbrec_port_binding_by_type,
> const struct ovsrec_bridge_table *bridge_table,
> @@ -295,10 +336,6 @@ patch_run(struct ovsdb_idl_txn *ovs_idl_txn,
> const struct sbrec_chassis *chassis,
> const struct hmap *local_datapaths)
> {
> - if (!ovs_idl_txn) {
> - return;
> - }
> -
> /* Figure out what patch ports already exist.
> *
> * ovn-controller does not create or use ports of type
> "ovn-l3gateway-port"
> @@ -335,9 +372,14 @@ patch_run(struct ovsdb_idl_txn *ovs_idl_txn,
> /* Create in the database any patch ports that should exist. Remove
> from
> * 'existing_ports' any patch ports that do exist in the database and
> * should be there. */
> - add_bridge_mappings(ovs_idl_txn, sbrec_port_binding_by_type,
> bridge_table,
> - ovs_table, br_int, &existing_ports, chassis,
> - local_datapaths);
> + bool synced = add_bridge_mappings(ovs_idl_txn,
> sbrec_port_binding_by_type,
> + bridge_table, ovs_table, br_int,
> + &existing_ports, chassis,
> + local_datapaths);
> +
> + if (!shash_is_empty(&existing_ports)) {
> + synced = false;
> + }
>
> /* Now 'existing_ports' only still contains patch ports that exist in
> the
> * database but shouldn't. Delete them from the database. */
> @@ -350,9 +392,10 @@ patch_run(struct ovsdb_idl_txn *ovs_idl_txn,
> * data is not completely downloaded yet after last restart of
> * ovn-controller. Otherwise it may cause unncessary dataplane
> * interruption during restart/upgrade. */
> - if (!daemon_started_recently()) {
> + if (!daemon_started_recently() && ovs_idl_txn) {
> remove_port(bridge_table, port);
> }
> }
> shash_destroy(&existing_ports);
> + return synced;
> }
> diff --git a/controller/patch.h b/controller/patch.h
> index db4a888e6..26ebf401c 100644
> --- a/controller/patch.h
> +++ b/controller/patch.h
> @@ -36,7 +36,7 @@ struct shash;
> void add_ovs_bridge_mappings(const struct ovsrec_open_vswitch_table
> *ovs_table,
> const struct ovsrec_bridge_table
> *bridge_table,
> struct shash *bridge_mappings);
> -void patch_run(struct ovsdb_idl_txn *ovs_idl_txn,
> +bool patch_run(struct ovsdb_idl_txn *ovs_idl_txn,
> struct ovsdb_idl_index *sbrec_port_binding_by_type,
> const struct ovsrec_bridge_table *,
> const struct ovsrec_open_vswitch_table *,
> diff --git a/tests/ovn.at b/tests/ovn.at
> index 6e05809f4..9445c40e3 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -40238,14 +40238,19 @@ check ovn-nbctl --wait=hv sync
>
> sleep_ovsdb hv1
>
> -# Do following operations through one nbctl command
> -# Otherwise, they would probably result in multiple sb updates to
> ovn-controller
> -# As ovsdb is sleeping those multiple updates would resulti in requests
> to ovsdb being
> -# postponed as ovsdb becomes ro.
> -# Hence, patch port creation would be delayed after ovsdb becomes rw.
> -check ovn-nbctl --wait=hv lsp-add-localnet-port ls1 ln1 phys
> -
> -# Controller should now have created the patch port, but is not yet
> notified of the ofport of those interfaces
> +# Create a localnet port and wait for ovn-controller to process the
> change.
> +# --wait=hv cannot be used here because hv1's ovsdb is asleep.
> +# Make sure ovn-controller has not updated nb_cfg.
> +runtime_data_runs=$(as hv1 ovn-appctl -t ovn-controller
> inc-engine/show-stats runtime_data compute)
> +nb_cfg="$(fetch_column nb:NB_Global nb_cfg)"
> +
> +check ovn-nbctl --wait=sb lsp-add-localnet-port ls1 ln1 phys
> +
> +OVS_WAIT_UNTIL([test $(as hv1 ovn-appctl -t ovn-controller
> inc-engine/show-stats runtime_data compute) -gt $runtime_data_runs])
> +check_row_count Chassis_Private 1 name=hv1 nb_cfg="${nb_cfg}"
> +
> +# Controller should now have sent transaction to create the patch port,
> but it
> +# has not yet been received by ovsdb-server.
> sleep_controller hv1
> wake_up_ovsdb hv1
> check as hv1 ovs-vsctl remove Interface vif1 external_ids iface-id
> --
> 2.55.0
>
>
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev