On Thu, Oct 15, 2026 at 7:34 AM MJ Ponsonby <[email protected]> wrote:
>
> From: Xavier Simonart <[email protected]>
>
> Transaction failures could occur as trying to plug multiple times
> the same interface in ovsdb i.e.
> "Transaction causes multiple rows in \"Interface\" table to have identical
> values".
> This could for instance occur between the time the VIF is plug first and
> an ofport is created.
>
> Reported-at: https://bugs.launchpad.net/networking-ovn/+bug/2170570
> Signed-off-by: Xavier Simonart <[email protected]>
> Reviewed-by: Ales Musil <[email protected]>
> Signed-off-by: Numan Siddique <[email protected]>
> (cherry picked from commit fcc2bf154ea591ec267940ba9ba9ef3fa16af6ab)
> Assisted-by: OpenCode (openai/gpt-6-astra)
> Signed-off-by: MJ Ponsonby <[email protected]>
> ---
> Backport to branch-24.03, based on dd45c938817e116e42f9a4f42be4c501d205bf14.
>
> The linked report describes duplicate OVSDB rows blocking ovn-controller
> transactions on a 24.03.8 DPU deployment. This backport is proposed to
> address that failure mode; validation on the affected hardware is pending.
>
> Only patch context is refreshed, with the regression test appended to the
> 24.03 test suite. The code and test changes match the upstream commit.
Thanks for the backport. branch-24.03 is EOL and we have locked this branch.
I guess you need to maintain a downstream version yourself with this fix.
@Dumitru Ceara correct me if I'm wrong here.
Numan
>
> Testing:
> - Built OVN against its pinned OVS revision
> bc99439446a1c0b94498ad7ea44f3d1d933ead90.
> - make check TESTSUITEFLAGS='-j4 -k vif-plug': all 4 tests passed,
> including both variants of the transaction-failure regression test.
> - git diff --check and git apply --check passed.
>
> controller/binding.c | 2 +-
> controller/binding.h | 3 +
> controller/ovn-controller.c | 7 +++
> controller/vif-plug.c | 117 ++++++++++++++++++++++--------------
> controller/vif-plug.h | 1 +
> tests/ovn.at | 53 ++++++++++++++++
> 6 files changed, 137 insertions(+), 46 deletions(-)
>
> diff --git a/controller/binding.c b/controller/binding.c
> index 199d08915..4d9715a4f 100644
> --- a/controller/binding.c
> +++ b/controller/binding.c
> @@ -2528,7 +2528,7 @@ is_iface_vif(const struct ovsrec_interface *iface_rec)
> return true;
> }
>
> -static bool
> +bool
> is_iface_in_int_bridge(const struct ovsrec_interface *iface,
> const struct ovsrec_bridge *br_int)
> {
> diff --git a/controller/binding.h b/controller/binding.h
> index 055b080a9..e954cd6a6 100644
> --- a/controller/binding.h
> +++ b/controller/binding.h
> @@ -283,4 +283,7 @@ void claimed_lport_set_up(const struct sbrec_port_binding
> *pb,
> bool port_binding_is_up(const struct sbrec_chassis *chassis_rec,
> const struct sbrec_port_binding_table *,
> const struct uuid *pb_uuid);
> +bool is_iface_in_int_bridge(const struct ovsrec_interface *iface,
> + const struct ovsrec_bridge *br_int);
> +
> #endif /* controller/binding.h */
> diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
> index 88e0b44ed..5ace54e17 100644
> --- a/controller/ovn-controller.c
> +++ b/controller/ovn-controller.c
> @@ -5268,6 +5268,9 @@ main(int argc, char *argv[])
> struct ovsdb_idl_index *ovsrec_port_by_qos
> = ovsdb_idl_index_create1(ovs_idl_loop.idl,
> &ovsrec_port_col_qos);
> + struct ovsdb_idl_index *ovsrec_interface_by_name
> + = ovsdb_idl_index_create1(ovs_idl_loop.idl,
> + &ovsrec_interface_col_name);
> struct ovsdb_idl_index *ovsrec_queue_by_external_ids
> = ovsdb_idl_index_create1(ovs_idl_loop.idl,
> &ovsrec_queue_col_external_ids);
> @@ -5658,6 +5661,8 @@ main(int argc, char *argv[])
> engine_ovsdb_node_add_index(&en_ovs_flow_sample_collector_set, "id",
> ovsrec_flow_sample_collector_set_by_id);
> engine_ovsdb_node_add_index(&en_ovs_port, "qos", ovsrec_port_by_qos);
> + engine_ovsdb_node_add_index(&en_ovs_interface, "name",
> + ovsrec_interface_by_name);
> engine_ovsdb_node_add_index(&en_ovs_queue, "external_ids",
> ovsrec_queue_by_external_ids);
>
> @@ -6030,6 +6035,8 @@ main(int argc, char *argv[])
> sbrec_port_binding_by_requested_chassis,
> .ovsrec_port_by_interfaces =
> ovsrec_port_by_interfaces,
> + .ovsrec_interface_by_name =
> + ovsrec_interface_by_name,
> .ovs_table = ovs_table,
> .br_int = br_int,
> .iface_table =
> diff --git a/controller/vif-plug.c b/controller/vif-plug.c
> index d4c7552ea..1fa536cad 100644
> --- a/controller/vif-plug.c
> +++ b/controller/vif-plug.c
> @@ -208,6 +208,22 @@ transact_update_port(const struct ovsrec_interface
> *iface_rec,
> mtu_request);
> }
>
> +static const struct ovsrec_interface *
> +iface_lookup_by_name(struct ovsdb_idl_index *ovsrec_interface_by_name,
> + char *name)
> +{
> + struct ovsrec_interface *iface = ovsrec_interface_index_init_row(
> + ovsrec_interface_by_name);
> + ovsrec_interface_set_name(iface, name);
> +
> + const struct ovsrec_interface *retval
> + = ovsrec_interface_index_find(ovsrec_interface_by_name,
> + iface);
> +
> + ovsrec_interface_index_destroy_row(iface);
> +
> + return retval;
> +}
>
> static bool
> consider_unplug_iface(const struct ovsrec_interface *iface,
> @@ -287,11 +303,10 @@ get_plug_mtu_request(const struct smap *lport_options)
> }
>
> static bool
> -consider_plug_lport_create__(const struct vif_plug_class *vif_plug,
> - const struct smap *iface_external_ids,
> - const struct sbrec_port_binding *pb,
> - struct vif_plug_ctx_in *vif_plug_ctx_in,
> - struct vif_plug_ctx_out *vif_plug_ctx_out)
> +consider_plug_lport__(const struct vif_plug_class *vif_plug,
> + const struct sbrec_port_binding *pb,
> + struct vif_plug_port_ctx **vif_plug_port_ctx_ptr,
> + struct vif_plug_ctx_in *vif_plug_ctx_in)
> {
> if (!vif_plug_ctx_in->chassis_rec || !vif_plug_ctx_in->br_int
> || !vif_plug_ctx_in->ovs_idl_txn) {
> @@ -317,13 +332,22 @@ consider_plug_lport_create__(const struct
> vif_plug_class *vif_plug,
> &vif_plug_port_ctx->vif_plug_port_ctx_out)) {
> static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
> VLOG_INFO_RL(&rl,
> - "Not plugging lport %s on direction from VIF plug "
> - "provider.",
> + "Not plugging/updating lport %s on direction from VIF "
> + "plug provider.",
> pb->logical_port);
> destroy_port_ctx(vif_plug_port_ctx);
> return true;
> }
> -
> + *vif_plug_port_ctx_ptr = vif_plug_port_ctx;
> + return true;
> +}
> +static bool
> +consider_plug_lport_create__(const struct smap *iface_external_ids,
> + const struct sbrec_port_binding *pb,
> + const struct vif_plug_port_ctx
> *vif_plug_port_ctx,
> + struct vif_plug_ctx_in *vif_plug_ctx_in,
> + struct vif_plug_ctx_out *vif_plug_ctx_out)
> +{
> VLOG_INFO("Plugging port %s into %s for lport %s on this "
> "chassis.",
> vif_plug_port_ctx->vif_plug_port_ctx_out.name,
> @@ -340,41 +364,12 @@ static bool
> consider_plug_lport_update__(const struct vif_plug_class *vif_plug,
> const struct smap *iface_external_ids,
> const struct sbrec_port_binding *pb,
> - struct local_binding *lbinding,
> + const struct ovsrec_interface *iface_rec,
> + struct vif_plug_port_ctx *vif_plug_port_ctx,
> struct vif_plug_ctx_in *vif_plug_ctx_in,
> struct vif_plug_ctx_out *vif_plug_ctx_out)
> {
> - if (!vif_plug_ctx_in->chassis_rec || !vif_plug_ctx_in->br_int
> - || !vif_plug_ctx_in->ovs_idl_txn) {
> - /* Some of our prerequisites are not available, ask for a recompute.
> */
> - return false;
> - }
> - /* Our contract with the VIF plug provider is that vif_plug_port_finish
> - * will be called with vif_plug_port_ctx_in and vif_plug_port_ctx_out
> - * objects once the transaction commits.
> - *
> - * Since this happens asynchronously we need to allocate memory for
> - * and duplicate any database references so that they stay valid.
> - *
> - * The data is freed with a call to destroy_port_ctx after the
> - * transaction completes at the end of the ovn-controller main
> - * loop. */
> - struct vif_plug_port_ctx *vif_plug_port_ctx = build_port_ctx(
> - vif_plug, PLUG_OP_CREATE, vif_plug_ctx_in, pb, NULL, NULL);
> -
> - if (!vif_plug_port_prepare(vif_plug,
> - &vif_plug_port_ctx->vif_plug_port_ctx_in,
> - &vif_plug_port_ctx->vif_plug_port_ctx_out)) {
> - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
> - VLOG_INFO_RL(&rl,
> - "Not updating lport %s on direction from VIF plug "
> - "provider.",
> - pb->logical_port);
> - destroy_port_ctx(vif_plug_port_ctx);
> - return true;
> - }
> -
> - if (strcmp(lbinding->iface->name,
> + if (strcmp(iface_rec->name,
> vif_plug_port_ctx->vif_plug_port_ctx_out.name)) {
> VLOG_WARN("Attempt of incompatible change to existing "
> "port detected, please recreate port: %s",
> @@ -386,7 +381,7 @@ consider_plug_lport_update__(const struct vif_plug_class
> *vif_plug,
> return false;
> }
> VLOG_DBG("updating iface for: %s", pb->logical_port);
> - transact_update_port(lbinding->iface, vif_plug_ctx_in, vif_plug_ctx_out,
> + transact_update_port(iface_rec, vif_plug_ctx_in, vif_plug_ctx_out,
> vif_plug_port_ctx, iface_external_ids,
> get_plug_mtu_request(&pb->options));
>
> @@ -438,13 +433,45 @@ consider_plug_lport(const struct sbrec_port_binding *pb,
> UUID_ARGS(&lbinding->iface->header_.uuid));
> return false;
> }
> + struct vif_plug_port_ctx *vif_plug_port_ctx = NULL;
> + if (!consider_plug_lport__(vif_plug, pb, &vif_plug_port_ctx,
> + vif_plug_ctx_in)) {
> + return false;
> + }
> +
> ret = consider_plug_lport_update__(vif_plug, &iface_external_ids,
> - pb, lbinding, vif_plug_ctx_in,
> + pb, lbinding->iface,
> + vif_plug_port_ctx,
> + vif_plug_ctx_in,
> vif_plug_ctx_out);
> } else {
> - ret = consider_plug_lport_create__(vif_plug, &iface_external_ids,
> - pb, vif_plug_ctx_in,
> - vif_plug_ctx_out);
> + struct vif_plug_port_ctx *vif_plug_port_ctx = NULL;
> + if (!consider_plug_lport__(vif_plug, pb, &vif_plug_port_ctx,
> + vif_plug_ctx_in)) {
> + return false;
> + }
> +
> + const struct ovsrec_interface *iface_rec =
> +
> iface_lookup_by_name(vif_plug_ctx_in->ovsrec_interface_by_name,
> + vif_plug_port_ctx->vif_plug_port_ctx_out.name);
> + if (iface_rec &&
> + smap_get(&iface_rec->external_ids, "iface-id") &&
> + smap_get(&iface_rec->external_ids,
> + OVN_PLUGGED_EXT_ID) &&
> + is_iface_in_int_bridge(iface_rec, vif_plug_ctx_in->br_int)) {
> +
> + ret = consider_plug_lport_update__(vif_plug,
> + &iface_external_ids,
> + pb, iface_rec,
> + vif_plug_port_ctx,
> + vif_plug_ctx_in,
> + vif_plug_ctx_out);
> + } else {
> + ret = consider_plug_lport_create__(&iface_external_ids, pb,
> + vif_plug_port_ctx,
> + vif_plug_ctx_in,
> + vif_plug_ctx_out);
> + }
> }
> }
>
> diff --git a/controller/vif-plug.h b/controller/vif-plug.h
> index 7a1978e38..0fc7219c4 100644
> --- a/controller/vif-plug.h
> +++ b/controller/vif-plug.h
> @@ -34,6 +34,7 @@ struct vif_plug_ctx_in {
> struct ovsdb_idl_index *sbrec_port_binding_by_name;
> struct ovsdb_idl_index *sbrec_port_binding_by_requested_chassis;
> struct ovsdb_idl_index *ovsrec_port_by_interfaces;
> + struct ovsdb_idl_index *ovsrec_interface_by_name;
> const struct ovsrec_open_vswitch_table *ovs_table;
> const struct ovsrec_bridge *br_int;
> const struct ovsrec_interface_table *iface_table;
> diff --git a/tests/ovn.at b/tests/ovn.at
> index 6dd62ece1..5133edb87 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -41170,3 +41170,56 @@ OVN_CHECK_PACKETS_UNIQ([hv1/snoopvif-tx.pcap],
> [expected])
>
> AT_CLEANUP
> ])
> +
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([ovn-controller - vif-plug transaction failures])
> +AT_KEYWORDS([vif-plug])
> +
> +ovn_start
> +
> +net_add n1
> +sim_add hv1
> +ovs-vsctl add-br br-phys
> +ovn_attach n1 br-phys 192.168.0.1
> +ovn-appctl vlog/set dbg
> +
> +sim_add hv2
> +ovs-vsctl add-br br-phys
> +ovn_attach n1 br-phys 192.168.0.2
> +ovn-appctl vlog/set dbg
> +
> +check ovn-nbctl ls-add lsw0
> +
> +check ovn-nbctl lsp-add lsw0 lsp1
> +check ovn-nbctl lsp-set-addresses lsp1 "f0:00:00:00:00:01 172.16.0.101"
> +
> +sleep_ovs hv1
> +
> +# This used to cause Transaction failures in ovsdb until ofport is created.
> +check ovn-nbctl lsp-set-options lsp1 \
> + requested-chassis=hv1 \
> + vif-plug-type=dummy \
> + vif-plug-mtu-request=420
> +
> +check ovn-nbctl lsp-add lsw0 lsp2
> +check ovn-nbctl lsp-set-addresses lsp2 "f0:00:00:00:00:02 172.16.0.102"
> +
> +sleep_ovs hv2
> +
> +check ovn-nbctl lsp-set-options lsp2 \
> + requested-chassis=hv2 \
> + vif-plug-type=dummy \
> + vif-plug-mtu-request=420
> +
> +wake_up_ovs hv1
> +wait_for_ports_up lsp1
> +
> +# Exit ovn-controller while last transaction to ovsdb is not completed
> +as hv2
> +OVS_APP_EXIT_AND_WAIT([ovn-controller])
> +wake_up_ovs hv2
> +OVS_APP_EXIT_AND_WAIT([ovs-vswitchd])
> +
> +OVN_CLEANUP([hv1])
> +AT_CLEANUP
> +])
>
> base-commit: dd45c938817e116e42f9a4f42be4c501d205bf14
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev