diff mbox series

[ovs-dev,v6,2/2] ovn-controller: Fixed missing flows after interface deletion

Message ID 20221121124046.3980712-3-xsimonar@redhat.com
State Accepted
Headers show
Series ovn-controller: Multiple OVS interfaces bound to same lport | expand

Checks

Context Check Description
ovsrobot/apply-robot success apply and check: success
ovsrobot/github-robot-_Build_and_Test success github build: passed
ovsrobot/github-robot-_ovn-kubernetes success github build: passed

Commit Message

Xavier Simonart Nov. 21, 2022, 12:40 p.m. UTC
In the following scenario:
- interface "old" is created and external_ids:iface-id is set (to lp)
- interface "new" is created and external_ids:iface-id is set (to same lp)
- interface "old" is deleted
flows related to lp were deleted.

Note that after "new" interface is created, flows use "new" ofport.
The state where old and new interfaces have both external_ids:iface-id set at
the same time is "invalid", and all flows are not installed for lpold.

Fixes: 3ae8470edc64 ("I-P: Handle runtime data changes for pflow_output engine.")
Reported-at: https://bugzilla.redhat.com/show_bug.cgi?id=2129866

Signed-off-by: Xavier Simonart <xsimonar@redhat.com>
---
 controller/binding.c |  43 +++++++----
 controller/binding.h |   1 +
 tests/ovn.at         | 168 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 200 insertions(+), 12 deletions(-)

Comments

Han Zhou Nov. 22, 2022, 7:36 a.m. UTC | #1
On Mon, Nov 21, 2022 at 4:40 AM Xavier Simonart <xsimonar@redhat.com> wrote:
>
> In the following scenario:
> - interface "old" is created and external_ids:iface-id is set (to lp)
> - interface "new" is created and external_ids:iface-id is set (to same lp)
> - interface "old" is deleted
> flows related to lp were deleted.
>
> Note that after "new" interface is created, flows use "new" ofport.
> The state where old and new interfaces have both external_ids:iface-id
set at
> the same time is "invalid", and all flows are not installed for lpold.
>
> Fixes: 3ae8470edc64 ("I-P: Handle runtime data changes for pflow_output
engine.")
> Reported-at: https://bugzilla.redhat.com/show_bug.cgi?id=2129866
>
> Signed-off-by: Xavier Simonart <xsimonar@redhat.com>
> ---
>  controller/binding.c |  43 +++++++----
>  controller/binding.h |   1 +
>  tests/ovn.at         | 168 +++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 200 insertions(+), 12 deletions(-)
>
> diff --git a/controller/binding.c b/controller/binding.c
> index db1eb7a40..5df62baef 100644
> --- a/controller/binding.c
> +++ b/controller/binding.c
> @@ -1873,6 +1873,7 @@ build_local_bindings(struct binding_ctx_in
*b_ctx_in,
>                      lbinding = local_binding_create(iface_id, iface_rec);
>                      local_binding_add(local_bindings, lbinding);
>                  } else {
> +                    lbinding->multiple_bindings = true;
>                      static struct vlog_rate_limit rl =
>                          VLOG_RATE_LIMIT_INIT(1, 5);
>                      VLOG_WARN_RL(
> @@ -2163,6 +2164,10 @@ consider_iface_claim(const struct ovsrec_interface
*iface_rec,
>          lbinding = local_binding_create(iface_id, iface_rec);
>          local_binding_add(local_bindings, lbinding);
>      } else {
> +        if (lbinding->iface && lbinding->iface != iface_rec) {
> +            lbinding->multiple_bindings = true;
> +            b_ctx_out->local_lports_changed = true;
> +        }
>          lbinding->iface = iface_rec;
>      }
>
> @@ -2181,6 +2186,13 @@ consider_iface_claim(const struct ovsrec_interface
*iface_rec,
>          return true;
>      }
>
> +    /* If multiple bindings to the same port, remove the "old" binding.
> +     * This ensures that change tracking is correct.
> +     */
> +    if (lbinding->multiple_bindings) {
> +        remove_related_lport(pb, b_ctx_out);
> +    }
> +
>      enum en_lport_type lport_type = get_lport_type(pb);
>      if (lport_type == LP_LOCALPORT) {
>          return consider_localport(pb, b_ctx_in, b_ctx_out);
> @@ -2235,18 +2247,24 @@ consider_iface_release(const struct
ovsrec_interface *iface_rec,
>      lbinding = local_binding_find(local_bindings, iface_id);
>
>     if (lbinding) {
> -        int64_t ofport = iface_rec->n_ofport ? *iface_rec->ofport : 0;
> -        if (lbinding->iface != iface_rec && !ofport) {
> -            /* If external_ids:iface-id is set within the same
transaction
> -             * as adding an interface to a bridge, ovn-controller is
> -             * usually initially notified of ovs interface changes with
> -             * ofport == 0. If the lport was bound to a different
interface
> -             * we do not want to release it.
> -             */
> -            VLOG_DBG("Not releasing lport %s as %s was claimed "
> -                     "and %s was never bound)", iface_id,
lbinding->iface ?
> -                     lbinding->iface->name : "", iface_rec->name);
> -            return true;
> +        if (lbinding->multiple_bindings) {
> +            VLOG_INFO("Multiple bindings for %s: force recompute to
clean up",
> +                      iface_id);
> +            return false;
> +        } else {
> +            int64_t ofport = iface_rec->n_ofport ? *iface_rec->ofport :
0;
> +            if (lbinding->iface != iface_rec && !ofport) {
> +                /* If external_ids:iface-id is set within the same
transaction
> +                 * as adding an interface to a bridge, ovn-controller is
> +                 * usually initially notified of ovs interface changes
with
> +                 * ofport == 0. If the lport was bound to a different
interface
> +                 * we do not want to release it.
> +                 */
> +                VLOG_DBG("Not releasing lport %s as %s was claimed "
> +                         "and %s was never bound)", iface_id,
lbinding->iface ?
> +                         lbinding->iface->name : "", iface_rec->name);
> +                return true;
> +            }
>          }
>      }
>
> @@ -3058,6 +3076,7 @@ local_binding_create(const char *name, const struct
ovsrec_interface *iface)
>      struct local_binding *lbinding = xzalloc(sizeof *lbinding);
>      lbinding->name = xstrdup(name);
>      lbinding->iface = iface;
> +    lbinding->multiple_bindings = false;
>      ovs_list_init(&lbinding->binding_lports);
>
>      return lbinding;
> diff --git a/controller/binding.h b/controller/binding.h
> index ad959a9e6..6c3a98b02 100644
> --- a/controller/binding.h
> +++ b/controller/binding.h
> @@ -135,6 +135,7 @@ struct local_binding {
>      char *name;
>      const struct ovsrec_interface *iface;
>      struct ovs_list binding_lports;
> +    bool multiple_bindings;
>  };
>
>  struct local_binding_data {
> diff --git a/tests/ovn.at b/tests/ovn.at
> index 6552681bd..7a918c639 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -33053,3 +33053,171 @@ check ovn-nbctl --wait=hv sync
>  OVN_CLEANUP([hv1])
>  AT_CLEANUP
>  ])
> +
> +m4_define([MULTIPLE_OVS_INT],
> +  [OVN_FOR_EACH_NORTHD([
> +   AT_SETUP([ovn-controller: Multiple OVS interfaces bound to same
logical port ($1)])
> +   ovn_start
> +   net_add n1
> +
> +   sim_add hv1
> +   as hv1
> +   ovs-vsctl add-br br-phys
> +   ovn_attach n1 br-phys 192.168.0.1
> +
> +   get_flows()
> +   {
> +       cookie=${1}
> +       ovs-ofctl dump-flows br-int | grep $cookie |
> +           sed -e 's/duration=[[0-9.]]*s, //g' |
> +           sed -e 's/idle_age=[[0-9]]*, //g' |
> +           sed -e 's/n_packets=[[0-9]]*, //g' |
> +           sed -e 's/n_bytes=[[0-9]]*, //g'
> +   }
> +
> +   check ovn-nbctl ls-add ls
> +   check ovn-nbctl lsp-add ls lp
> +   if test X$1 != X; then
> +       check ovn-nbctl lsp-set-type lp $1
> +   fi
> +   check ovn-nbctl lsp-set-addresses lp "00:00:00:01:01:02 192.168.1.2"
> +
> +   check ovn-nbctl lsp-add ls vm1
> +   check ovn-nbctl lsp-set-addresses vm1 "00:00:00:01:01:11 192.168.1.11"
> +   check ovs-vsctl add-port br-int vm1 -- set interface vm1
type=internal external_ids:iface-id=vm1
> +
> +   check ovn-nbctl --wait=hv sync
> +
> +   check ovs-vsctl add-port br-int lpold -- set interface lpold
type=internal
> +   check ovs-vsctl set interface lpold external_ids:iface-id=lp
> +
> +   OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns _uuid find
port_binding logical_port=lp) != x])
> +   echo ======================================================
> +   echo === Flows after iface-id set for the old interface ===
> +   echo ======================================================
> +   COOKIE=$(ovn-sbctl find port_binding logical_port=lp|grep uuid|cut
-d: -f2| cut -c1-8 | sed 's/^\s*0\{0,8\}//')
> +
> +   OVS_WAIT_UNTIL([
> +       ofport=$(ovs-vsctl --bare --columns ofport find Interface
name=lpold)
> +       ovs-ofctl dump-flows br-int | grep $COOKIE | grep
"actions=output:$ofport"
> +   ])
> +   nb_flows=`ovs-ofctl dump-flows br-int | grep $COOKIE | wc -l`
> +   echo $nb_flows "flows after iface-id set for old interface"
> +
> +   echo ======================================================
> +   echo === Flows after iface-id set for the new interface ===
> +   echo ======================================================
> +   # Set external_ids:iface-id within same transaction as adding the
port.
> +   # This will generally cause ovn-controller to get initially notified
of ovs interface changes with ofport == 0.
> +   check ovs-vsctl add-port br-int lpnew -- set interface lpnew
type=internal -- set interface lpnew external_ids:iface-id=lp
> +   OVS_WAIT_UNTIL([
> +       ofport=$(ovs-vsctl --bare --columns ofport find Interface
name=lpnew)
> +       ovs-ofctl dump-flows br-int | grep $COOKIE | grep
"actions=output:$ofport"
> +   ])
> +   check test "$nb_flows" = $(ovs-ofctl dump-flows br-int | grep $COOKIE
| wc -l)
> +   flows_lpnew=$(get_flows $COOKIE)
> +
> +   echo ======================================================
> +   echo ======= Flows after old interface is deleted =========
> +   echo ======================================================
> +   check ovs-vsctl del-port br-int lpold
> +   # We do not expect changes, so let's wait for controller to get time
to process any update
> +   check ovn-nbctl --wait=hv sync
> +   check test "$nb_flows" = $(ovs-ofctl dump-flows br-int | grep $COOKIE
| wc -l)
> +   flows_after_deletion=$(get_flows $COOKIE)
> +   check test "$flows_lpnew" = "$flows_after_deletion"
> +
> +   echo ======================================================
> +   echo ======= Flows after lptemp interface is created ====
> +   echo ======================================================
> +   # Set external_ids:iface-id in a different transaction as adding the
port.
> +   # This will generally cause ovn-controller to get notified of ovs
interface changes with a proper ofport.
> +   check ovs-vsctl add-port br-int lptemp -- set Interface lptemp
type=internal
> +   check ovs-vsctl set Interface lptemp external_ids:iface-id=lp
> +   OVS_WAIT_UNTIL([
> +       ofport=$(ovs-vsctl --bare --columns ofport find Interface
name=lptemp)
> +       ovs-ofctl dump-flows br-int | grep $COOKIE | grep
"actions=output:$ofport"
> +   ])
> +   check test "$nb_flows" = $(ovs-ofctl dump-flows br-int | grep $COOKIE
| wc -l)
> +
> +   echo ======================================================
> +   echo ======= Flows after lptemp interface is deleted ======
> +   echo ======================================================
> +   check ovs-vsctl del-port br-int lptemp
> +   OVS_WAIT_UNTIL([
> +       ofport=$(ovs-vsctl --bare --columns ofport find Interface
name=lpnew)
> +       echo $ofport
> +       ovs-ofctl dump-flows br-int  | grep $COOKIE
> +       ovs-ofctl dump-flows br-int | grep $COOKIE | grep
"actions=output:$ofport"
> +   ])
> +   check test "$nb_flows" = $(ovs-ofctl dump-flows br-int | grep $COOKIE
| wc -l)
> +   flows_after_deletion=$(get_flows $COOKIE)
> +   check test "$flows_lpnew" = "$flows_after_deletion"
> +
> +   echo ======================================================
> +   echo ======= Flows after new interface is deleted =========
> +   echo ======================================================
> +   check ovs-vsctl del-port br-int lpnew
> +   OVS_WAIT_UNTIL([
> +       nb_flows=`ovs-ofctl dump-flows br-int | grep $COOKIE | wc -l`
> +       test "${nb_flows}" = 0
> +   ])
> +
> +   echo ======================================================
> +   echo ======= Three interfaces bound to the same port ======
> +   echo ======================================================
> +   check ovs-vsctl add-port br-int lpold -- set interface lpold
type=internal
> +   check ovs-vsctl set interface lpold external_ids:iface-id=lp
> +   check ovs-vsctl add-port br-int lpnew -- set interface lpnew
type=internal
> +   check ovs-vsctl set interface lpnew external_ids:iface-id=lp
> +
> +   # Wait for lpnew  flows to be installed
> +   OVS_WAIT_UNTIL([
> +       ofport=$(ovs-vsctl --bare --columns ofport find Interface
name=lpnew)
> +       ovs-ofctl dump-flows br-int | grep $COOKIE | grep
"actions=output:$ofport"
> +   ])
> +   flows_lpnew=$(get_flows $COOKIE)
> +   nb_flows=`ovs-ofctl dump-flows br-int | grep $COOKIE | wc -l`
> +
> +   check ovs-vsctl add-port br-int lptemp -- set Interface lptemp
type=internal
> +   check ovs-vsctl set Interface lptemp external_ids:iface-id=lp
> +
> +   # Wait for lptemp  flows to be installed
> +   OVS_WAIT_UNTIL([
> +       ofport=$(ovs-vsctl --bare --columns ofport find Interface
name=lptemp)
> +       ovs-ofctl dump-flows br-int | grep $COOKIE | grep
"actions=output:$ofport"
> +   ])
> +
> +   # Delete both lpold and lptemp to go to a stable situation
> +   check ovs-vsctl del-port br-int lptemp
> +   check ovs-vsctl del-port br-int lpold
> +
> +   OVS_WAIT_UNTIL([
> +        test 0 = $(ovs-vsctl show | grep "Port lpold" | wc -l)
> +   ])
> +
> +   # Wait for correct/lpnew flows to be installed
> +   OVS_WAIT_UNTIL([
> +       ofport=$(ovs-vsctl --bare --columns ofport find Interface
name=lpnew)
> +       ovs-ofctl dump-flows br-int | grep $COOKIE | grep
"actions=output:$ofport"
> +   ])
> +   check test "$nb_flows" = $(ovs-ofctl dump-flows br-int | grep $COOKIE
| wc -l)
> +   flows_after_deletion=$(get_flows $COOKIE)
> +   check test "$flows_lpnew" = "$flows_after_deletion"
> +
> +   # Check that recompute still works
> +   check ovn-appctl -t ovn-controller recompute
> +   OVS_WAIT_UNTIL([
> +       ofport=$(ovs-vsctl --bare --columns ofport find Interface
name=lpnew)
> +       ovs-ofctl dump-flows br-int | grep $COOKIE | grep
"actions=output:$ofport"
> +   ])
> +   check test "$nb_flows" = $(ovs-ofctl dump-flows br-int | grep $COOKIE
| wc -l)
> +   flows_after_deletion=$(get_flows $COOKIE)
> +   check test "$flows_lpnew" = "$flows_after_deletion"
> +
> +   OVN_CLEANUP([hv1])
> +   AT_CLEANUP
> +   ])])
> +
> +MULTIPLE_OVS_INT([localport])
> +MULTIPLE_OVS_INT([])
> --
> 2.31.1
>

Acked-by: Han Zhou <hzhou@ovn.org>
Numan Siddique Nov. 23, 2022, 1:39 a.m. UTC | #2
On Tue, Nov 22, 2022 at 2:36 AM Han Zhou <hzhou@ovn.org> wrote:
>
> On Mon, Nov 21, 2022 at 4:40 AM Xavier Simonart <xsimonar@redhat.com> wrote:
> >
> > In the following scenario:
> > - interface "old" is created and external_ids:iface-id is set (to lp)
> > - interface "new" is created and external_ids:iface-id is set (to same lp)
> > - interface "old" is deleted
> > flows related to lp were deleted.
> >
> > Note that after "new" interface is created, flows use "new" ofport.
> > The state where old and new interfaces have both external_ids:iface-id
> set at
> > the same time is "invalid", and all flows are not installed for lpold.
> >
> > Fixes: 3ae8470edc64 ("I-P: Handle runtime data changes for pflow_output
> engine.")
> > Reported-at: https://bugzilla.redhat.com/show_bug.cgi?id=2129866
> >
> > Signed-off-by: Xavier Simonart <xsimonar@redhat.com>
> > ---
> >  controller/binding.c |  43 +++++++----
> >  controller/binding.h |   1 +
> >  tests/ovn.at         | 168 +++++++++++++++++++++++++++++++++++++++++++
> >  3 files changed, 200 insertions(+), 12 deletions(-)
> >
> > diff --git a/controller/binding.c b/controller/binding.c
> > index db1eb7a40..5df62baef 100644
> > --- a/controller/binding.c
> > +++ b/controller/binding.c
> > @@ -1873,6 +1873,7 @@ build_local_bindings(struct binding_ctx_in
> *b_ctx_in,
> >                      lbinding = local_binding_create(iface_id, iface_rec);
> >                      local_binding_add(local_bindings, lbinding);
> >                  } else {
> > +                    lbinding->multiple_bindings = true;
> >                      static struct vlog_rate_limit rl =
> >                          VLOG_RATE_LIMIT_INIT(1, 5);
> >                      VLOG_WARN_RL(
> > @@ -2163,6 +2164,10 @@ consider_iface_claim(const struct ovsrec_interface
> *iface_rec,
> >          lbinding = local_binding_create(iface_id, iface_rec);
> >          local_binding_add(local_bindings, lbinding);
> >      } else {
> > +        if (lbinding->iface && lbinding->iface != iface_rec) {
> > +            lbinding->multiple_bindings = true;
> > +            b_ctx_out->local_lports_changed = true;
> > +        }
> >          lbinding->iface = iface_rec;
> >      }
> >
> > @@ -2181,6 +2186,13 @@ consider_iface_claim(const struct ovsrec_interface
> *iface_rec,
> >          return true;
> >      }
> >
> > +    /* If multiple bindings to the same port, remove the "old" binding.
> > +     * This ensures that change tracking is correct.
> > +     */
> > +    if (lbinding->multiple_bindings) {
> > +        remove_related_lport(pb, b_ctx_out);
> > +    }
> > +
> >      enum en_lport_type lport_type = get_lport_type(pb);
> >      if (lport_type == LP_LOCALPORT) {
> >          return consider_localport(pb, b_ctx_in, b_ctx_out);
> > @@ -2235,18 +2247,24 @@ consider_iface_release(const struct
> ovsrec_interface *iface_rec,
> >      lbinding = local_binding_find(local_bindings, iface_id);
> >
> >     if (lbinding) {
> > -        int64_t ofport = iface_rec->n_ofport ? *iface_rec->ofport : 0;
> > -        if (lbinding->iface != iface_rec && !ofport) {
> > -            /* If external_ids:iface-id is set within the same
> transaction
> > -             * as adding an interface to a bridge, ovn-controller is
> > -             * usually initially notified of ovs interface changes with
> > -             * ofport == 0. If the lport was bound to a different
> interface
> > -             * we do not want to release it.
> > -             */
> > -            VLOG_DBG("Not releasing lport %s as %s was claimed "
> > -                     "and %s was never bound)", iface_id,
> lbinding->iface ?
> > -                     lbinding->iface->name : "", iface_rec->name);
> > -            return true;
> > +        if (lbinding->multiple_bindings) {
> > +            VLOG_INFO("Multiple bindings for %s: force recompute to
> clean up",
> > +                      iface_id);
> > +            return false;
> > +        } else {
> > +            int64_t ofport = iface_rec->n_ofport ? *iface_rec->ofport :
> 0;
> > +            if (lbinding->iface != iface_rec && !ofport) {
> > +                /* If external_ids:iface-id is set within the same
> transaction
> > +                 * as adding an interface to a bridge, ovn-controller is
> > +                 * usually initially notified of ovs interface changes
> with
> > +                 * ofport == 0. If the lport was bound to a different
> interface
> > +                 * we do not want to release it.
> > +                 */
> > +                VLOG_DBG("Not releasing lport %s as %s was claimed "
> > +                         "and %s was never bound)", iface_id,
> lbinding->iface ?
> > +                         lbinding->iface->name : "", iface_rec->name);
> > +                return true;
> > +            }
> >          }
> >      }
> >
> > @@ -3058,6 +3076,7 @@ local_binding_create(const char *name, const struct
> ovsrec_interface *iface)
> >      struct local_binding *lbinding = xzalloc(sizeof *lbinding);
> >      lbinding->name = xstrdup(name);
> >      lbinding->iface = iface;
> > +    lbinding->multiple_bindings = false;
> >      ovs_list_init(&lbinding->binding_lports);
> >
> >      return lbinding;
> > diff --git a/controller/binding.h b/controller/binding.h
> > index ad959a9e6..6c3a98b02 100644
> > --- a/controller/binding.h
> > +++ b/controller/binding.h
> > @@ -135,6 +135,7 @@ struct local_binding {
> >      char *name;
> >      const struct ovsrec_interface *iface;
> >      struct ovs_list binding_lports;
> > +    bool multiple_bindings;
> >  };
> >
> >  struct local_binding_data {
> > diff --git a/tests/ovn.at b/tests/ovn.at
> > index 6552681bd..7a918c639 100644
> > --- a/tests/ovn.at
> > +++ b/tests/ovn.at
> > @@ -33053,3 +33053,171 @@ check ovn-nbctl --wait=hv sync
> >  OVN_CLEANUP([hv1])
> >  AT_CLEANUP
> >  ])
> > +
> > +m4_define([MULTIPLE_OVS_INT],
> > +  [OVN_FOR_EACH_NORTHD([
> > +   AT_SETUP([ovn-controller: Multiple OVS interfaces bound to same
> logical port ($1)])
> > +   ovn_start
> > +   net_add n1
> > +
> > +   sim_add hv1
> > +   as hv1
> > +   ovs-vsctl add-br br-phys
> > +   ovn_attach n1 br-phys 192.168.0.1
> > +
> > +   get_flows()
> > +   {
> > +       cookie=${1}
> > +       ovs-ofctl dump-flows br-int | grep $cookie |
> > +           sed -e 's/duration=[[0-9.]]*s, //g' |
> > +           sed -e 's/idle_age=[[0-9]]*, //g' |
> > +           sed -e 's/n_packets=[[0-9]]*, //g' |
> > +           sed -e 's/n_bytes=[[0-9]]*, //g'
> > +   }
> > +
> > +   check ovn-nbctl ls-add ls
> > +   check ovn-nbctl lsp-add ls lp
> > +   if test X$1 != X; then
> > +       check ovn-nbctl lsp-set-type lp $1
> > +   fi
> > +   check ovn-nbctl lsp-set-addresses lp "00:00:00:01:01:02 192.168.1.2"
> > +
> > +   check ovn-nbctl lsp-add ls vm1
> > +   check ovn-nbctl lsp-set-addresses vm1 "00:00:00:01:01:11 192.168.1.11"
> > +   check ovs-vsctl add-port br-int vm1 -- set interface vm1
> type=internal external_ids:iface-id=vm1
> > +
> > +   check ovn-nbctl --wait=hv sync
> > +
> > +   check ovs-vsctl add-port br-int lpold -- set interface lpold
> type=internal
> > +   check ovs-vsctl set interface lpold external_ids:iface-id=lp
> > +
> > +   OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns _uuid find
> port_binding logical_port=lp) != x])
> > +   echo ======================================================
> > +   echo === Flows after iface-id set for the old interface ===
> > +   echo ======================================================
> > +   COOKIE=$(ovn-sbctl find port_binding logical_port=lp|grep uuid|cut
> -d: -f2| cut -c1-8 | sed 's/^\s*0\{0,8\}//')
> > +
> > +   OVS_WAIT_UNTIL([
> > +       ofport=$(ovs-vsctl --bare --columns ofport find Interface
> name=lpold)
> > +       ovs-ofctl dump-flows br-int | grep $COOKIE | grep
> "actions=output:$ofport"
> > +   ])
> > +   nb_flows=`ovs-ofctl dump-flows br-int | grep $COOKIE | wc -l`
> > +   echo $nb_flows "flows after iface-id set for old interface"
> > +
> > +   echo ======================================================
> > +   echo === Flows after iface-id set for the new interface ===
> > +   echo ======================================================
> > +   # Set external_ids:iface-id within same transaction as adding the
> port.
> > +   # This will generally cause ovn-controller to get initially notified
> of ovs interface changes with ofport == 0.
> > +   check ovs-vsctl add-port br-int lpnew -- set interface lpnew
> type=internal -- set interface lpnew external_ids:iface-id=lp
> > +   OVS_WAIT_UNTIL([
> > +       ofport=$(ovs-vsctl --bare --columns ofport find Interface
> name=lpnew)
> > +       ovs-ofctl dump-flows br-int | grep $COOKIE | grep
> "actions=output:$ofport"
> > +   ])
> > +   check test "$nb_flows" = $(ovs-ofctl dump-flows br-int | grep $COOKIE
> | wc -l)
> > +   flows_lpnew=$(get_flows $COOKIE)
> > +
> > +   echo ======================================================
> > +   echo ======= Flows after old interface is deleted =========
> > +   echo ======================================================
> > +   check ovs-vsctl del-port br-int lpold
> > +   # We do not expect changes, so let's wait for controller to get time
> to process any update
> > +   check ovn-nbctl --wait=hv sync
> > +   check test "$nb_flows" = $(ovs-ofctl dump-flows br-int | grep $COOKIE
> | wc -l)
> > +   flows_after_deletion=$(get_flows $COOKIE)
> > +   check test "$flows_lpnew" = "$flows_after_deletion"
> > +
> > +   echo ======================================================
> > +   echo ======= Flows after lptemp interface is created ====
> > +   echo ======================================================
> > +   # Set external_ids:iface-id in a different transaction as adding the
> port.
> > +   # This will generally cause ovn-controller to get notified of ovs
> interface changes with a proper ofport.
> > +   check ovs-vsctl add-port br-int lptemp -- set Interface lptemp
> type=internal
> > +   check ovs-vsctl set Interface lptemp external_ids:iface-id=lp
> > +   OVS_WAIT_UNTIL([
> > +       ofport=$(ovs-vsctl --bare --columns ofport find Interface
> name=lptemp)
> > +       ovs-ofctl dump-flows br-int | grep $COOKIE | grep
> "actions=output:$ofport"
> > +   ])
> > +   check test "$nb_flows" = $(ovs-ofctl dump-flows br-int | grep $COOKIE
> | wc -l)
> > +
> > +   echo ======================================================
> > +   echo ======= Flows after lptemp interface is deleted ======
> > +   echo ======================================================
> > +   check ovs-vsctl del-port br-int lptemp
> > +   OVS_WAIT_UNTIL([
> > +       ofport=$(ovs-vsctl --bare --columns ofport find Interface
> name=lpnew)
> > +       echo $ofport
> > +       ovs-ofctl dump-flows br-int  | grep $COOKIE
> > +       ovs-ofctl dump-flows br-int | grep $COOKIE | grep
> "actions=output:$ofport"
> > +   ])
> > +   check test "$nb_flows" = $(ovs-ofctl dump-flows br-int | grep $COOKIE
> | wc -l)
> > +   flows_after_deletion=$(get_flows $COOKIE)
> > +   check test "$flows_lpnew" = "$flows_after_deletion"
> > +
> > +   echo ======================================================
> > +   echo ======= Flows after new interface is deleted =========
> > +   echo ======================================================
> > +   check ovs-vsctl del-port br-int lpnew
> > +   OVS_WAIT_UNTIL([
> > +       nb_flows=`ovs-ofctl dump-flows br-int | grep $COOKIE | wc -l`
> > +       test "${nb_flows}" = 0
> > +   ])
> > +
> > +   echo ======================================================
> > +   echo ======= Three interfaces bound to the same port ======
> > +   echo ======================================================
> > +   check ovs-vsctl add-port br-int lpold -- set interface lpold
> type=internal
> > +   check ovs-vsctl set interface lpold external_ids:iface-id=lp
> > +   check ovs-vsctl add-port br-int lpnew -- set interface lpnew
> type=internal
> > +   check ovs-vsctl set interface lpnew external_ids:iface-id=lp
> > +
> > +   # Wait for lpnew  flows to be installed
> > +   OVS_WAIT_UNTIL([
> > +       ofport=$(ovs-vsctl --bare --columns ofport find Interface
> name=lpnew)
> > +       ovs-ofctl dump-flows br-int | grep $COOKIE | grep
> "actions=output:$ofport"
> > +   ])
> > +   flows_lpnew=$(get_flows $COOKIE)
> > +   nb_flows=`ovs-ofctl dump-flows br-int | grep $COOKIE | wc -l`
> > +
> > +   check ovs-vsctl add-port br-int lptemp -- set Interface lptemp
> type=internal
> > +   check ovs-vsctl set Interface lptemp external_ids:iface-id=lp
> > +
> > +   # Wait for lptemp  flows to be installed
> > +   OVS_WAIT_UNTIL([
> > +       ofport=$(ovs-vsctl --bare --columns ofport find Interface
> name=lptemp)
> > +       ovs-ofctl dump-flows br-int | grep $COOKIE | grep
> "actions=output:$ofport"
> > +   ])
> > +
> > +   # Delete both lpold and lptemp to go to a stable situation
> > +   check ovs-vsctl del-port br-int lptemp
> > +   check ovs-vsctl del-port br-int lpold
> > +
> > +   OVS_WAIT_UNTIL([
> > +        test 0 = $(ovs-vsctl show | grep "Port lpold" | wc -l)
> > +   ])
> > +
> > +   # Wait for correct/lpnew flows to be installed
> > +   OVS_WAIT_UNTIL([
> > +       ofport=$(ovs-vsctl --bare --columns ofport find Interface
> name=lpnew)
> > +       ovs-ofctl dump-flows br-int | grep $COOKIE | grep
> "actions=output:$ofport"
> > +   ])
> > +   check test "$nb_flows" = $(ovs-ofctl dump-flows br-int | grep $COOKIE
> | wc -l)
> > +   flows_after_deletion=$(get_flows $COOKIE)
> > +   check test "$flows_lpnew" = "$flows_after_deletion"
> > +
> > +   # Check that recompute still works
> > +   check ovn-appctl -t ovn-controller recompute
> > +   OVS_WAIT_UNTIL([
> > +       ofport=$(ovs-vsctl --bare --columns ofport find Interface
> name=lpnew)
> > +       ovs-ofctl dump-flows br-int | grep $COOKIE | grep
> "actions=output:$ofport"
> > +   ])
> > +   check test "$nb_flows" = $(ovs-ofctl dump-flows br-int | grep $COOKIE
> | wc -l)
> > +   flows_after_deletion=$(get_flows $COOKIE)
> > +   check test "$flows_lpnew" = "$flows_after_deletion"
> > +
> > +   OVN_CLEANUP([hv1])
> > +   AT_CLEANUP
> > +   ])])
> > +
> > +MULTIPLE_OVS_INT([localport])
> > +MULTIPLE_OVS_INT([])
> > --
> > 2.31.1
> >
>
> Acked-by: Han Zhou <hzhou@ovn.org>

Thanks.  I applied both the patches to the main branch.

I suppose this needs to be backported.  I'll backport tomorrow.

Numan



> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
diff mbox series

Patch

diff --git a/controller/binding.c b/controller/binding.c
index db1eb7a40..5df62baef 100644
--- a/controller/binding.c
+++ b/controller/binding.c
@@ -1873,6 +1873,7 @@  build_local_bindings(struct binding_ctx_in *b_ctx_in,
                     lbinding = local_binding_create(iface_id, iface_rec);
                     local_binding_add(local_bindings, lbinding);
                 } else {
+                    lbinding->multiple_bindings = true;
                     static struct vlog_rate_limit rl =
                         VLOG_RATE_LIMIT_INIT(1, 5);
                     VLOG_WARN_RL(
@@ -2163,6 +2164,10 @@  consider_iface_claim(const struct ovsrec_interface *iface_rec,
         lbinding = local_binding_create(iface_id, iface_rec);
         local_binding_add(local_bindings, lbinding);
     } else {
+        if (lbinding->iface && lbinding->iface != iface_rec) {
+            lbinding->multiple_bindings = true;
+            b_ctx_out->local_lports_changed = true;
+        }
         lbinding->iface = iface_rec;
     }
 
@@ -2181,6 +2186,13 @@  consider_iface_claim(const struct ovsrec_interface *iface_rec,
         return true;
     }
 
+    /* If multiple bindings to the same port, remove the "old" binding.
+     * This ensures that change tracking is correct.
+     */
+    if (lbinding->multiple_bindings) {
+        remove_related_lport(pb, b_ctx_out);
+    }
+
     enum en_lport_type lport_type = get_lport_type(pb);
     if (lport_type == LP_LOCALPORT) {
         return consider_localport(pb, b_ctx_in, b_ctx_out);
@@ -2235,18 +2247,24 @@  consider_iface_release(const struct ovsrec_interface *iface_rec,
     lbinding = local_binding_find(local_bindings, iface_id);
 
    if (lbinding) {
-        int64_t ofport = iface_rec->n_ofport ? *iface_rec->ofport : 0;
-        if (lbinding->iface != iface_rec && !ofport) {
-            /* If external_ids:iface-id is set within the same transaction
-             * as adding an interface to a bridge, ovn-controller is
-             * usually initially notified of ovs interface changes with
-             * ofport == 0. If the lport was bound to a different interface
-             * we do not want to release it.
-             */
-            VLOG_DBG("Not releasing lport %s as %s was claimed "
-                     "and %s was never bound)", iface_id, lbinding->iface ?
-                     lbinding->iface->name : "", iface_rec->name);
-            return true;
+        if (lbinding->multiple_bindings) {
+            VLOG_INFO("Multiple bindings for %s: force recompute to clean up",
+                      iface_id);
+            return false;
+        } else {
+            int64_t ofport = iface_rec->n_ofport ? *iface_rec->ofport : 0;
+            if (lbinding->iface != iface_rec && !ofport) {
+                /* If external_ids:iface-id is set within the same transaction
+                 * as adding an interface to a bridge, ovn-controller is
+                 * usually initially notified of ovs interface changes with
+                 * ofport == 0. If the lport was bound to a different interface
+                 * we do not want to release it.
+                 */
+                VLOG_DBG("Not releasing lport %s as %s was claimed "
+                         "and %s was never bound)", iface_id, lbinding->iface ?
+                         lbinding->iface->name : "", iface_rec->name);
+                return true;
+            }
         }
     }
 
@@ -3058,6 +3076,7 @@  local_binding_create(const char *name, const struct ovsrec_interface *iface)
     struct local_binding *lbinding = xzalloc(sizeof *lbinding);
     lbinding->name = xstrdup(name);
     lbinding->iface = iface;
+    lbinding->multiple_bindings = false;
     ovs_list_init(&lbinding->binding_lports);
 
     return lbinding;
diff --git a/controller/binding.h b/controller/binding.h
index ad959a9e6..6c3a98b02 100644
--- a/controller/binding.h
+++ b/controller/binding.h
@@ -135,6 +135,7 @@  struct local_binding {
     char *name;
     const struct ovsrec_interface *iface;
     struct ovs_list binding_lports;
+    bool multiple_bindings;
 };
 
 struct local_binding_data {
diff --git a/tests/ovn.at b/tests/ovn.at
index 6552681bd..7a918c639 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -33053,3 +33053,171 @@  check ovn-nbctl --wait=hv sync
 OVN_CLEANUP([hv1])
 AT_CLEANUP
 ])
+
+m4_define([MULTIPLE_OVS_INT],
+  [OVN_FOR_EACH_NORTHD([
+   AT_SETUP([ovn-controller: Multiple OVS interfaces bound to same logical port ($1)])
+   ovn_start
+   net_add n1
+
+   sim_add hv1
+   as hv1
+   ovs-vsctl add-br br-phys
+   ovn_attach n1 br-phys 192.168.0.1
+
+   get_flows()
+   {
+       cookie=${1}
+       ovs-ofctl dump-flows br-int | grep $cookie |
+           sed -e 's/duration=[[0-9.]]*s, //g' |
+           sed -e 's/idle_age=[[0-9]]*, //g' |
+           sed -e 's/n_packets=[[0-9]]*, //g' |
+           sed -e 's/n_bytes=[[0-9]]*, //g'
+   }
+
+   check ovn-nbctl ls-add ls
+   check ovn-nbctl lsp-add ls lp
+   if test X$1 != X; then
+       check ovn-nbctl lsp-set-type lp $1
+   fi
+   check ovn-nbctl lsp-set-addresses lp "00:00:00:01:01:02 192.168.1.2"
+
+   check ovn-nbctl lsp-add ls vm1
+   check ovn-nbctl lsp-set-addresses vm1 "00:00:00:01:01:11 192.168.1.11"
+   check ovs-vsctl add-port br-int vm1 -- set interface vm1 type=internal external_ids:iface-id=vm1
+
+   check ovn-nbctl --wait=hv sync
+
+   check ovs-vsctl add-port br-int lpold -- set interface lpold type=internal
+   check ovs-vsctl set interface lpold external_ids:iface-id=lp
+
+   OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns _uuid find port_binding logical_port=lp) != x])
+   echo ======================================================
+   echo === Flows after iface-id set for the old interface ===
+   echo ======================================================
+   COOKIE=$(ovn-sbctl find port_binding logical_port=lp|grep uuid|cut -d: -f2| cut -c1-8 | sed 's/^\s*0\{0,8\}//')
+
+   OVS_WAIT_UNTIL([
+       ofport=$(ovs-vsctl --bare --columns ofport find Interface name=lpold)
+       ovs-ofctl dump-flows br-int | grep $COOKIE | grep "actions=output:$ofport"
+   ])
+   nb_flows=`ovs-ofctl dump-flows br-int | grep $COOKIE | wc -l`
+   echo $nb_flows "flows after iface-id set for old interface"
+
+   echo ======================================================
+   echo === Flows after iface-id set for the new interface ===
+   echo ======================================================
+   # Set external_ids:iface-id within same transaction as adding the port.
+   # This will generally cause ovn-controller to get initially notified of ovs interface changes with ofport == 0.
+   check ovs-vsctl add-port br-int lpnew -- set interface lpnew type=internal -- set interface lpnew external_ids:iface-id=lp
+   OVS_WAIT_UNTIL([
+       ofport=$(ovs-vsctl --bare --columns ofport find Interface name=lpnew)
+       ovs-ofctl dump-flows br-int | grep $COOKIE | grep "actions=output:$ofport"
+   ])
+   check test "$nb_flows" = $(ovs-ofctl dump-flows br-int | grep $COOKIE | wc -l)
+   flows_lpnew=$(get_flows $COOKIE)
+
+   echo ======================================================
+   echo ======= Flows after old interface is deleted =========
+   echo ======================================================
+   check ovs-vsctl del-port br-int lpold
+   # We do not expect changes, so let's wait for controller to get time to process any update
+   check ovn-nbctl --wait=hv sync
+   check test "$nb_flows" = $(ovs-ofctl dump-flows br-int | grep $COOKIE | wc -l)
+   flows_after_deletion=$(get_flows $COOKIE)
+   check test "$flows_lpnew" = "$flows_after_deletion"
+
+   echo ======================================================
+   echo ======= Flows after lptemp interface is created ====
+   echo ======================================================
+   # Set external_ids:iface-id in a different transaction as adding the port.
+   # This will generally cause ovn-controller to get notified of ovs interface changes with a proper ofport.
+   check ovs-vsctl add-port br-int lptemp -- set Interface lptemp type=internal
+   check ovs-vsctl set Interface lptemp external_ids:iface-id=lp
+   OVS_WAIT_UNTIL([
+       ofport=$(ovs-vsctl --bare --columns ofport find Interface name=lptemp)
+       ovs-ofctl dump-flows br-int | grep $COOKIE | grep "actions=output:$ofport"
+   ])
+   check test "$nb_flows" = $(ovs-ofctl dump-flows br-int | grep $COOKIE | wc -l)
+
+   echo ======================================================
+   echo ======= Flows after lptemp interface is deleted ======
+   echo ======================================================
+   check ovs-vsctl del-port br-int lptemp
+   OVS_WAIT_UNTIL([
+       ofport=$(ovs-vsctl --bare --columns ofport find Interface name=lpnew)
+       echo $ofport
+       ovs-ofctl dump-flows br-int  | grep $COOKIE
+       ovs-ofctl dump-flows br-int | grep $COOKIE | grep "actions=output:$ofport"
+   ])
+   check test "$nb_flows" = $(ovs-ofctl dump-flows br-int | grep $COOKIE | wc -l)
+   flows_after_deletion=$(get_flows $COOKIE)
+   check test "$flows_lpnew" = "$flows_after_deletion"
+
+   echo ======================================================
+   echo ======= Flows after new interface is deleted =========
+   echo ======================================================
+   check ovs-vsctl del-port br-int lpnew
+   OVS_WAIT_UNTIL([
+       nb_flows=`ovs-ofctl dump-flows br-int | grep $COOKIE | wc -l`
+       test "${nb_flows}" = 0
+   ])
+
+   echo ======================================================
+   echo ======= Three interfaces bound to the same port ======
+   echo ======================================================
+   check ovs-vsctl add-port br-int lpold -- set interface lpold type=internal
+   check ovs-vsctl set interface lpold external_ids:iface-id=lp
+   check ovs-vsctl add-port br-int lpnew -- set interface lpnew type=internal
+   check ovs-vsctl set interface lpnew external_ids:iface-id=lp
+
+   # Wait for lpnew  flows to be installed
+   OVS_WAIT_UNTIL([
+       ofport=$(ovs-vsctl --bare --columns ofport find Interface name=lpnew)
+       ovs-ofctl dump-flows br-int | grep $COOKIE | grep "actions=output:$ofport"
+   ])
+   flows_lpnew=$(get_flows $COOKIE)
+   nb_flows=`ovs-ofctl dump-flows br-int | grep $COOKIE | wc -l`
+
+   check ovs-vsctl add-port br-int lptemp -- set Interface lptemp type=internal
+   check ovs-vsctl set Interface lptemp external_ids:iface-id=lp
+
+   # Wait for lptemp  flows to be installed
+   OVS_WAIT_UNTIL([
+       ofport=$(ovs-vsctl --bare --columns ofport find Interface name=lptemp)
+       ovs-ofctl dump-flows br-int | grep $COOKIE | grep "actions=output:$ofport"
+   ])
+
+   # Delete both lpold and lptemp to go to a stable situation
+   check ovs-vsctl del-port br-int lptemp
+   check ovs-vsctl del-port br-int lpold
+
+   OVS_WAIT_UNTIL([
+        test 0 = $(ovs-vsctl show | grep "Port lpold" | wc -l)
+   ])
+
+   # Wait for correct/lpnew flows to be installed
+   OVS_WAIT_UNTIL([
+       ofport=$(ovs-vsctl --bare --columns ofport find Interface name=lpnew)
+       ovs-ofctl dump-flows br-int | grep $COOKIE | grep "actions=output:$ofport"
+   ])
+   check test "$nb_flows" = $(ovs-ofctl dump-flows br-int | grep $COOKIE | wc -l)
+   flows_after_deletion=$(get_flows $COOKIE)
+   check test "$flows_lpnew" = "$flows_after_deletion"
+
+   # Check that recompute still works
+   check ovn-appctl -t ovn-controller recompute
+   OVS_WAIT_UNTIL([
+       ofport=$(ovs-vsctl --bare --columns ofport find Interface name=lpnew)
+       ovs-ofctl dump-flows br-int | grep $COOKIE | grep "actions=output:$ofport"
+   ])
+   check test "$nb_flows" = $(ovs-ofctl dump-flows br-int | grep $COOKIE | wc -l)
+   flows_after_deletion=$(get_flows $COOKIE)
+   check test "$flows_lpnew" = "$flows_after_deletion"
+
+   OVN_CLEANUP([hv1])
+   AT_CLEANUP
+   ])])
+
+MULTIPLE_OVS_INT([localport])
+MULTIPLE_OVS_INT([])