diff mbox series

[ovs-dev,ovn,v7,2/9] ovn-controller: I-P for SB port binding and OVS interface in runtime_data.

Message ID 20200520193951.2351827-1-numans@ovn.org
State Superseded
Headers show
Series Incremental processing improvements. | expand

Commit Message

Numan Siddique May 20, 2020, 7:39 p.m. UTC
From: Numan Siddique <numans@ovn.org>

This patch handles SB port binding changes and OVS interface changes
incrementally in the runtime_data stage which otherwise would have
resulted in calls to binding_run().

Prior to this patch, port binding changes were handled differently.
The changes were only evaluated to see if they need full recompute
or they can be ignored.

With this patch, the runtime data is updated with the changes (both
SB port binding and OVS interface) and ports are claimed/released in
the handlers itself, avoiding the need to trigger recompute of runtime
data stage.

Acked-by: Mark Michelson <mmichels@redhat.com>
Signed-off-by: Numan Siddique <numans@ovn.org>
---
 controller/binding.c        | 906 +++++++++++++++++++++++++++++++-----
 controller/binding.h        |   9 +-
 controller/ovn-controller.c |  61 ++-
 tests/ovn-performance.at    |  13 +
 4 files changed, 855 insertions(+), 134 deletions(-)

Comments

Han Zhou May 21, 2020, 1:26 a.m. UTC | #1
Hi Numan, please see my comments below.

On Wed, May 20, 2020 at 12:41 PM <numans@ovn.org> wrote:
>
> From: Numan Siddique <numans@ovn.org>
>
> This patch handles SB port binding changes and OVS interface changes
> incrementally in the runtime_data stage which otherwise would have
> resulted in calls to binding_run().
>
> Prior to this patch, port binding changes were handled differently.
> The changes were only evaluated to see if they need full recompute
> or they can be ignored.
>
> With this patch, the runtime data is updated with the changes (both
> SB port binding and OVS interface) and ports are claimed/released in
> the handlers itself, avoiding the need to trigger recompute of runtime
> data stage.
>
> Acked-by: Mark Michelson <mmichels@redhat.com>
> Signed-off-by: Numan Siddique <numans@ovn.org>
> ---
>  controller/binding.c        | 906 +++++++++++++++++++++++++++++++-----
>  controller/binding.h        |   9 +-
>  controller/ovn-controller.c |  61 ++-
>  tests/ovn-performance.at    |  13 +
>  4 files changed, 855 insertions(+), 134 deletions(-)
>
> diff --git a/controller/binding.c b/controller/binding.c
> index 2997fcae8..d5e8e4773 100644
> --- a/controller/binding.c
> +++ b/controller/binding.c
> @@ -360,17 +360,6 @@ destroy_qos_map(struct hmap *qos_map)
>      hmap_destroy(qos_map);
>  }
>
> -static void
> -update_local_lport_ids(struct sset *local_lport_ids,
> -                       const struct sbrec_port_binding *binding_rec)
> -{
> -        char buf[16];
> -        snprintf(buf, sizeof(buf), "%"PRId64"_%"PRId64,
> -                 binding_rec->datapath->tunnel_key,
> -                 binding_rec->tunnel_key);
> -        sset_add(local_lport_ids, buf);
> -}
> -
>  /*
>   * Get the encap from the chassis for this port. The interface
>   * may have an external_ids:encap-ip=<encap-ip> set; if so we
> @@ -449,10 +438,10 @@ is_network_plugged(const struct sbrec_port_binding
*binding_rec,
>  }
>
>  static void
> -consider_localnet_port(const struct sbrec_port_binding *binding_rec,
> -                       struct shash *bridge_mappings,
> -                       struct sset *egress_ifaces,
> -                       struct hmap *local_datapaths)
> +update_ld_localnet_port(const struct sbrec_port_binding *binding_rec,
> +                        struct shash *bridge_mappings,
> +                        struct sset *egress_ifaces,
> +                        struct hmap *local_datapaths)
>  {
>      /* Ignore localnet ports for unplugged networks. */
>      if (!is_network_plugged(binding_rec, bridge_mappings)) {
> @@ -482,6 +471,28 @@ consider_localnet_port(const struct
sbrec_port_binding *binding_rec,
>      ld->localnet_port = binding_rec;
>  }
>
> +static void
> +update_local_lport_ids(struct sset *local_lport_ids,
> +                       const struct sbrec_port_binding *binding_rec)
> +{
> +        char buf[16];
> +        snprintf(buf, sizeof(buf), "%"PRId64"_%"PRId64,
> +                 binding_rec->datapath->tunnel_key,
> +                 binding_rec->tunnel_key);
> +        sset_add(local_lport_ids, buf);
> +}
> +
> +static void
> +remove_local_lport_ids(const struct sbrec_port_binding *binding_rec,
> +                       struct sset *local_lport_ids)
> +{
> +        char buf[16];
> +        snprintf(buf, sizeof(buf), "%"PRId64"_%"PRId64,
> +                 binding_rec->datapath->tunnel_key,
> +                 binding_rec->tunnel_key);
> +        sset_find_and_delete(local_lport_ids, buf);
> +}
> +
>  /* Local bindings. binding.c module binds the logical port (represented
by
>   * Port_Binding rows) and sets the 'chassis' column when it is sees the
>   * OVS interface row (of type "" or "internal") with the
> @@ -599,6 +610,14 @@ local_bindings_destroy(struct shash *local_bindings)
>      shash_destroy(local_bindings);
>  }
>
> +static
> +void local_binding_delete(struct shash *local_bindings,
> +                          struct local_binding *lbinding)
> +{
> +    shash_find_and_delete(local_bindings, lbinding->name);
> +    local_binding_destroy(lbinding);
> +}
> +
>  static void
>  local_binding_add_child(struct local_binding *lbinding,
>                          struct local_binding *child)
> @@ -625,6 +644,7 @@ is_lport_container(const struct sbrec_port_binding
*pb)
>      return !pb->type[0] && pb->parent_port && pb->parent_port[0];
>  }
>
> +/* Corresponds to each Port_Binding.type. */
>  enum en_lport_type {
>      LP_UNKNOWN,
>      LP_VIF,
> @@ -670,12 +690,20 @@ get_lport_type(const struct sbrec_port_binding *pb)
>      return LP_UNKNOWN;
>  }
>
> -static void
> +/* Returns false if lport is not claimed due to 'sb_readonly'.
> + * Returns true otherwise.
> + */
> +static bool
>  claim_lport(const struct sbrec_port_binding *pb,
>              const struct sbrec_chassis *chassis_rec,
> -            const struct ovsrec_interface *iface_rec)
> +            const struct ovsrec_interface *iface_rec,
> +            bool sb_readonly)
>  {
>      if (pb->chassis != chassis_rec) {
> +        if (sb_readonly) {
> +            return false;
> +        }
> +
>          if (pb->chassis) {
>              VLOG_INFO("Changing chassis for lport %s from %s to %s.",
>                      pb->logical_port, pb->chassis->name,
> @@ -694,26 +722,48 @@ claim_lport(const struct sbrec_port_binding *pb,
>      struct sbrec_encap *encap_rec =
>          sbrec_get_port_encap(chassis_rec, iface_rec);
>      if (encap_rec && pb->encap != encap_rec) {
> +        if (sb_readonly) {
> +            return false;
> +        }
>          sbrec_port_binding_set_encap(pb, encap_rec);
>      }
> +
> +    return true;
>  }
>
> -static void
> -release_lport(const struct sbrec_port_binding *pb)
> +/* Returns false if lport is not released due to 'sb_readonly'.
> + * Returns true otherwise.
> + */
> +static bool
> +release_lport(const struct sbrec_port_binding *pb, bool sb_readonly)
>  {
>      if (!pb) {
> -        return;
> +        return true;
>      }
>
> -    VLOG_INFO("Releasing lport %s from this chassis.", pb->logical_port);
>      if (pb->encap) {
> +        if (sb_readonly) {
> +            return false;
> +        }
>          sbrec_port_binding_set_encap(pb, NULL);
>      }
> -    sbrec_port_binding_set_chassis(pb, NULL);
> +
> +    if (pb->chassis) {
> +        if (sb_readonly) {
> +            return false;
> +        }
> +        sbrec_port_binding_set_chassis(pb, NULL);
> +    }
>
>      if (pb->virtual_parent) {
> +        if (sb_readonly) {
> +            return false;
> +        }
>          sbrec_port_binding_set_virtual_parent(pb, NULL);
>      }
> +
> +    VLOG_INFO("Releasing lport %s from this chassis.", pb->logical_port);
> +    return true;
>  }
>
>  static bool
> @@ -738,7 +788,41 @@ can_bind_on_this_chassis(const struct sbrec_chassis
*chassis_rec,
>             || !strcmp(requested_chassis, chassis_rec->hostname);
>  }
>
> -static void
> +static bool
> +release_local_binding_children(const struct sbrec_chassis *chassis_rec,
> +                               struct local_binding *lbinding,
> +                               bool sb_readonly)
> +{
> +    struct shash_node *node;
> +    SHASH_FOR_EACH (node, &lbinding->children) {
> +        struct local_binding *l = node->data;
> +        if (is_lbinding_this_chassis(l, chassis_rec)) {
> +            if (!release_lport(l->pb, sb_readonly)) {
> +                return false;
> +            }
> +        }
> +    }
> +
> +    return true;
> +}
> +
> +static bool
> +release_local_binding(const struct sbrec_chassis *chassis_rec,
> +                      struct local_binding *lbinding, bool sb_readonly)
> +{
> +    if (!release_local_binding_children(chassis_rec, lbinding,
> +                                        sb_readonly)) {
> +        return false;
> +    }
> +
> +    if (is_lbinding_this_chassis(lbinding, chassis_rec)) {
> +        return release_lport(lbinding->pb, sb_readonly);
> +    }
> +
> +    return true;
> +}
> +
> +static bool
>  consider_vif_lport_(const struct sbrec_port_binding *pb,
>                      bool can_bind, const char *vif_chassis,
>                      struct binding_ctx_in *b_ctx_in,
> @@ -750,7 +834,10 @@ consider_vif_lport_(const struct sbrec_port_binding
*pb,
>      if (lbinding_set) {
>          if (can_bind) {
>              /* We can claim the lport. */
> -            claim_lport(pb, b_ctx_in->chassis_rec, lbinding->iface);
> +            if (!claim_lport(pb, b_ctx_in->chassis_rec, lbinding->iface,
> +                             !b_ctx_in->ovnsb_idl_txn)){
> +                return false;
> +            }
>
>              add_local_datapath(b_ctx_in->sbrec_datapath_binding_by_key,
>                              b_ctx_in->sbrec_port_binding_by_datapath,
> @@ -775,13 +862,14 @@ consider_vif_lport_(const struct sbrec_port_binding
*pb,
>      if (pb->chassis == b_ctx_in->chassis_rec) {
>          /* Release the lport if there is no lbinding. */
>          if (!lbinding_set || !can_bind) {
> -            release_lport(pb);
> +            return release_lport(pb, !b_ctx_in->ovnsb_idl_txn);
>          }
>      }
>
> +    return true;
>  }
>
> -static void
> +static bool
>  consider_vif_lport(const struct sbrec_port_binding *pb,
>                     struct binding_ctx_in *b_ctx_in,
>                     struct binding_ctx_out *b_ctx_out,
> @@ -801,11 +889,11 @@ consider_vif_lport(const struct sbrec_port_binding
*pb,
>          lbinding->pb = pb;
>      }
>
> -    consider_vif_lport_(pb, can_bind, vif_chassis, b_ctx_in,
> -                        b_ctx_out, lbinding, qos_map);
> +    return consider_vif_lport_(pb, can_bind, vif_chassis, b_ctx_in,
> +                               b_ctx_out, lbinding, qos_map);
>  }
>
> -static void
> +static bool
>  consider_container_lport(const struct sbrec_port_binding *pb,
>                           struct binding_ctx_in *b_ctx_in,
>                           struct binding_ctx_out *b_ctx_out,
> @@ -815,7 +903,39 @@ consider_container_lport(const struct
sbrec_port_binding *pb,
>      parent_lbinding = local_binding_find(b_ctx_out->local_bindings,
>                                           pb->parent_port);
>
> -    if (parent_lbinding && !parent_lbinding->pb) {
> +    if (!parent_lbinding) {
> +        /* There is no local_binding for parent port. Create it
> +         * without OVS interface row. This is the only exception
> +         * for creating the 'struct local_binding' object without
> +         * corresponding OVS interface row.
> +         *
> +         * This is required for the following reasons:
> +         *   - If a logical port P1 is created and then
> +         *     few container ports - C1, C2, .. are created first by CMS.
> +         *   - And later when OVS interface row  is created for P1, then
> +         *     we want the these container ports also be claimed by the
> +         *     chassis.
> +         * */

It seems the assumption is, for all container PBs, their parent PBs should
always have local_binding created, no matter if they are bound to current
chassis.
However, at least some of the code in this patch would violate this
assumption, e.g. handle_iface_delete() will call local_binding_delete() and
delete the parent binding and all the children container bindings.
It is also better to document this clearly in the comment of struct
local_binding for what should be expected in the local_bindings. My
understanding is, for each binding_run() or change_handler execution, the
expected items left in it is:
1) For each interface that has iface-id configured.
2) For each container PBs and their parent PBs, no matter if they are bound
to this chassis.
3) For some of the non VIF PBs ...
Is this correct?

> +        parent_lbinding = local_binding_create(pb->parent_port, NULL,
NULL,
> +                                               BT_VIF);
> +        local_binding_add(b_ctx_out->local_bindings, parent_lbinding);
> +    }
> +
> +    struct local_binding *container_lbinding =
> +        local_binding_find_child(parent_lbinding, pb->logical_port);
> +
> +    if (!container_lbinding) {
> +        container_lbinding = local_binding_create(pb->logical_port,
> +                                                  parent_lbinding->iface,
> +                                                  pb, BT_CONTAINER);
> +        local_binding_add_child(parent_lbinding, container_lbinding);
> +    } else {
> +        ovs_assert(container_lbinding->type == BT_CONTAINER);
> +        container_lbinding->pb = pb;
> +        container_lbinding->iface = parent_lbinding->iface;
> +    }
> +
> +    if (!parent_lbinding->pb) {
>          parent_lbinding->pb = lport_lookup_by_name(
>              b_ctx_in->sbrec_port_binding_by_name, pb->parent_port);
>
> @@ -824,37 +944,28 @@ consider_container_lport(const struct
sbrec_port_binding *pb,
>               * So call consider_vif_lport() to process it first. */
>              consider_vif_lport(parent_lbinding->pb, b_ctx_in, b_ctx_out,
>                                 parent_lbinding, qos_map);
> -        }
> -    }
> +        } else {
> +            /* The parent lport doesn't exist. Call release_lport() to
> +             * release the container lport, if it was bound earlier. */
> +            if (is_lbinding_this_chassis(container_lbinding,
> +                                         b_ctx_in->chassis_rec)) {
> +               return release_lport(pb, !b_ctx_in->ovnsb_idl_txn);
> +            }
>
> -    if (!parent_lbinding || !parent_lbinding->pb) {
> -        /* Call release_lport, to release the container lport, if
> -         * it was bound earlier. */
> -        if (pb->chassis == b_ctx_in->chassis_rec) {
> -            release_lport(pb);
> +            return true;
>          }
> -        return;
>      }
>
> -    struct local_binding *container_lbinding =
> -        local_binding_find_child(parent_lbinding, pb->logical_port);
> -    ovs_assert(!container_lbinding);
> -
> -    container_lbinding = local_binding_create(pb->logical_port,
> -                                              parent_lbinding->iface,
> -                                              pb, BT_CONTAINER);
> -    local_binding_add_child(parent_lbinding, container_lbinding);
> -
>      const char *vif_chassis = smap_get(&parent_lbinding->pb->options,
>                                         "requested-chassis");
>      bool can_bind = can_bind_on_this_chassis(b_ctx_in->chassis_rec,
>                                               vif_chassis);
>
> -    consider_vif_lport_(pb, can_bind, vif_chassis, b_ctx_in, b_ctx_out,
> -                        container_lbinding, qos_map);
> +    return consider_vif_lport_(pb, can_bind, vif_chassis, b_ctx_in,
b_ctx_out,
> +                               container_lbinding, qos_map);
>  }
>
> -static void
> +static bool
>  consider_virtual_lport(const struct sbrec_port_binding *pb,
>                         struct binding_ctx_in *b_ctx_in,
>                         struct binding_ctx_out *b_ctx_out,
> @@ -881,11 +992,16 @@ consider_virtual_lport(const struct
sbrec_port_binding *pb,
>      if (is_lbinding_this_chassis(parent_lbinding,
b_ctx_in->chassis_rec)) {
>          virtual_lbinding =
>              local_binding_find_child(parent_lbinding, pb->logical_port);
> -        ovs_assert(!virtual_lbinding);
> -        virtual_lbinding = local_binding_create(pb->logical_port,
> -                                                parent_lbinding->iface,
> -                                                pb, BT_VIRTUAL);
> -        local_binding_add_child(parent_lbinding, virtual_lbinding);
> +        if (!virtual_lbinding) {
> +            virtual_lbinding = local_binding_create(pb->logical_port,
> +
 parent_lbinding->iface,
> +                                                    pb, BT_VIRTUAL);
> +            local_binding_add_child(parent_lbinding, virtual_lbinding);
> +        } else {
> +            ovs_assert(virtual_lbinding->type == BT_VIRTUAL);
> +            virtual_lbinding->pb = pb;
> +            virtual_lbinding->iface = parent_lbinding->iface;
> +        }
>      }
>
>      return consider_vif_lport_(pb, true, NULL, b_ctx_in, b_ctx_out,
> @@ -895,14 +1011,13 @@ consider_virtual_lport(const struct
sbrec_port_binding *pb,
>  /* Considers either claiming the lport or releasing the lport
>   * for non VIF lports.
>   */
> -static void
> +static bool
>  consider_nonvif_lport_(const struct sbrec_port_binding *pb,
>                         bool our_chassis,
>                         bool has_local_l3gateway,
>                         struct binding_ctx_in *b_ctx_in,
>                         struct binding_ctx_out *b_ctx_out)
>  {
> -    ovs_assert(b_ctx_in->ovnsb_idl_txn);
>      if (our_chassis) {
>          sset_add(b_ctx_out->local_lports, pb->logical_port);
>          add_local_datapath(b_ctx_in->sbrec_datapath_binding_by_key,
> @@ -912,13 +1027,16 @@ consider_nonvif_lport_(const struct
sbrec_port_binding *pb,
>                             b_ctx_out->local_datapaths);
>
>          update_local_lport_ids(b_ctx_out->local_lport_ids, pb);
> -        claim_lport(pb, b_ctx_in->chassis_rec, NULL);
> +        return claim_lport(pb, b_ctx_in->chassis_rec, NULL,
> +                           !b_ctx_in->ovnsb_idl_txn);
>      } else if (pb->chassis == b_ctx_in->chassis_rec) {
> -        release_lport(pb);
> +        return release_lport(pb, !b_ctx_in->ovnsb_idl_txn);
>      }
> +
> +    return true;
>  }
>
> -static void
> +static bool
>  consider_l2gw_lport(const struct sbrec_port_binding *pb,
>                      struct binding_ctx_in *b_ctx_in,
>                      struct binding_ctx_out *b_ctx_out)
> @@ -927,10 +1045,10 @@ consider_l2gw_lport(const struct
sbrec_port_binding *pb,
>      bool our_chassis = chassis_id && !strcmp(chassis_id,
>
b_ctx_in->chassis_rec->name);
>
> -    consider_nonvif_lport_(pb, our_chassis, false, b_ctx_in, b_ctx_out);
> +    return consider_nonvif_lport_(pb, our_chassis, false, b_ctx_in,
b_ctx_out);
>  }
>
> -static void
> +static bool
>  consider_l3gw_lport(const struct sbrec_port_binding *pb,
>                      struct binding_ctx_in *b_ctx_in,
>                      struct binding_ctx_out *b_ctx_out)
> @@ -939,7 +1057,7 @@ consider_l3gw_lport(const struct sbrec_port_binding
*pb,
>      bool our_chassis = chassis_id && !strcmp(chassis_id,
>
b_ctx_in->chassis_rec->name);
>
> -    consider_nonvif_lport_(pb, our_chassis, true, b_ctx_in, b_ctx_out);
> +    return consider_nonvif_lport_(pb, our_chassis, true, b_ctx_in,
b_ctx_out);
>  }
>
>  static void
> @@ -958,7 +1076,7 @@ consider_localnet_lport(const struct
sbrec_port_binding *pb,
>      update_local_lport_ids(b_ctx_out->local_lport_ids, pb);
>  }
>
> -static void
> +static bool
>  consider_ha_lport(const struct sbrec_port_binding *pb,
>                    struct binding_ctx_in *b_ctx_in,
>                    struct binding_ctx_out *b_ctx_out)
> @@ -986,18 +1104,18 @@ consider_ha_lport(const struct sbrec_port_binding
*pb,
>                             b_ctx_out->local_datapaths);
>      }
>
> -    consider_nonvif_lport_(pb, our_chassis, false, b_ctx_in, b_ctx_out);
> +    return consider_nonvif_lport_(pb, our_chassis, false, b_ctx_in,
b_ctx_out);
>  }
>
> -static void
> +static bool
>  consider_cr_lport(const struct sbrec_port_binding *pb,
>                    struct binding_ctx_in *b_ctx_in,
>                    struct binding_ctx_out *b_ctx_out)
>  {
> -    consider_ha_lport(pb, b_ctx_in, b_ctx_out);
> +    return consider_ha_lport(pb, b_ctx_in, b_ctx_out);
>  }
>
> -static void
> +static bool
>  consider_external_lport(const struct sbrec_port_binding *pb,
>                          struct binding_ctx_in *b_ctx_in,
>                          struct binding_ctx_out *b_ctx_out)
> @@ -1050,6 +1168,8 @@ build_local_bindings(struct binding_ctx_in
*b_ctx_in,
>                  }
>
>                  sset_add(b_ctx_out->local_lports, iface_id);
> +                smap_replace(b_ctx_out->local_iface_ids, iface_rec->name,
> +                             iface_id);
>              }
>
>              /* Check if this is a tunnel interface. */
> @@ -1165,9 +1285,9 @@ binding_run(struct binding_ctx_in *b_ctx_in, struct
binding_ctx_out *b_ctx_out)
>       * corresponding local datapath accordingly. */
>      struct localnet_lport *lnet_lport;
>      LIST_FOR_EACH_POP (lnet_lport, list_node, &localnet_lports) {
> -        consider_localnet_port(lnet_lport->pb, &bridge_mappings,
> -                               b_ctx_out->egress_ifaces,
> -                               b_ctx_out->local_datapaths);
> +        update_ld_localnet_port(lnet_lport->pb, &bridge_mappings,
> +                                b_ctx_out->egress_ifaces,
> +                                b_ctx_out->local_datapaths);
>          free(lnet_lport);
>      }
>
> @@ -1185,95 +1305,625 @@ binding_run(struct binding_ctx_in *b_ctx_in,
struct binding_ctx_out *b_ctx_out)
>      destroy_qos_map(&qos_map);
>  }
>
> -/* Returns true if port-binding changes potentially require flow changes
on
> - * the current chassis. Returns false if we are sure there is no impact.
*/
> +/* Returns true if the database is all cleaned up, false if more work is
> + * required. */
>  bool
> -binding_evaluate_port_binding_changes(struct binding_ctx_in *b_ctx_in,
> -                                      struct binding_ctx_out *b_ctx_out)
> +binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn,
> +                const struct sbrec_port_binding_table
*port_binding_table,
> +                const struct sbrec_chassis *chassis_rec)
>  {
> -    if (!b_ctx_in->chassis_rec) {
> +    if (!ovnsb_idl_txn) {
> +        return false;
> +    }
> +    if (!chassis_rec) {
>          return true;
>      }
>
> -    bool changed = false;
> -
>      const struct sbrec_port_binding *binding_rec;
> -    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (binding_rec,
> -
b_ctx_in->port_binding_table) {
> -        /* XXX: currently OVSDB change tracking doesn't support getting
old
> -         * data when the operation is update, so if a port-binding moved
from
> -         * this chassis to another, there is no easy way to find out the
> -         * change. To workaround this problem, we just makes sure if
> -         * any port *related to* this chassis has any change, then
trigger
> -         * recompute.
> -         *
> -         * - If a regular VIF is unbound from this chassis, the local
ovsdb
> -         *   interface table will be updated, which will trigger
recompute.
> -         *
> -         * - If the port is not a regular VIF, always trigger recompute.
*/
> -        if (binding_rec->chassis == b_ctx_in->chassis_rec) {
> -            changed = true;
> +    bool any_changes = false;
> +    SBREC_PORT_BINDING_TABLE_FOR_EACH (binding_rec, port_binding_table) {
> +        if (binding_rec->chassis == chassis_rec) {
> +            if (binding_rec->encap) {
> +                sbrec_port_binding_set_encap(binding_rec, NULL);
> +            }
> +            sbrec_port_binding_set_chassis(binding_rec, NULL);
> +            any_changes = true;
> +        }
> +    }
> +
> +    if (any_changes) {
> +        ovsdb_idl_txn_add_comment(
> +            ovnsb_idl_txn,
> +            "ovn-controller: removing all port bindings for '%s'",
> +            chassis_rec->name);
> +    }
> +
> +    return !any_changes;
> +}
> +
> +static void
> +add_local_datapath_peer_port(const struct sbrec_port_binding *pb,
> +                             struct binding_ctx_in *b_ctx_in,
> +                             struct binding_ctx_out *b_ctx_out,
> +                             struct local_datapath *ld)
> +{
> +    const char *peer_name = smap_get(&pb->options, "peer");
> +    if (strcmp(pb->type, "patch") || !peer_name) {
> +        return;
> +    }
> +
> +    const struct sbrec_port_binding *peer;
> +    peer = lport_lookup_by_name(b_ctx_in->sbrec_port_binding_by_name,
> +                                peer_name);
> +
> +    if (!peer || !peer->datapath) {
> +        return;
> +    }
> +
> +    bool present = false;
> +    for (size_t i = 0; i < ld->n_peer_ports; i++) {
> +        if (ld->peer_ports[i].local == pb) {
> +            present = true;
>              break;
>          }
> +    }
>
> -        if (!strcmp(binding_rec->type, "remote")) {
> -            continue;
> +    if (!present) {
> +        ld->n_peer_ports++;
> +        if (ld->n_peer_ports > ld->n_allocated_peer_ports) {
> +            ld->peer_ports =
> +                x2nrealloc(ld->peer_ports,
> +                           &ld->n_allocated_peer_ports,
> +                           sizeof *ld->peer_ports);
> +        }
> +        ld->peer_ports[ld->n_peer_ports - 1].local = pb;
> +        ld->peer_ports[ld->n_peer_ports - 1].remote = peer;
> +    }
> +
> +    struct local_datapath *peer_ld =
> +        get_local_datapath(b_ctx_out->local_datapaths,
> +                           peer->datapath->tunnel_key);
> +    if (!peer_ld) {
> +        add_local_datapath__(b_ctx_in->sbrec_datapath_binding_by_key,
> +                             b_ctx_in->sbrec_port_binding_by_datapath,
> +                             b_ctx_in->sbrec_port_binding_by_name,
> +                             peer->datapath, false,
> +                             1, b_ctx_out->local_datapaths);
> +        return;
> +    }
> +
> +    for (size_t i = 0; i < peer_ld->n_peer_ports; i++) {
> +        if (peer_ld->peer_ports[i].local == peer) {
> +            return;
>          }
> +    }
>
> -        if (strcmp(binding_rec->type, "")) {
> -            changed = true;
> +    peer_ld->n_peer_ports++;
> +    if (peer_ld->n_peer_ports > peer_ld->n_allocated_peer_ports) {
> +        peer_ld->peer_ports =
> +            x2nrealloc(peer_ld->peer_ports,
> +                        &peer_ld->n_allocated_peer_ports,
> +                        sizeof *peer_ld->peer_ports);
> +    }
> +    peer_ld->peer_ports[peer_ld->n_peer_ports - 1].local = peer;
> +    peer_ld->peer_ports[peer_ld->n_peer_ports - 1].remote = pb;
> +}
> +
> +static void
> +remove_local_datapath_peer_port(const struct sbrec_port_binding *pb,
> +                                struct local_datapath *ld,
> +                                struct hmap *local_datapaths)
> +{
> +    size_t i = 0;
> +    for (i = 0; i < ld->n_peer_ports; i++) {
> +        if (ld->peer_ports[i].local == pb) {
>              break;
>          }
> +    }
> +
> +    if (i == ld->n_peer_ports) {
> +        return;
> +    }
> +
> +    const struct sbrec_port_binding *peer = ld->peer_ports[i].remote;
>
> -        struct local_binding *lbinding = NULL;
> -        if (!binding_rec->parent_port || !binding_rec->parent_port[0]) {
> -            lbinding = local_binding_find(b_ctx_out->local_bindings,
> -                                          binding_rec->logical_port);
> +    /* Possible improvement: We can shrint the allocated peer ports
> +     * if (ld->n_peer_ports < ld->n_allocated_peer_ports / 2).
> +     */
> +    ld->peer_ports[i].local = ld->peer_ports[ld->n_peer_ports - 1].local;
> +    ld->peer_ports[i].remote = ld->peer_ports[ld->n_peer_ports -
1].remote;
> +    ld->n_peer_ports--;
> +
> +    struct local_datapath *peer_ld =
> +        get_local_datapath(local_datapaths, peer->datapath->tunnel_key);
> +    if (peer_ld) {
> +        remove_local_datapath_peer_port(peer, peer_ld, local_datapaths);

Sorry that I don't understand this part. peer's peer is the "pb" that is
passed in the parameter of the current function call, so this recursive
call would only try to remove the "pb", which is already removed (before
calling this function). So I think it is useless.

> +    }
> +}
> +
> +static void
> +remove_pb_from_local_datapath(const struct sbrec_port_binding *pb,
> +                              const struct sbrec_chassis *chassis_rec,
> +                              struct binding_ctx_out *b_ctx_out,
> +                              struct local_datapath *ld)
> +{
> +    remove_local_lport_ids(pb, b_ctx_out->local_lport_ids);
> +    if (!strcmp(pb->type, "patch") ||
> +        !strcmp(pb->type, "l3gateway")) {
> +        remove_local_datapath_peer_port(pb, ld,
b_ctx_out->local_datapaths);
> +    } else if (!strcmp(pb->type, "localnet")) {
> +        if (ld->localnet_port && !strcmp(ld->localnet_port->logical_port,
> +                                         pb->logical_port)) {
> +            ld->localnet_port = NULL;
> +        }
> +    } else if (!strcmp(pb->type, "l3gateway")) {
> +        const char *chassis_id = smap_get(&pb->options,
> +                                          "l3gateway-chassis");
> +        if (chassis_id && !strcmp(chassis_id, chassis_rec->name)) {
> +            ld->has_local_l3gateway = false;
> +        }
> +    }

I think we need to remove ld from the local_datapaths if the current pb is
the only reason for that ld to be exist on this chassis. Also, all the
related lds connected by patch ports should be removed as well, if there
are no other pbs causing them to be added to this chassis. This is the
reverse of add_local_datapath__(). It doesn't seem to be easy without going
through all local_lbindings. Maybe this can be done in
binding_handle_ovs_interface_changes() and
binding_handle_port_binding_changes() out of the loop.

> +}
> +
> +static bool
> +handle_iface_add(const struct ovsrec_interface *iface_rec,
> +                 const char *iface_id,
> +                 struct binding_ctx_in *b_ctx_in,
> +                 struct binding_ctx_out *b_ctx_out,
> +                 struct hmap *qos_map)
> +{
> +    sset_add(b_ctx_out->local_lports, iface_id);
> +    smap_replace(b_ctx_out->local_iface_ids, iface_rec->name, iface_id);
> +
> +    struct local_binding *lbinding =
> +        local_binding_find(b_ctx_out->local_bindings, iface_id);
> +
> +    if (!lbinding) {
> +        lbinding = local_binding_create(iface_id, iface_rec, NULL,
BT_VIF);
> +        local_binding_add(b_ctx_out->local_bindings, lbinding);
> +    } else {
> +        lbinding->iface = iface_rec;
> +    }
> +
> +    if (!lbinding->pb || strcmp(lbinding->name,
lbinding->pb->logical_port)) {
> +        lbinding->pb = lport_lookup_by_name(
> +            b_ctx_in->sbrec_port_binding_by_name, lbinding->name);
> +        if (lbinding->pb && !strcmp(lbinding->pb->type, "virtual")) {

Since we never bind "virtual" ports, this should be a misconfiguration, and
it is better to log a warning message to be more clear.

> +            lbinding->pb = NULL;
> +        }
> +    }
> +
> +    if (lbinding->pb) {
> +        if (!consider_vif_lport(lbinding->pb, b_ctx_in, b_ctx_out,
> +                                lbinding, qos_map)) {
> +            return false;
> +        }
> +    }
> +
> +    /* Update the child local_binding's iface (if any children) and try
to
> +     *  claim the container lbindings. */
> +    struct shash_node *node;
> +    SHASH_FOR_EACH (node, &lbinding->children) {
> +        struct local_binding *child = node->data;
> +        child->iface = iface_rec;
> +        if (child->type == BT_CONTAINER) {
> +            if (!consider_container_lport(child->pb, b_ctx_in, b_ctx_out,
> +                                          qos_map)) {
> +                return false;
> +            }
> +        }
> +    }
> +
> +    return true;
> +}
> +
> +static bool
> +handle_iface_delete(const char *lport_name, const char *iface_name,
> +                    struct binding_ctx_in *b_ctx_in,
> +                    struct binding_ctx_out *b_ctx_out, bool *changed)
> +{
> +    struct local_binding *lbinding;
> +    lbinding = local_binding_find(b_ctx_out->local_bindings,
> +                                  lport_name);
> +    if (lbinding && lbinding->pb &&
> +        lbinding->pb->chassis == b_ctx_in->chassis_rec) {
> +
> +        if (!release_local_binding(b_ctx_in->chassis_rec, lbinding,
> +                                   !b_ctx_in->ovnsb_idl_txn)) {
> +            return false;
> +        }
> +
> +        struct local_datapath *ld =
> +            get_local_datapath(b_ctx_out->local_datapaths,
> +                                lbinding->pb->datapath->tunnel_key);
> +        if (ld) {
> +            remove_pb_from_local_datapath(lbinding->pb,
> +                                            b_ctx_in->chassis_rec,
> +                                            b_ctx_out, ld);
> +        }
> +        local_binding_delete(b_ctx_out->local_bindings, lbinding);
> +        *changed = true;
> +    }
> +
> +    sset_find_and_delete(b_ctx_out->local_lports, lport_name);
> +    smap_remove(b_ctx_out->local_iface_ids, iface_name);
> +
> +    return true;
> +}
> +
> +static bool
> +is_iface_vif(const struct ovsrec_interface *iface_rec)
> +{
> +    if (iface_rec->type && iface_rec->type[0] &&
> +        strcmp(iface_rec->type, "internal")) {
> +        return false;
> +    }
> +
> +    return true;
> +}
> +
> +/* Returns true if the ovs interface changes were handled successfully,
> + * false otherwise.
> + */
> +bool
> +binding_handle_ovs_interface_changes(struct binding_ctx_in *b_ctx_in,
> +                                     struct binding_ctx_out *b_ctx_out,
> +                                     bool *changed)
> +{
> +    if (!b_ctx_in->chassis_rec) {
> +        return false;
> +    }
> +
> +    bool handled = true;
> +    *changed = false;
> +
> +    /* Run the tracked interfaces loop twice. One to handle deleted
> +     * changes. And another to handle add/update changes.
> +     * This will ensure correctness.
> +     */
> +    const struct ovsrec_interface *iface_rec;
> +    OVSREC_INTERFACE_TABLE_FOR_EACH_TRACKED (iface_rec,
> +                                             b_ctx_in->iface_table) {
> +        if (!is_iface_vif(iface_rec)) {
> +            /* Right now are not handling ovs_interface changes of
> +             * other types. This can be enhanced to handle of
> +             * types - patch and tunnel. */
> +            handled = false;
> +            break;
> +        }
> +
> +        const char *iface_id = smap_get(&iface_rec->external_ids,
"iface-id");
> +        const char *old_iface_id = smap_get(b_ctx_out->local_iface_ids,
> +                                            iface_rec->name);
> +        const char *cleared_iface_id = NULL;
> +        if (!ovsrec_interface_is_deleted(iface_rec)) {
> +            if (iface_id) {
> +                /* Check if iface_id is changed. If so we need to
> +                 * release the old port binding and associate this
> +                 * inteface to new port binding. */
> +                if (old_iface_id && strcmp(iface_id, old_iface_id)) {
> +                    cleared_iface_id = old_iface_id;
> +                }
> +            } else if (old_iface_id) {
> +                cleared_iface_id = old_iface_id;
> +            }
>          } else {
> -            lbinding = local_binding_find(b_ctx_out->local_bindings,
> -                                          binding_rec->parent_port);
> +            cleared_iface_id = iface_id;
> +            iface_id = NULL;
>          }
>
> -        if (lbinding) {
> -            changed = true;
> +        if (cleared_iface_id) {
> +            handled = handle_iface_delete(cleared_iface_id,
iface_rec->name,
> +                                          b_ctx_in, b_ctx_out, changed);
> +        }
> +
> +        if (!handled) {
>              break;
>          }
>      }
>
> -    return changed;
> +    if (!handled) {
> +        return false;
> +    }
> +
> +    struct hmap qos_map = HMAP_INITIALIZER(&qos_map);
> +    struct hmap *qos_map_ptr =
> +        sset_is_empty(b_ctx_out->egress_ifaces) ? NULL : &qos_map;
> +
> +    OVSREC_INTERFACE_TABLE_FOR_EACH_TRACKED (iface_rec,
> +                                             b_ctx_in->iface_table) {
> +        /* Loop to handle create and update changes only. */

Thanks for splitting the loops, but here are still some problems.

1. The first loop handled: a) interface deletion. b) interface update with
old iface-id cleared. So in this second loop it should handle all the rest
cases: a) interface add. b) interface update without iface-id change. It
seems b) is treated as just interface add, which may be wrong: if iface-id
didn't change but some other fields of the interface record is changed, it
is not handled properly.

2. Some other condition checks should be done for this loop, such as: if
(!is_iface_vif(iface_rec)) ...

> +        if (ovsrec_interface_is_deleted(iface_rec)) {
> +            continue;
> +        }
> +
> +        const char *iface_id = smap_get(&iface_rec->external_ids,
"iface-id");
> +        int64_t ofport = iface_rec->n_ofport ? *iface_rec->ofport : 0;
> +        if (iface_id && ofport > 0) {

What if iface-id didn't change, but ofport changed from non-zero to 0? How
should we handle the change?

A general approach can be: whenever there is an update
(!ovsrec_interface_is_new()), we firstly handle it as interface delete, and
then handle an interface add.

> +            *changed = true;
> +            handled = handle_iface_add(iface_rec, iface_id, b_ctx_in,
> +                                       b_ctx_out, qos_map_ptr);
> +            if (!handled) {
> +                break;
> +            }
> +        }
> +    }
> +
> +    if (handled && qos_map_ptr && set_noop_qos(b_ctx_in->ovs_idl_txn,
> +                                               b_ctx_in->port_table,
> +                                               b_ctx_in->qos_table,
> +
b_ctx_out->egress_ifaces)) {
> +        const char *entry;
> +        SSET_FOR_EACH (entry, b_ctx_out->egress_ifaces) {
> +            setup_qos(entry, &qos_map);
> +        }
> +    }
> +
> +    destroy_qos_map(&qos_map);
> +    return handled;
>  }
>
> -/* Returns true if the database is all cleaned up, false if more work is
> - * required. */
> -bool
> -binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn,
> -                const struct sbrec_port_binding_table
*port_binding_table,
> -                const struct sbrec_chassis *chassis_rec)
> +static void
> +handle_deleted_lport(const struct sbrec_port_binding *pb,
> +                     struct binding_ctx_in *b_ctx_in,
> +                     struct binding_ctx_out *b_ctx_out)
>  {
> -    if (!ovnsb_idl_txn) {
> +    struct local_datapath *ld =
> +        get_local_datapath(b_ctx_out->local_datapaths,
> +                           pb->datapath->tunnel_key);
> +    if (ld) {
> +        remove_pb_from_local_datapath(pb, b_ctx_in->chassis_rec,
> +                                      b_ctx_out, ld);
> +    }
> +}
> +
> +static struct local_binding *
> +get_lbinding_for_lport(const struct sbrec_port_binding *pb,
> +                       enum en_lport_type lport_type,
> +                       struct binding_ctx_out *b_ctx_out)
> +{
> +    ovs_assert(lport_type == LP_VIF || lport_type == LP_VIRTUAL);
> +
> +    if (lport_type == LP_VIF && !is_lport_container(pb)) {
> +        return local_binding_find(b_ctx_out->local_bindings,
pb->logical_port);
> +    }
> +
> +    struct local_binding *parent_lbinding = NULL;
> +
> +    if (lport_type == LP_VIRTUAL) {
> +        parent_lbinding = local_binding_find(b_ctx_out->local_bindings,
> +                                             pb->virtual_parent);
> +    } else {
> +        parent_lbinding = local_binding_find(b_ctx_out->local_bindings,
> +                                             pb->parent_port);
> +    }
> +
> +    return parent_lbinding
> +           ? local_binding_find(&parent_lbinding->children,
pb->logical_port)
> +           : NULL;
> +}
> +
> +static bool
> +handle_deleted_vif_lport(const struct sbrec_port_binding *pb,
> +                         enum en_lport_type lport_type,
> +                         struct binding_ctx_in *b_ctx_in,
> +                         struct binding_ctx_out *b_ctx_out,
> +                         bool *changed)
> +{
> +    struct local_binding *lbinding =
> +        get_lbinding_for_lport(pb, lport_type, b_ctx_out);
> +
> +    if (lbinding) {
> +        lbinding->pb = NULL;
> +        /* The port_binding 'pb' is deleted. So there is no need to
> +         * clear the 'chassis' column of 'pb'. But we need to do
> +         * for the local_binding's children. */
> +        if (lbinding->type == BT_VIF &&
> +                !release_local_binding_children(b_ctx_in->chassis_rec,
> +                                                lbinding,
> +
 !b_ctx_in->ovnsb_idl_txn)) {
> +            return false;
> +        }
> +
> +        *changed = true;
> +    }
> +
> +    handle_deleted_lport(pb, b_ctx_in, b_ctx_out);
> +    return true;
> +}
> +
> +static bool
> +handle_updated_vif_lport(const struct sbrec_port_binding *pb,
> +                         enum en_lport_type lport_type,
> +                         struct binding_ctx_in *b_ctx_in,
> +                         struct binding_ctx_out *b_ctx_out,
> +                         struct hmap *qos_map,
> +                         bool *changed)
> +{
> +    bool claimed = (pb->chassis == b_ctx_in->chassis_rec);
> +    bool handled = true;
> +
> +    if (lport_type == LP_VIRTUAL) {
> +        handled = consider_virtual_lport(pb, b_ctx_in, b_ctx_out,
qos_map);
> +    } else if (lport_type == LP_VIF && is_lport_container(pb)) {
> +        handled = consider_container_lport(pb, b_ctx_in, b_ctx_out,
qos_map);
> +    } else {
> +        handled = consider_vif_lport(pb, b_ctx_in, b_ctx_out, NULL,
qos_map);
> +    }
> +
> +    if (!handled) {
>          return false;
>      }
> -    if (!chassis_rec) {
> +
> +    bool now_claimed = (pb->chassis == b_ctx_in->chassis_rec);
> +    bool changed_ = (claimed != now_claimed);
> +
> +    if (changed_) {
> +        *changed = true;
> +    }
> +
> +    if (lport_type == LP_VIRTUAL ||
> +        (lport_type == LP_VIF && is_lport_container(pb)) || !changed_) {
>          return true;
>      }
>
> -    const struct sbrec_port_binding *binding_rec;
> -    bool any_changes = false;
> -    SBREC_PORT_BINDING_TABLE_FOR_EACH (binding_rec, port_binding_table) {
> -        if (binding_rec->chassis == chassis_rec) {
> -            if (binding_rec->encap)
> -                sbrec_port_binding_set_encap(binding_rec, NULL);
> -            sbrec_port_binding_set_chassis(binding_rec, NULL);
> -            any_changes = true;
> +    struct local_binding *lbinding =
> +        local_binding_find(b_ctx_out->local_bindings, pb->logical_port);
> +
> +    ovs_assert(lbinding);
> +
> +    struct shash_node *node;
> +    SHASH_FOR_EACH (node, &lbinding->children) {
> +        struct local_binding *child = node->data;
> +        if (child->type == BT_CONTAINER) {
> +            handled = consider_container_lport(child->pb, b_ctx_in,
b_ctx_out,
> +                                               qos_map);
> +            if (!handled) {
> +                return false;
> +            }
>          }
>      }
>
> -    if (any_changes) {
> -        ovsdb_idl_txn_add_comment(
> -            ovnsb_idl_txn,
> -            "ovn-controller: removing all port bindings for '%s'",
> -            chassis_rec->name);
> +    return true;
> +}
> +
> +/* Returns true if the port binding changes resulted in local binding
> + * updates, false otherwise.
> + */
> +bool
> +binding_handle_port_binding_changes(struct binding_ctx_in *b_ctx_in,
> +                                    struct binding_ctx_out *b_ctx_out,
> +                                    bool *changed)
> +{
> +    bool handled = true;
> +    *changed = false;
> +
> +    const struct sbrec_port_binding *pb;
> +
> +    /* Run the tracked port binding loop twice. One to handle deleted
> +     * changes. And another to handle add/update changes.
> +     * This will ensure correctness.
> +     */
> +    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
> +
b_ctx_in->port_binding_table) {
> +        if (!sbrec_port_binding_is_deleted(pb)) {
> +            continue;
> +        }
> +
> +        enum en_lport_type lport_type = get_lport_type(pb);
> +        if (lport_type == LP_VIF || lport_type == LP_VIRTUAL) {
> +            handled = handle_deleted_vif_lport(pb, lport_type, b_ctx_in,
> +                                                b_ctx_out, changed);
> +        } else {
> +            handle_deleted_lport(pb, b_ctx_in, b_ctx_out);
> +        }
> +
> +        if (!handled) {
> +            break;
> +        }
>      }
>
> -    return !any_changes;
> +    if (!handled) {
> +        return false;
> +    }
> +
> +    struct hmap qos_map = HMAP_INITIALIZER(&qos_map);
> +    struct hmap *qos_map_ptr =
> +        sset_is_empty(b_ctx_out->egress_ifaces) ? NULL : &qos_map;
> +
> +    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
> +
b_ctx_in->port_binding_table) {
> +        /* Loop to handle create and update changes only. */
> +        if (sbrec_port_binding_is_deleted(pb)) {
> +            continue;
> +        }
> +
> +        enum en_lport_type lport_type = get_lport_type(pb);
> +
> +        struct local_datapath *ld =
> +            get_local_datapath(b_ctx_out->local_datapaths,
> +                               pb->datapath->tunnel_key);
> +
> +        switch (lport_type) {
> +        case LP_VIF:
> +        case LP_VIRTUAL:
> +            handled = handle_updated_vif_lport(pb, lport_type, b_ctx_in,
> +                                               b_ctx_out, qos_map_ptr,
> +                                               changed);
> +            break;
> +
> +        case LP_PATCH:
> +        case LP_LOCALPORT:
> +        case LP_VTEP:
> +            *changed = true;
> +            update_local_lport_ids(b_ctx_out->local_lport_ids, pb);
> +            if (lport_type ==  LP_PATCH) {
> +                /* Add the peer datapath to the local datapaths if it's
> +                    * not present yet.
> +                    */
> +                if (ld) {
> +                    add_local_datapath_peer_port(pb, b_ctx_in,
> +                                                    b_ctx_out, ld);
> +                }
> +            }
> +            break;
> +
> +        case LP_L2GATEWAY:
> +            *changed = true;
> +            handled = consider_l2gw_lport(pb, b_ctx_in, b_ctx_out);
> +            break;
> +
> +        case LP_L3GATEWAY:
> +            *changed = true;
> +            handled = consider_l3gw_lport(pb, b_ctx_in, b_ctx_out);
> +            break;
> +
> +        case LP_CHASSISREDIRECT:
> +            *changed = true;
> +            handled = consider_cr_lport(pb, b_ctx_in, b_ctx_out);
> +            break;
> +
> +        case LP_EXTERNAL:
> +            *changed = true;
> +            handled = consider_external_lport(pb, b_ctx_in, b_ctx_out);
> +            break;
> +
> +        case LP_LOCALNET: {
> +            *changed = true;
> +            consider_localnet_lport(pb, b_ctx_in, b_ctx_out,
qos_map_ptr);
> +
> +            struct shash bridge_mappings =
> +                SHASH_INITIALIZER(&bridge_mappings);
> +            add_ovs_bridge_mappings(b_ctx_in->ovs_table,
> +                                    b_ctx_in->bridge_table,
> +                                    &bridge_mappings);
> +            update_ld_localnet_port(pb, &bridge_mappings,
> +                                    b_ctx_out->egress_ifaces,
> +                                    b_ctx_out->local_datapaths);
> +            shash_destroy(&bridge_mappings);
> +            break;
> +        }
> +
> +        case LP_REMOTE:
> +        case LP_UNKNOWN:
> +            break;
> +        }
> +
> +        if (!handled) {
> +            break;
> +        }
> +    }
> +
> +    if (handled && qos_map_ptr && set_noop_qos(b_ctx_in->ovs_idl_txn,
> +                                               b_ctx_in->port_table,
> +                                               b_ctx_in->qos_table,
> +
b_ctx_out->egress_ifaces)) {
> +        const char *entry;
> +        SSET_FOR_EACH (entry, b_ctx_out->egress_ifaces) {
> +            setup_qos(entry, &qos_map);
> +        }
> +    }
> +
> +    destroy_qos_map(&qos_map);
> +    return handled;
>  }
> diff --git a/controller/binding.h b/controller/binding.h
> index 9affc9a96..f7ace6faf 100644
> --- a/controller/binding.h
> +++ b/controller/binding.h
> @@ -57,6 +57,7 @@ struct binding_ctx_out {
>      struct sset *local_lports;
>      struct sset *local_lport_ids;
>      struct sset *egress_ifaces;
> +    struct smap *local_iface_ids;
>  };
>
>  void binding_register_ovs_idl(struct ovsdb_idl *);
> @@ -64,9 +65,13 @@ void binding_run(struct binding_ctx_in *, struct
binding_ctx_out *);
>  bool binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn,
>                       const struct sbrec_port_binding_table *,
>                       const struct sbrec_chassis *);
> -bool binding_evaluate_port_binding_changes(struct binding_ctx_in *,
> -                                           struct binding_ctx_out *);
>
>  void local_bindings_init(struct shash *local_bindings);
>  void local_bindings_destroy(struct shash *local_bindings);
> +bool binding_handle_ovs_interface_changes(struct binding_ctx_in *,
> +                                          struct binding_ctx_out *,
> +                                          bool *changed);
> +bool binding_handle_port_binding_changes(struct binding_ctx_in *,
> +                                         struct binding_ctx_out *,
> +                                         bool *changed);
>  #endif /* controller/binding.h */
> diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
> index 6b759966b..08b074415 100644
> --- a/controller/ovn-controller.c
> +++ b/controller/ovn-controller.c
> @@ -753,6 +753,7 @@ enum sb_engine_node {
>      OVS_NODE(open_vswitch, "open_vswitch") \
>      OVS_NODE(bridge, "bridge") \
>      OVS_NODE(port, "port") \
> +    OVS_NODE(interface, "interface") \
>      OVS_NODE(qos, "qos")
>
>  enum ovs_engine_node {
> @@ -974,6 +975,7 @@ struct ed_type_runtime_data {
>      struct sset active_tunnels;
>
>      struct sset egress_ifaces;
> +    struct smap local_iface_ids;
>  };
>
>  static void *
> @@ -987,6 +989,7 @@ en_runtime_data_init(struct engine_node *node
OVS_UNUSED,
>      sset_init(&data->local_lport_ids);
>      sset_init(&data->active_tunnels);
>      sset_init(&data->egress_ifaces);
> +    smap_init(&data->local_iface_ids);
>      local_bindings_init(&data->local_bindings);
>      return data;
>  }
> @@ -1000,6 +1003,7 @@ en_runtime_data_cleanup(void *data)
>      sset_destroy(&rt_data->local_lport_ids);
>      sset_destroy(&rt_data->active_tunnels);
>      sset_destroy(&rt_data->egress_ifaces);
> +    smap_destroy(&rt_data->local_iface_ids);
>      struct local_datapath *cur_node, *next_node;
>      HMAP_FOR_EACH_SAFE (cur_node, next_node, hmap_node,
>                          &rt_data->local_datapaths) {
> @@ -1041,6 +1045,10 @@ init_binding_ctx(struct engine_node *node,
>          (struct ovsrec_port_table *)EN_OVSDB_GET(
>              engine_get_input("OVS_port", node));
>
> +    struct ovsrec_interface_table *iface_table =
> +        (struct ovsrec_interface_table *)EN_OVSDB_GET(
> +            engine_get_input("OVS_interface", node));
> +
>      struct ovsrec_qos_table *qos_table =
>          (struct ovsrec_qos_table *)EN_OVSDB_GET(
>              engine_get_input("OVS_qos", node));
> @@ -1070,6 +1078,7 @@ init_binding_ctx(struct engine_node *node,
>      b_ctx_in->sbrec_port_binding_by_datapath =
sbrec_port_binding_by_datapath;
>      b_ctx_in->sbrec_port_binding_by_name = sbrec_port_binding_by_name;
>      b_ctx_in->port_table = port_table;
> +    b_ctx_in->iface_table = iface_table;
>      b_ctx_in->qos_table = qos_table;
>      b_ctx_in->port_binding_table = pb_table;
>      b_ctx_in->br_int = br_int;
> @@ -1083,6 +1092,7 @@ init_binding_ctx(struct engine_node *node,
>      b_ctx_out->local_lport_ids = &rt_data->local_lport_ids;
>      b_ctx_out->egress_ifaces = &rt_data->egress_ifaces;
>      b_ctx_out->local_bindings = &rt_data->local_bindings;
> +    b_ctx_out->local_iface_ids = &rt_data->local_iface_ids;
>  }
>
>  static void
> @@ -1111,10 +1121,12 @@ en_runtime_data_run(struct engine_node *node,
void *data)
>          sset_destroy(local_lport_ids);
>          sset_destroy(active_tunnels);
>          sset_destroy(&rt_data->egress_ifaces);
> +        smap_destroy(&rt_data->local_iface_ids);
>          sset_init(local_lports);
>          sset_init(local_lport_ids);
>          sset_init(active_tunnels);
>          sset_init(&rt_data->egress_ifaces);
> +        smap_init(&rt_data->local_iface_ids);
>          local_bindings_init(&rt_data->local_bindings);
>      }
>
> @@ -1140,6 +1152,34 @@ en_runtime_data_run(struct engine_node *node, void
*data)
>      engine_set_node_state(node, EN_UPDATED);
>  }
>
> +static bool
> +runtime_data_ovs_interface_handler(struct engine_node *node, void *data)
> +{
> +    struct ed_type_runtime_data *rt_data = data;
> +    struct binding_ctx_in b_ctx_in;
> +    struct binding_ctx_out b_ctx_out;
> +    init_binding_ctx(node, rt_data, &b_ctx_in, &b_ctx_out);
> +
> +    bool changed = false;
> +    if (!binding_handle_ovs_interface_changes(&b_ctx_in, &b_ctx_out,
> +                                              &changed)) {
> +        return false;
> +    }
> +
> +    if (changed) {
> +        engine_set_node_state(node, EN_UPDATED);
> +    }
> +
> +    return true;
> +}
> +
> +static bool
> +runtime_data_noop_handler(struct engine_node *node OVS_UNUSED,
> +                          void *data OVS_UNUSED)
> +{
> +    return true;
> +}
> +
>  static bool
>  runtime_data_sb_port_binding_handler(struct engine_node *node, void
*data)
>  {
> @@ -1147,11 +1187,21 @@ runtime_data_sb_port_binding_handler(struct
engine_node *node, void *data)
>      struct binding_ctx_in b_ctx_in;
>      struct binding_ctx_out b_ctx_out;
>      init_binding_ctx(node, rt_data, &b_ctx_in, &b_ctx_out);
> +    if (!b_ctx_in.chassis_rec) {
> +        return false;
> +    }
> +
> +    bool changed = false;
> +    if (!binding_handle_port_binding_changes(&b_ctx_in, &b_ctx_out,
> +                                             &changed)) {
> +        return false;
> +    }
>
> -    bool changed = binding_evaluate_port_binding_changes(&b_ctx_in,
> -                                                         &b_ctx_out);
> +    if (changed) {
> +        engine_set_node_state(node, EN_UPDATED);
> +    }
>
> -    return !changed;
> +    return true;
>  }
>
>  /* Connection tracking zones. */
> @@ -1894,7 +1944,10 @@ main(int argc, char *argv[])
>
>      engine_add_input(&en_runtime_data, &en_ovs_open_vswitch, NULL);
>      engine_add_input(&en_runtime_data, &en_ovs_bridge, NULL);
> -    engine_add_input(&en_runtime_data, &en_ovs_port, NULL);
> +    engine_add_input(&en_runtime_data, &en_ovs_port,
> +                     runtime_data_noop_handler);
> +    engine_add_input(&en_runtime_data, &en_ovs_interface,
> +                     runtime_data_ovs_interface_handler);
>      engine_add_input(&en_runtime_data, &en_ovs_qos, NULL);
>
>      engine_add_input(&en_runtime_data, &en_sb_chassis, NULL);
> diff --git a/tests/ovn-performance.at b/tests/ovn-performance.at
> index a8a15f8fe..5dfc6f7ca 100644
> --- a/tests/ovn-performance.at
> +++ b/tests/ovn-performance.at
> @@ -239,6 +239,19 @@ for i in 1 2; do
>      ovn_attach n1 br-phys 192.168.0.$i
>  done
>
> +# Wait for the tunnel ports to be created and up.
> +# Otherwise this may affect the lflow_run count.
> +
> +OVS_WAIT_UNTIL([
> +    test $(as hv1 ovs-vsctl list interface ovn-hv2-0 | \
> +grep tunnel_egress_iface_carrier=up | wc -l) -eq 1
> +])
> +
> +OVS_WAIT_UNTIL([
> +    test $(as hv2 ovs-vsctl list interface ovn-hv1-0 | \
> +grep tunnel_egress_iface_carrier=up | wc -l) -eq 1
> +])
> +
>  # Add router lr1
>  OVN_CONTROLLER_EXPECT_HIT(
>      [hv1 hv2], [lflow_run],
> --
> 2.26.2
>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
Numan Siddique May 21, 2020, 10:48 a.m. UTC | #2
On Thu, May 21, 2020 at 6:57 AM Han Zhou <hzhou@ovn.org> wrote:

> Hi Numan, please see my comments below.
>

Thanks for the review. Please see below for a few comments.



>
> On Wed, May 20, 2020 at 12:41 PM <numans@ovn.org> wrote:
> >
> > From: Numan Siddique <numans@ovn.org>
> >
> > This patch handles SB port binding changes and OVS interface changes
> > incrementally in the runtime_data stage which otherwise would have
> > resulted in calls to binding_run().
> >
> > Prior to this patch, port binding changes were handled differently.
> > The changes were only evaluated to see if they need full recompute
> > or they can be ignored.
> >
> > With this patch, the runtime data is updated with the changes (both
> > SB port binding and OVS interface) and ports are claimed/released in
> > the handlers itself, avoiding the need to trigger recompute of runtime
> > data stage.
> >
> > Acked-by: Mark Michelson <mmichels@redhat.com>
> > Signed-off-by: Numan Siddique <numans@ovn.org>
> > ---
> >  controller/binding.c        | 906 +++++++++++++++++++++++++++++++-----
> >  controller/binding.h        |   9 +-
> >  controller/ovn-controller.c |  61 ++-
> >  tests/ovn-performance.at    |  13 +
> >  4 files changed, 855 insertions(+), 134 deletions(-)
> >
> > diff --git a/controller/binding.c b/controller/binding.c
> > index 2997fcae8..d5e8e4773 100644
> > --- a/controller/binding.c
> > +++ b/controller/binding.c
> > @@ -360,17 +360,6 @@ destroy_qos_map(struct hmap *qos_map)
> >      hmap_destroy(qos_map);
> >  }
> >
> > -static void
> > -update_local_lport_ids(struct sset *local_lport_ids,
> > -                       const struct sbrec_port_binding *binding_rec)
> > -{
> > -        char buf[16];
> > -        snprintf(buf, sizeof(buf), "%"PRId64"_%"PRId64,
> > -                 binding_rec->datapath->tunnel_key,
> > -                 binding_rec->tunnel_key);
> > -        sset_add(local_lport_ids, buf);
> > -}
> > -
> >  /*
> >   * Get the encap from the chassis for this port. The interface
> >   * may have an external_ids:encap-ip=<encap-ip> set; if so we
> > @@ -449,10 +438,10 @@ is_network_plugged(const struct sbrec_port_binding
> *binding_rec,
> >  }
> >
> >  static void
> > -consider_localnet_port(const struct sbrec_port_binding *binding_rec,
> > -                       struct shash *bridge_mappings,
> > -                       struct sset *egress_ifaces,
> > -                       struct hmap *local_datapaths)
> > +update_ld_localnet_port(const struct sbrec_port_binding *binding_rec,
> > +                        struct shash *bridge_mappings,
> > +                        struct sset *egress_ifaces,
> > +                        struct hmap *local_datapaths)
> >  {
> >      /* Ignore localnet ports for unplugged networks. */
> >      if (!is_network_plugged(binding_rec, bridge_mappings)) {
> > @@ -482,6 +471,28 @@ consider_localnet_port(const struct
> sbrec_port_binding *binding_rec,
> >      ld->localnet_port = binding_rec;
> >  }
> >
> > +static void
> > +update_local_lport_ids(struct sset *local_lport_ids,
> > +                       const struct sbrec_port_binding *binding_rec)
> > +{
> > +        char buf[16];
> > +        snprintf(buf, sizeof(buf), "%"PRId64"_%"PRId64,
> > +                 binding_rec->datapath->tunnel_key,
> > +                 binding_rec->tunnel_key);
> > +        sset_add(local_lport_ids, buf);
> > +}
> > +
> > +static void
> > +remove_local_lport_ids(const struct sbrec_port_binding *binding_rec,
> > +                       struct sset *local_lport_ids)
> > +{
> > +        char buf[16];
> > +        snprintf(buf, sizeof(buf), "%"PRId64"_%"PRId64,
> > +                 binding_rec->datapath->tunnel_key,
> > +                 binding_rec->tunnel_key);
> > +        sset_find_and_delete(local_lport_ids, buf);
> > +}
> > +
> >  /* Local bindings. binding.c module binds the logical port (represented
> by
> >   * Port_Binding rows) and sets the 'chassis' column when it is sees the
> >   * OVS interface row (of type "" or "internal") with the
> > @@ -599,6 +610,14 @@ local_bindings_destroy(struct shash *local_bindings)
> >      shash_destroy(local_bindings);
> >  }
> >
> > +static
> > +void local_binding_delete(struct shash *local_bindings,
> > +                          struct local_binding *lbinding)
> > +{
> > +    shash_find_and_delete(local_bindings, lbinding->name);
> > +    local_binding_destroy(lbinding);
> > +}
> > +
> >  static void
> >  local_binding_add_child(struct local_binding *lbinding,
> >                          struct local_binding *child)
> > @@ -625,6 +644,7 @@ is_lport_container(const struct sbrec_port_binding
> *pb)
> >      return !pb->type[0] && pb->parent_port && pb->parent_port[0];
> >  }
> >
> > +/* Corresponds to each Port_Binding.type. */
> >  enum en_lport_type {
> >      LP_UNKNOWN,
> >      LP_VIF,
> > @@ -670,12 +690,20 @@ get_lport_type(const struct sbrec_port_binding *pb)
> >      return LP_UNKNOWN;
> >  }
> >
> > -static void
> > +/* Returns false if lport is not claimed due to 'sb_readonly'.
> > + * Returns true otherwise.
> > + */
> > +static bool
> >  claim_lport(const struct sbrec_port_binding *pb,
> >              const struct sbrec_chassis *chassis_rec,
> > -            const struct ovsrec_interface *iface_rec)
> > +            const struct ovsrec_interface *iface_rec,
> > +            bool sb_readonly)
> >  {
> >      if (pb->chassis != chassis_rec) {
> > +        if (sb_readonly) {
> > +            return false;
> > +        }
> > +
> >          if (pb->chassis) {
> >              VLOG_INFO("Changing chassis for lport %s from %s to %s.",
> >                      pb->logical_port, pb->chassis->name,
> > @@ -694,26 +722,48 @@ claim_lport(const struct sbrec_port_binding *pb,
> >      struct sbrec_encap *encap_rec =
> >          sbrec_get_port_encap(chassis_rec, iface_rec);
> >      if (encap_rec && pb->encap != encap_rec) {
> > +        if (sb_readonly) {
> > +            return false;
> > +        }
> >          sbrec_port_binding_set_encap(pb, encap_rec);
> >      }
> > +
> > +    return true;
> >  }
> >
> > -static void
> > -release_lport(const struct sbrec_port_binding *pb)
> > +/* Returns false if lport is not released due to 'sb_readonly'.
> > + * Returns true otherwise.
> > + */
> > +static bool
> > +release_lport(const struct sbrec_port_binding *pb, bool sb_readonly)
> >  {
> >      if (!pb) {
> > -        return;
> > +        return true;
> >      }
> >
> > -    VLOG_INFO("Releasing lport %s from this chassis.",
> pb->logical_port);
> >      if (pb->encap) {
> > +        if (sb_readonly) {
> > +            return false;
> > +        }
> >          sbrec_port_binding_set_encap(pb, NULL);
> >      }
> > -    sbrec_port_binding_set_chassis(pb, NULL);
> > +
> > +    if (pb->chassis) {
> > +        if (sb_readonly) {
> > +            return false;
> > +        }
> > +        sbrec_port_binding_set_chassis(pb, NULL);
> > +    }
> >
> >      if (pb->virtual_parent) {
> > +        if (sb_readonly) {
> > +            return false;
> > +        }
> >          sbrec_port_binding_set_virtual_parent(pb, NULL);
> >      }
> > +
> > +    VLOG_INFO("Releasing lport %s from this chassis.",
> pb->logical_port);
> > +    return true;
> >  }
> >
> >  static bool
> > @@ -738,7 +788,41 @@ can_bind_on_this_chassis(const struct sbrec_chassis
> *chassis_rec,
> >             || !strcmp(requested_chassis, chassis_rec->hostname);
> >  }
> >
> > -static void
> > +static bool
> > +release_local_binding_children(const struct sbrec_chassis *chassis_rec,
> > +                               struct local_binding *lbinding,
> > +                               bool sb_readonly)
> > +{
> > +    struct shash_node *node;
> > +    SHASH_FOR_EACH (node, &lbinding->children) {
> > +        struct local_binding *l = node->data;
> > +        if (is_lbinding_this_chassis(l, chassis_rec)) {
> > +            if (!release_lport(l->pb, sb_readonly)) {
> > +                return false;
> > +            }
> > +        }
> > +    }
> > +
> > +    return true;
> > +}
> > +
> > +static bool
> > +release_local_binding(const struct sbrec_chassis *chassis_rec,
> > +                      struct local_binding *lbinding, bool sb_readonly)
> > +{
> > +    if (!release_local_binding_children(chassis_rec, lbinding,
> > +                                        sb_readonly)) {
> > +        return false;
> > +    }
> > +
> > +    if (is_lbinding_this_chassis(lbinding, chassis_rec)) {
> > +        return release_lport(lbinding->pb, sb_readonly);
> > +    }
> > +
> > +    return true;
> > +}
> > +
> > +static bool
> >  consider_vif_lport_(const struct sbrec_port_binding *pb,
> >                      bool can_bind, const char *vif_chassis,
> >                      struct binding_ctx_in *b_ctx_in,
> > @@ -750,7 +834,10 @@ consider_vif_lport_(const struct sbrec_port_binding
> *pb,
> >      if (lbinding_set) {
> >          if (can_bind) {
> >              /* We can claim the lport. */
> > -            claim_lport(pb, b_ctx_in->chassis_rec, lbinding->iface);
> > +            if (!claim_lport(pb, b_ctx_in->chassis_rec, lbinding->iface,
> > +                             !b_ctx_in->ovnsb_idl_txn)){
> > +                return false;
> > +            }
> >
> >              add_local_datapath(b_ctx_in->sbrec_datapath_binding_by_key,
> >                              b_ctx_in->sbrec_port_binding_by_datapath,
> > @@ -775,13 +862,14 @@ consider_vif_lport_(const struct sbrec_port_binding
> *pb,
> >      if (pb->chassis == b_ctx_in->chassis_rec) {
> >          /* Release the lport if there is no lbinding. */
> >          if (!lbinding_set || !can_bind) {
> > -            release_lport(pb);
> > +            return release_lport(pb, !b_ctx_in->ovnsb_idl_txn);
> >          }
> >      }
> >
> > +    return true;
> >  }
> >
> > -static void
> > +static bool
> >  consider_vif_lport(const struct sbrec_port_binding *pb,
> >                     struct binding_ctx_in *b_ctx_in,
> >                     struct binding_ctx_out *b_ctx_out,
> > @@ -801,11 +889,11 @@ consider_vif_lport(const struct sbrec_port_binding
> *pb,
> >          lbinding->pb = pb;
> >      }
> >
> > -    consider_vif_lport_(pb, can_bind, vif_chassis, b_ctx_in,
> > -                        b_ctx_out, lbinding, qos_map);
> > +    return consider_vif_lport_(pb, can_bind, vif_chassis, b_ctx_in,
> > +                               b_ctx_out, lbinding, qos_map);
> >  }
> >
> > -static void
> > +static bool
> >  consider_container_lport(const struct sbrec_port_binding *pb,
> >                           struct binding_ctx_in *b_ctx_in,
> >                           struct binding_ctx_out *b_ctx_out,
> > @@ -815,7 +903,39 @@ consider_container_lport(const struct
> sbrec_port_binding *pb,
> >      parent_lbinding = local_binding_find(b_ctx_out->local_bindings,
> >                                           pb->parent_port);
> >
> > -    if (parent_lbinding && !parent_lbinding->pb) {
> > +    if (!parent_lbinding) {
> > +        /* There is no local_binding for parent port. Create it
> > +         * without OVS interface row. This is the only exception
> > +         * for creating the 'struct local_binding' object without
> > +         * corresponding OVS interface row.
> > +         *
> > +         * This is required for the following reasons:
> > +         *   - If a logical port P1 is created and then
> > +         *     few container ports - C1, C2, .. are created first by
> CMS.
> > +         *   - And later when OVS interface row  is created for P1, then
> > +         *     we want the these container ports also be claimed by the
> > +         *     chassis.
> > +         * */
>
> It seems the assumption is, for all container PBs, their parent PBs should
> always have local_binding created, no matter if they are bound to current
> chassis.
>

That's right.


> However, at least some of the code in this patch would violate this
> assumption, e.g. handle_iface_delete() will call local_binding_delete() and
> delete the parent binding and all the children container bindings.
>

I missed this part. I'll handle it in next version.


> It is also better to document this clearly in the comment of struct
> local_binding for what should be expected in the local_bindings.


I've added comments for the local_bindings struct. But it looks like it's
not
sufficient. I'll add more .

My
> understanding is, for each binding_run() or change_handler execution, the
> expected items left in it is:
> 1) For each interface that has iface-id configured.
>
Yes.


> 2) For each container PBs and their parent PBs, no matter if they are bound
> to this chassis.
>

Yes.


> 3) For some of the non VIF PBs ...
>

Only for the "virtual" PB if it's parent is bound.


> Is this correct?
>
> > +        parent_lbinding = local_binding_create(pb->parent_port, NULL,
> NULL,
> > +                                               BT_VIF);
> > +        local_binding_add(b_ctx_out->local_bindings, parent_lbinding);
> > +    }
> > +
> > +    struct local_binding *container_lbinding =
> > +        local_binding_find_child(parent_lbinding, pb->logical_port);
> > +
> > +    if (!container_lbinding) {
> > +        container_lbinding = local_binding_create(pb->logical_port,
> > +
> parent_lbinding->iface,
> > +                                                  pb, BT_CONTAINER);
> > +        local_binding_add_child(parent_lbinding, container_lbinding);
> > +    } else {
> > +        ovs_assert(container_lbinding->type == BT_CONTAINER);
> > +        container_lbinding->pb = pb;
> > +        container_lbinding->iface = parent_lbinding->iface;
> > +    }
> > +
> > +    if (!parent_lbinding->pb) {
> >          parent_lbinding->pb = lport_lookup_by_name(
> >              b_ctx_in->sbrec_port_binding_by_name, pb->parent_port);
> >
> > @@ -824,37 +944,28 @@ consider_container_lport(const struct
> sbrec_port_binding *pb,
> >               * So call consider_vif_lport() to process it first. */
> >              consider_vif_lport(parent_lbinding->pb, b_ctx_in, b_ctx_out,
> >                                 parent_lbinding, qos_map);
> > -        }
> > -    }
> > +        } else {
> > +            /* The parent lport doesn't exist. Call release_lport() to
> > +             * release the container lport, if it was bound earlier. */
> > +            if (is_lbinding_this_chassis(container_lbinding,
> > +                                         b_ctx_in->chassis_rec)) {
> > +               return release_lport(pb, !b_ctx_in->ovnsb_idl_txn);
> > +            }
> >
> > -    if (!parent_lbinding || !parent_lbinding->pb) {
> > -        /* Call release_lport, to release the container lport, if
> > -         * it was bound earlier. */
> > -        if (pb->chassis == b_ctx_in->chassis_rec) {
> > -            release_lport(pb);
> > +            return true;
> >          }
> > -        return;
> >      }
> >
> > -    struct local_binding *container_lbinding =
> > -        local_binding_find_child(parent_lbinding, pb->logical_port);
> > -    ovs_assert(!container_lbinding);
> > -
> > -    container_lbinding = local_binding_create(pb->logical_port,
> > -                                              parent_lbinding->iface,
> > -                                              pb, BT_CONTAINER);
> > -    local_binding_add_child(parent_lbinding, container_lbinding);
> > -
> >      const char *vif_chassis = smap_get(&parent_lbinding->pb->options,
> >                                         "requested-chassis");
> >      bool can_bind = can_bind_on_this_chassis(b_ctx_in->chassis_rec,
> >                                               vif_chassis);
> >
> > -    consider_vif_lport_(pb, can_bind, vif_chassis, b_ctx_in, b_ctx_out,
> > -                        container_lbinding, qos_map);
> > +    return consider_vif_lport_(pb, can_bind, vif_chassis, b_ctx_in,
> b_ctx_out,
> > +                               container_lbinding, qos_map);
> >  }
> >
> > -static void
> > +static bool
> >  consider_virtual_lport(const struct sbrec_port_binding *pb,
> >                         struct binding_ctx_in *b_ctx_in,
> >                         struct binding_ctx_out *b_ctx_out,
> > @@ -881,11 +992,16 @@ consider_virtual_lport(const struct
> sbrec_port_binding *pb,
> >      if (is_lbinding_this_chassis(parent_lbinding,
> b_ctx_in->chassis_rec)) {
> >          virtual_lbinding =
> >              local_binding_find_child(parent_lbinding, pb->logical_port);
> > -        ovs_assert(!virtual_lbinding);
> > -        virtual_lbinding = local_binding_create(pb->logical_port,
> > -                                                parent_lbinding->iface,
> > -                                                pb, BT_VIRTUAL);
> > -        local_binding_add_child(parent_lbinding, virtual_lbinding);
> > +        if (!virtual_lbinding) {
> > +            virtual_lbinding = local_binding_create(pb->logical_port,
> > +
>  parent_lbinding->iface,
> > +                                                    pb, BT_VIRTUAL);
> > +            local_binding_add_child(parent_lbinding, virtual_lbinding);
> > +        } else {
> > +            ovs_assert(virtual_lbinding->type == BT_VIRTUAL);
> > +            virtual_lbinding->pb = pb;
> > +            virtual_lbinding->iface = parent_lbinding->iface;
> > +        }
> >      }
> >
> >      return consider_vif_lport_(pb, true, NULL, b_ctx_in, b_ctx_out,
> > @@ -895,14 +1011,13 @@ consider_virtual_lport(const struct
> sbrec_port_binding *pb,
> >  /* Considers either claiming the lport or releasing the lport
> >   * for non VIF lports.
> >   */
> > -static void
> > +static bool
> >  consider_nonvif_lport_(const struct sbrec_port_binding *pb,
> >                         bool our_chassis,
> >                         bool has_local_l3gateway,
> >                         struct binding_ctx_in *b_ctx_in,
> >                         struct binding_ctx_out *b_ctx_out)
> >  {
> > -    ovs_assert(b_ctx_in->ovnsb_idl_txn);
> >      if (our_chassis) {
> >          sset_add(b_ctx_out->local_lports, pb->logical_port);
> >          add_local_datapath(b_ctx_in->sbrec_datapath_binding_by_key,
> > @@ -912,13 +1027,16 @@ consider_nonvif_lport_(const struct
> sbrec_port_binding *pb,
> >                             b_ctx_out->local_datapaths);
> >
> >          update_local_lport_ids(b_ctx_out->local_lport_ids, pb);
> > -        claim_lport(pb, b_ctx_in->chassis_rec, NULL);
> > +        return claim_lport(pb, b_ctx_in->chassis_rec, NULL,
> > +                           !b_ctx_in->ovnsb_idl_txn);
> >      } else if (pb->chassis == b_ctx_in->chassis_rec) {
> > -        release_lport(pb);
> > +        return release_lport(pb, !b_ctx_in->ovnsb_idl_txn);
> >      }
> > +
> > +    return true;
> >  }
> >
> > -static void
> > +static bool
> >  consider_l2gw_lport(const struct sbrec_port_binding *pb,
> >                      struct binding_ctx_in *b_ctx_in,
> >                      struct binding_ctx_out *b_ctx_out)
> > @@ -927,10 +1045,10 @@ consider_l2gw_lport(const struct
> sbrec_port_binding *pb,
> >      bool our_chassis = chassis_id && !strcmp(chassis_id,
> >
> b_ctx_in->chassis_rec->name);
> >
> > -    consider_nonvif_lport_(pb, our_chassis, false, b_ctx_in, b_ctx_out);
> > +    return consider_nonvif_lport_(pb, our_chassis, false, b_ctx_in,
> b_ctx_out);
> >  }
> >
> > -static void
> > +static bool
> >  consider_l3gw_lport(const struct sbrec_port_binding *pb,
> >                      struct binding_ctx_in *b_ctx_in,
> >                      struct binding_ctx_out *b_ctx_out)
> > @@ -939,7 +1057,7 @@ consider_l3gw_lport(const struct sbrec_port_binding
> *pb,
> >      bool our_chassis = chassis_id && !strcmp(chassis_id,
> >
> b_ctx_in->chassis_rec->name);
> >
> > -    consider_nonvif_lport_(pb, our_chassis, true, b_ctx_in, b_ctx_out);
> > +    return consider_nonvif_lport_(pb, our_chassis, true, b_ctx_in,
> b_ctx_out);
> >  }
> >
> >  static void
> > @@ -958,7 +1076,7 @@ consider_localnet_lport(const struct
> sbrec_port_binding *pb,
> >      update_local_lport_ids(b_ctx_out->local_lport_ids, pb);
> >  }
> >
> > -static void
> > +static bool
> >  consider_ha_lport(const struct sbrec_port_binding *pb,
> >                    struct binding_ctx_in *b_ctx_in,
> >                    struct binding_ctx_out *b_ctx_out)
> > @@ -986,18 +1104,18 @@ consider_ha_lport(const struct sbrec_port_binding
> *pb,
> >                             b_ctx_out->local_datapaths);
> >      }
> >
> > -    consider_nonvif_lport_(pb, our_chassis, false, b_ctx_in, b_ctx_out);
> > +    return consider_nonvif_lport_(pb, our_chassis, false, b_ctx_in,
> b_ctx_out);
> >  }
> >
> > -static void
> > +static bool
> >  consider_cr_lport(const struct sbrec_port_binding *pb,
> >                    struct binding_ctx_in *b_ctx_in,
> >                    struct binding_ctx_out *b_ctx_out)
> >  {
> > -    consider_ha_lport(pb, b_ctx_in, b_ctx_out);
> > +    return consider_ha_lport(pb, b_ctx_in, b_ctx_out);
> >  }
> >
> > -static void
> > +static bool
> >  consider_external_lport(const struct sbrec_port_binding *pb,
> >                          struct binding_ctx_in *b_ctx_in,
> >                          struct binding_ctx_out *b_ctx_out)
> > @@ -1050,6 +1168,8 @@ build_local_bindings(struct binding_ctx_in
> *b_ctx_in,
> >                  }
> >
> >                  sset_add(b_ctx_out->local_lports, iface_id);
> > +                smap_replace(b_ctx_out->local_iface_ids,
> iface_rec->name,
> > +                             iface_id);
> >              }
> >
> >              /* Check if this is a tunnel interface. */
> > @@ -1165,9 +1285,9 @@ binding_run(struct binding_ctx_in *b_ctx_in, struct
> binding_ctx_out *b_ctx_out)
> >       * corresponding local datapath accordingly. */
> >      struct localnet_lport *lnet_lport;
> >      LIST_FOR_EACH_POP (lnet_lport, list_node, &localnet_lports) {
> > -        consider_localnet_port(lnet_lport->pb, &bridge_mappings,
> > -                               b_ctx_out->egress_ifaces,
> > -                               b_ctx_out->local_datapaths);
> > +        update_ld_localnet_port(lnet_lport->pb, &bridge_mappings,
> > +                                b_ctx_out->egress_ifaces,
> > +                                b_ctx_out->local_datapaths);
> >          free(lnet_lport);
> >      }
> >
> > @@ -1185,95 +1305,625 @@ binding_run(struct binding_ctx_in *b_ctx_in,
> struct binding_ctx_out *b_ctx_out)
> >      destroy_qos_map(&qos_map);
> >  }
> >
> > -/* Returns true if port-binding changes potentially require flow changes
> on
> > - * the current chassis. Returns false if we are sure there is no impact.
> */
> > +/* Returns true if the database is all cleaned up, false if more work is
> > + * required. */
> >  bool
> > -binding_evaluate_port_binding_changes(struct binding_ctx_in *b_ctx_in,
> > -                                      struct binding_ctx_out *b_ctx_out)
> > +binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn,
> > +                const struct sbrec_port_binding_table
> *port_binding_table,
> > +                const struct sbrec_chassis *chassis_rec)
> >  {
> > -    if (!b_ctx_in->chassis_rec) {
> > +    if (!ovnsb_idl_txn) {
> > +        return false;
> > +    }
> > +    if (!chassis_rec) {
> >          return true;
> >      }
> >
> > -    bool changed = false;
> > -
> >      const struct sbrec_port_binding *binding_rec;
> > -    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (binding_rec,
> > -
> b_ctx_in->port_binding_table) {
> > -        /* XXX: currently OVSDB change tracking doesn't support getting
> old
> > -         * data when the operation is update, so if a port-binding moved
> from
> > -         * this chassis to another, there is no easy way to find out the
> > -         * change. To workaround this problem, we just makes sure if
> > -         * any port *related to* this chassis has any change, then
> trigger
> > -         * recompute.
> > -         *
> > -         * - If a regular VIF is unbound from this chassis, the local
> ovsdb
> > -         *   interface table will be updated, which will trigger
> recompute.
> > -         *
> > -         * - If the port is not a regular VIF, always trigger recompute.
> */
> > -        if (binding_rec->chassis == b_ctx_in->chassis_rec) {
> > -            changed = true;
> > +    bool any_changes = false;
> > +    SBREC_PORT_BINDING_TABLE_FOR_EACH (binding_rec, port_binding_table)
> {
> > +        if (binding_rec->chassis == chassis_rec) {
> > +            if (binding_rec->encap) {
> > +                sbrec_port_binding_set_encap(binding_rec, NULL);
> > +            }
> > +            sbrec_port_binding_set_chassis(binding_rec, NULL);
> > +            any_changes = true;
> > +        }
> > +    }
> > +
> > +    if (any_changes) {
> > +        ovsdb_idl_txn_add_comment(
> > +            ovnsb_idl_txn,
> > +            "ovn-controller: removing all port bindings for '%s'",
> > +            chassis_rec->name);
> > +    }
> > +
> > +    return !any_changes;
> > +}
> > +
> > +static void
> > +add_local_datapath_peer_port(const struct sbrec_port_binding *pb,
> > +                             struct binding_ctx_in *b_ctx_in,
> > +                             struct binding_ctx_out *b_ctx_out,
> > +                             struct local_datapath *ld)
> > +{
> > +    const char *peer_name = smap_get(&pb->options, "peer");
> > +    if (strcmp(pb->type, "patch") || !peer_name) {
> > +        return;
> > +    }
> > +
> > +    const struct sbrec_port_binding *peer;
> > +    peer = lport_lookup_by_name(b_ctx_in->sbrec_port_binding_by_name,
> > +                                peer_name);
> > +
> > +    if (!peer || !peer->datapath) {
> > +        return;
> > +    }
> > +
> > +    bool present = false;
> > +    for (size_t i = 0; i < ld->n_peer_ports; i++) {
> > +        if (ld->peer_ports[i].local == pb) {
> > +            present = true;
> >              break;
> >          }
> > +    }
> >
> > -        if (!strcmp(binding_rec->type, "remote")) {
> > -            continue;
> > +    if (!present) {
> > +        ld->n_peer_ports++;
> > +        if (ld->n_peer_ports > ld->n_allocated_peer_ports) {
> > +            ld->peer_ports =
> > +                x2nrealloc(ld->peer_ports,
> > +                           &ld->n_allocated_peer_ports,
> > +                           sizeof *ld->peer_ports);
> > +        }
> > +        ld->peer_ports[ld->n_peer_ports - 1].local = pb;
> > +        ld->peer_ports[ld->n_peer_ports - 1].remote = peer;
> > +    }
> > +
> > +    struct local_datapath *peer_ld =
> > +        get_local_datapath(b_ctx_out->local_datapaths,
> > +                           peer->datapath->tunnel_key);
> > +    if (!peer_ld) {
> > +        add_local_datapath__(b_ctx_in->sbrec_datapath_binding_by_key,
> > +                             b_ctx_in->sbrec_port_binding_by_datapath,
> > +                             b_ctx_in->sbrec_port_binding_by_name,
> > +                             peer->datapath, false,
> > +                             1, b_ctx_out->local_datapaths);
> > +        return;
> > +    }
> > +
> > +    for (size_t i = 0; i < peer_ld->n_peer_ports; i++) {
> > +        if (peer_ld->peer_ports[i].local == peer) {
> > +            return;
> >          }
> > +    }
> >
> > -        if (strcmp(binding_rec->type, "")) {
> > -            changed = true;
> > +    peer_ld->n_peer_ports++;
> > +    if (peer_ld->n_peer_ports > peer_ld->n_allocated_peer_ports) {
> > +        peer_ld->peer_ports =
> > +            x2nrealloc(peer_ld->peer_ports,
> > +                        &peer_ld->n_allocated_peer_ports,
> > +                        sizeof *peer_ld->peer_ports);
> > +    }
> > +    peer_ld->peer_ports[peer_ld->n_peer_ports - 1].local = peer;
> > +    peer_ld->peer_ports[peer_ld->n_peer_ports - 1].remote = pb;
> > +}
> > +
> > +static void
> > +remove_local_datapath_peer_port(const struct sbrec_port_binding *pb,
> > +                                struct local_datapath *ld,
> > +                                struct hmap *local_datapaths)
> > +{
> > +    size_t i = 0;
> > +    for (i = 0; i < ld->n_peer_ports; i++) {
> > +        if (ld->peer_ports[i].local == pb) {
> >              break;
> >          }
> > +    }
> > +
> > +    if (i == ld->n_peer_ports) {
> > +        return;
> > +    }
> > +
> > +    const struct sbrec_port_binding *peer = ld->peer_ports[i].remote;
> >
> > -        struct local_binding *lbinding = NULL;
> > -        if (!binding_rec->parent_port || !binding_rec->parent_port[0]) {
> > -            lbinding = local_binding_find(b_ctx_out->local_bindings,
> > -                                          binding_rec->logical_port);
> > +    /* Possible improvement: We can shrint the allocated peer ports
> > +     * if (ld->n_peer_ports < ld->n_allocated_peer_ports / 2).
> > +     */
> > +    ld->peer_ports[i].local = ld->peer_ports[ld->n_peer_ports -
> 1].local;
> > +    ld->peer_ports[i].remote = ld->peer_ports[ld->n_peer_ports -
> 1].remote;
> > +    ld->n_peer_ports--;
> > +
> > +    struct local_datapath *peer_ld =
> > +        get_local_datapath(local_datapaths, peer->datapath->tunnel_key);
> > +    if (peer_ld) {
> > +        remove_local_datapath_peer_port(peer, peer_ld, local_datapaths);
>
> Sorry that I don't understand this part. peer's peer is the "pb" that is
> passed in the parameter of the current function call, so this recursive
> call would only try to remove the "pb", which is already removed (before
> calling this function). So I think it is useless.
>

Let's say there is sw0-lr0 of sw0 connected to the peer lr0-sw0 of lr0.

When this function is called for sw0-lr0, it removes the peer ports sw0-lr0
from
the 'sw0' ld and then it calls recursively for lr0.

Yes you're right. It will again call for sw0-lr0 of sw0. But that would be
a no-op as
the for loop in the beginning cannot find sw0-lr0  in 'sw0' ld.. Even
though it
is useless are there any issues with it ? Since its recursion and it has
proper return
conditions, I think it should be fine.

Another option is duplicating the code for removing the peer.



> > +    }
> > +}
> > +
> > +static void
> > +remove_pb_from_local_datapath(const struct sbrec_port_binding *pb,
> > +                              const struct sbrec_chassis *chassis_rec,
> > +                              struct binding_ctx_out *b_ctx_out,
> > +                              struct local_datapath *ld)
> > +{
> > +    remove_local_lport_ids(pb, b_ctx_out->local_lport_ids);
> > +    if (!strcmp(pb->type, "patch") ||
> > +        !strcmp(pb->type, "l3gateway")) {
> > +        remove_local_datapath_peer_port(pb, ld,
> b_ctx_out->local_datapaths);
> > +    } else if (!strcmp(pb->type, "localnet")) {
> > +        if (ld->localnet_port &&
> !strcmp(ld->localnet_port->logical_port,
> > +                                         pb->logical_port)) {
> > +            ld->localnet_port = NULL;
> > +        }
> > +    } else if (!strcmp(pb->type, "l3gateway")) {
> > +        const char *chassis_id = smap_get(&pb->options,
> > +                                          "l3gateway-chassis");
> > +        if (chassis_id && !strcmp(chassis_id, chassis_rec->name)) {
> > +            ld->has_local_l3gateway = false;
> > +        }
> > +    }
>
> I think we need to remove ld from the local_datapaths if the current pb is
> the only reason for that ld to be exist on this chassis. Also, all the
> related lds connected by patch ports should be removed as well, if there
> are no other pbs causing them to be added to this chassis. This is the
> reverse of add_local_datapath__(). It doesn't seem to be easy without going
> through all local_lbindings. Maybe this can be done in
> binding_handle_ovs_interface_changes() and
> binding_handle_port_binding_changes() out of the loop.
>
>
I left this unaddressed intentionally as I think it will complicate the
code.

Patch 3 of this series handles I-P for datapath and when a datapath is
deleted,
it removes the datapath from local_datapaths if it is present.
I think this should be Ok as we will eventually free it. If we really want
to address this,
I'm fine with it but I'd prefer a follow up patch to do this.



> > +}
> > +
> > +static bool
> > +handle_iface_add(const struct ovsrec_interface *iface_rec,
> > +                 const char *iface_id,
> > +                 struct binding_ctx_in *b_ctx_in,
> > +                 struct binding_ctx_out *b_ctx_out,
> > +                 struct hmap *qos_map)
> > +{
> > +    sset_add(b_ctx_out->local_lports, iface_id);
> > +    smap_replace(b_ctx_out->local_iface_ids, iface_rec->name, iface_id);
> > +
> > +    struct local_binding *lbinding =
> > +        local_binding_find(b_ctx_out->local_bindings, iface_id);
> > +
> > +    if (!lbinding) {
> > +        lbinding = local_binding_create(iface_id, iface_rec, NULL,
> BT_VIF);
> > +        local_binding_add(b_ctx_out->local_bindings, lbinding);
> > +    } else {
> > +        lbinding->iface = iface_rec;
> > +    }
> > +
> > +    if (!lbinding->pb || strcmp(lbinding->name,
> lbinding->pb->logical_port)) {
> > +        lbinding->pb = lport_lookup_by_name(
> > +            b_ctx_in->sbrec_port_binding_by_name, lbinding->name);
> > +        if (lbinding->pb && !strcmp(lbinding->pb->type, "virtual")) {
>
> Since we never bind "virtual" ports, this should be a misconfiguration, and
> it is better to log a warning message to be more clear.
>
> > +            lbinding->pb = NULL;
> > +        }
> > +    }
> > +
> > +    if (lbinding->pb) {
> > +        if (!consider_vif_lport(lbinding->pb, b_ctx_in, b_ctx_out,
> > +                                lbinding, qos_map)) {
> > +            return false;
> > +        }
> > +    }
> > +
> > +    /* Update the child local_binding's iface (if any children) and try
> to
> > +     *  claim the container lbindings. */
> > +    struct shash_node *node;
> > +    SHASH_FOR_EACH (node, &lbinding->children) {
> > +        struct local_binding *child = node->data;
> > +        child->iface = iface_rec;
> > +        if (child->type == BT_CONTAINER) {
> > +            if (!consider_container_lport(child->pb, b_ctx_in,
> b_ctx_out,
> > +                                          qos_map)) {
> > +                return false;
> > +            }
> > +        }
> > +    }
> > +
> > +    return true;
> > +}
> > +
> > +static bool
> > +handle_iface_delete(const char *lport_name, const char *iface_name,
> > +                    struct binding_ctx_in *b_ctx_in,
> > +                    struct binding_ctx_out *b_ctx_out, bool *changed)
> > +{
> > +    struct local_binding *lbinding;
> > +    lbinding = local_binding_find(b_ctx_out->local_bindings,
> > +                                  lport_name);
> > +    if (lbinding && lbinding->pb &&
> > +        lbinding->pb->chassis == b_ctx_in->chassis_rec) {
> > +
> > +        if (!release_local_binding(b_ctx_in->chassis_rec, lbinding,
> > +                                   !b_ctx_in->ovnsb_idl_txn)) {
> > +            return false;
> > +        }
> > +
> > +        struct local_datapath *ld =
> > +            get_local_datapath(b_ctx_out->local_datapaths,
> > +                                lbinding->pb->datapath->tunnel_key);
> > +        if (ld) {
> > +            remove_pb_from_local_datapath(lbinding->pb,
> > +                                            b_ctx_in->chassis_rec,
> > +                                            b_ctx_out, ld);
> > +        }
> > +        local_binding_delete(b_ctx_out->local_bindings, lbinding);
> > +        *changed = true;
> > +    }
> > +
> > +    sset_find_and_delete(b_ctx_out->local_lports, lport_name);
> > +    smap_remove(b_ctx_out->local_iface_ids, iface_name);
> > +
> > +    return true;
> > +}
> > +
> > +static bool
> > +is_iface_vif(const struct ovsrec_interface *iface_rec)
> > +{
> > +    if (iface_rec->type && iface_rec->type[0] &&
> > +        strcmp(iface_rec->type, "internal")) {
> > +        return false;
> > +    }
> > +
> > +    return true;
> > +}
> > +
> > +/* Returns true if the ovs interface changes were handled successfully,
> > + * false otherwise.
> > + */
> > +bool
> > +binding_handle_ovs_interface_changes(struct binding_ctx_in *b_ctx_in,
> > +                                     struct binding_ctx_out *b_ctx_out,
> > +                                     bool *changed)
> > +{
> > +    if (!b_ctx_in->chassis_rec) {
> > +        return false;
> > +    }
> > +
> > +    bool handled = true;
> > +    *changed = false;
> > +
> > +    /* Run the tracked interfaces loop twice. One to handle deleted
> > +     * changes. And another to handle add/update changes.
> > +     * This will ensure correctness.
> > +     */
> > +    const struct ovsrec_interface *iface_rec;
> > +    OVSREC_INTERFACE_TABLE_FOR_EACH_TRACKED (iface_rec,
> > +                                             b_ctx_in->iface_table) {
> > +        if (!is_iface_vif(iface_rec)) {
> > +            /* Right now are not handling ovs_interface changes of
> > +             * other types. This can be enhanced to handle of
> > +             * types - patch and tunnel. */
> > +            handled = false;
> > +            break;
> > +        }
> > +
> > +        const char *iface_id = smap_get(&iface_rec->external_ids,
> "iface-id");
> > +        const char *old_iface_id = smap_get(b_ctx_out->local_iface_ids,
> > +                                            iface_rec->name);
> > +        const char *cleared_iface_id = NULL;
> > +        if (!ovsrec_interface_is_deleted(iface_rec)) {
> > +            if (iface_id) {
> > +                /* Check if iface_id is changed. If so we need to
> > +                 * release the old port binding and associate this
> > +                 * inteface to new port binding. */
> > +                if (old_iface_id && strcmp(iface_id, old_iface_id)) {
> > +                    cleared_iface_id = old_iface_id;
> > +                }
> > +            } else if (old_iface_id) {
> > +                cleared_iface_id = old_iface_id;
> > +            }
> >          } else {
> > -            lbinding = local_binding_find(b_ctx_out->local_bindings,
> > -                                          binding_rec->parent_port);
> > +            cleared_iface_id = iface_id;
> > +            iface_id = NULL;
> >          }
> >
> > -        if (lbinding) {
> > -            changed = true;
> > +        if (cleared_iface_id) {
> > +            handled = handle_iface_delete(cleared_iface_id,
> iface_rec->name,
> > +                                          b_ctx_in, b_ctx_out, changed);
> > +        }
> > +
> > +        if (!handled) {
> >              break;
> >          }
> >      }
> >
> > -    return changed;
> > +    if (!handled) {
> > +        return false;
> > +    }
> > +
> > +    struct hmap qos_map = HMAP_INITIALIZER(&qos_map);
> > +    struct hmap *qos_map_ptr =
> > +        sset_is_empty(b_ctx_out->egress_ifaces) ? NULL : &qos_map;
> > +
> > +    OVSREC_INTERFACE_TABLE_FOR_EACH_TRACKED (iface_rec,
> > +                                             b_ctx_in->iface_table) {
> > +        /* Loop to handle create and update changes only. */
>
> Thanks for splitting the loops, but here are still some problems.
>
> 1. The first loop handled: a) interface deletion. b) interface update with
> old iface-id cleared. So in this second loop it should handle all the rest
> cases: a) interface add. b) interface update without iface-id change. It
> seems b) is treated as just interface add, which may be wrong: if iface-id
> didn't change but some other fields of the interface record is changed, it
> is not handled properly.
>

If other fields change, what should we handle ? Can ofport change ?
I'll see if ofport can be changed and what happens when it changes.

For other fields like name, mtu etc do we need to handle anything ?
I think we only care about the external_ids:iface-id and the ofport.
The function handle_iface_add() even if it is called for other changes,
it will be a no-op.

Also from the tracking we can't figure out what fields change right ?




>
> 2. Some other condition checks should be done for this loop, such as: if
> (!is_iface_vif(iface_rec)) ...
>

This is not required because the first loop checks for it and if there is
any
non vif interface change, then this function returns false, before the
second loop.


>
> > +        if (ovsrec_interface_is_deleted(iface_rec)) {
> > +            continue;
> > +        }
> > +
> > +        const char *iface_id = smap_get(&iface_rec->external_ids,
> "iface-id");
> > +        int64_t ofport = iface_rec->n_ofport ? *iface_rec->ofport : 0;
> > +        if (iface_id && ofport > 0) {
>
> What if iface-id didn't change, but ofport changed from non-zero to 0? How
> should we handle the change?
>
>
Good point. I think we should consider this as  handle_iface_delete.

I'll rename the function handle_iface_delete() to handle_iface_release()
and handle_iface_add() to handle_iface_claim() to be more clear.

A general approach can be: whenever there is an update
> (!ovsrec_interface_is_new()), we firstly handle it as interface delete, and
> then handle an interface add.
>

But why delete and add if can determine that it is going to be either one
i.e release or claim and I think we can definitely determine.
Also in patch 6/7 where we do tracking, it becomes a bit complicated
if we handle it as delete and then add as we may add 2 such tracking
data.

Thanks
Numan


> > +            *changed = true;
> > +            handled = handle_iface_add(iface_rec, iface_id, b_ctx_in,
> > +                                       b_ctx_out, qos_map_ptr);
> > +            if (!handled) {
> > +                break;
> > +            }
> > +        }
> > +    }
> > +
> > +    if (handled && qos_map_ptr && set_noop_qos(b_ctx_in->ovs_idl_txn,
> > +                                               b_ctx_in->port_table,
> > +                                               b_ctx_in->qos_table,
> > +
> b_ctx_out->egress_ifaces)) {
> > +        const char *entry;
> > +        SSET_FOR_EACH (entry, b_ctx_out->egress_ifaces) {
> > +            setup_qos(entry, &qos_map);
> > +        }
> > +    }
> > +
> > +    destroy_qos_map(&qos_map);
> > +    return handled;
> >  }
> >
> > -/* Returns true if the database is all cleaned up, false if more work is
> > - * required. */
> > -bool
> > -binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn,
> > -                const struct sbrec_port_binding_table
> *port_binding_table,
> > -                const struct sbrec_chassis *chassis_rec)
> > +static void
> > +handle_deleted_lport(const struct sbrec_port_binding *pb,
> > +                     struct binding_ctx_in *b_ctx_in,
> > +                     struct binding_ctx_out *b_ctx_out)
> >  {
> > -    if (!ovnsb_idl_txn) {
> > +    struct local_datapath *ld =
> > +        get_local_datapath(b_ctx_out->local_datapaths,
> > +                           pb->datapath->tunnel_key);
> > +    if (ld) {
> > +        remove_pb_from_local_datapath(pb, b_ctx_in->chassis_rec,
> > +                                      b_ctx_out, ld);
> > +    }
> > +}
> > +
> > +static struct local_binding *
> > +get_lbinding_for_lport(const struct sbrec_port_binding *pb,
> > +                       enum en_lport_type lport_type,
> > +                       struct binding_ctx_out *b_ctx_out)
> > +{
> > +    ovs_assert(lport_type == LP_VIF || lport_type == LP_VIRTUAL);
> > +
> > +    if (lport_type == LP_VIF && !is_lport_container(pb)) {
> > +        return local_binding_find(b_ctx_out->local_bindings,
> pb->logical_port);
> > +    }
> > +
> > +    struct local_binding *parent_lbinding = NULL;
> > +
> > +    if (lport_type == LP_VIRTUAL) {
> > +        parent_lbinding = local_binding_find(b_ctx_out->local_bindings,
> > +                                             pb->virtual_parent);
> > +    } else {
> > +        parent_lbinding = local_binding_find(b_ctx_out->local_bindings,
> > +                                             pb->parent_port);
> > +    }
> > +
> > +    return parent_lbinding
> > +           ? local_binding_find(&parent_lbinding->children,
> pb->logical_port)
> > +           : NULL;
> > +}
> > +
> > +static bool
> > +handle_deleted_vif_lport(const struct sbrec_port_binding *pb,
> > +                         enum en_lport_type lport_type,
> > +                         struct binding_ctx_in *b_ctx_in,
> > +                         struct binding_ctx_out *b_ctx_out,
> > +                         bool *changed)
> > +{
> > +    struct local_binding *lbinding =
> > +        get_lbinding_for_lport(pb, lport_type, b_ctx_out);
> > +
> > +    if (lbinding) {
> > +        lbinding->pb = NULL;
> > +        /* The port_binding 'pb' is deleted. So there is no need to
> > +         * clear the 'chassis' column of 'pb'. But we need to do
> > +         * for the local_binding's children. */
> > +        if (lbinding->type == BT_VIF &&
> > +                !release_local_binding_children(b_ctx_in->chassis_rec,
> > +                                                lbinding,
> > +
>  !b_ctx_in->ovnsb_idl_txn)) {
> > +            return false;
> > +        }
> > +
> > +        *changed = true;
> > +    }
> > +
> > +    handle_deleted_lport(pb, b_ctx_in, b_ctx_out);
> > +    return true;
> > +}
> > +
> > +static bool
> > +handle_updated_vif_lport(const struct sbrec_port_binding *pb,
> > +                         enum en_lport_type lport_type,
> > +                         struct binding_ctx_in *b_ctx_in,
> > +                         struct binding_ctx_out *b_ctx_out,
> > +                         struct hmap *qos_map,
> > +                         bool *changed)
> > +{
> > +    bool claimed = (pb->chassis == b_ctx_in->chassis_rec);
> > +    bool handled = true;
> > +
> > +    if (lport_type == LP_VIRTUAL) {
> > +        handled = consider_virtual_lport(pb, b_ctx_in, b_ctx_out,
> qos_map);
> > +    } else if (lport_type == LP_VIF && is_lport_container(pb)) {
> > +        handled = consider_container_lport(pb, b_ctx_in, b_ctx_out,
> qos_map);
> > +    } else {
> > +        handled = consider_vif_lport(pb, b_ctx_in, b_ctx_out, NULL,
> qos_map);
> > +    }
> > +
> > +    if (!handled) {
> >          return false;
> >      }
> > -    if (!chassis_rec) {
> > +
> > +    bool now_claimed = (pb->chassis == b_ctx_in->chassis_rec);
> > +    bool changed_ = (claimed != now_claimed);
> > +
> > +    if (changed_) {
> > +        *changed = true;
> > +    }
> > +
> > +    if (lport_type == LP_VIRTUAL ||
> > +        (lport_type == LP_VIF && is_lport_container(pb)) || !changed_) {
> >          return true;
> >      }
> >
> > -    const struct sbrec_port_binding *binding_rec;
> > -    bool any_changes = false;
> > -    SBREC_PORT_BINDING_TABLE_FOR_EACH (binding_rec, port_binding_table)
> {
> > -        if (binding_rec->chassis == chassis_rec) {
> > -            if (binding_rec->encap)
> > -                sbrec_port_binding_set_encap(binding_rec, NULL);
> > -            sbrec_port_binding_set_chassis(binding_rec, NULL);
> > -            any_changes = true;
> > +    struct local_binding *lbinding =
> > +        local_binding_find(b_ctx_out->local_bindings, pb->logical_port);
> > +
> > +    ovs_assert(lbinding);
> > +
> > +    struct shash_node *node;
> > +    SHASH_FOR_EACH (node, &lbinding->children) {
> > +        struct local_binding *child = node->data;
> > +        if (child->type == BT_CONTAINER) {
> > +            handled = consider_container_lport(child->pb, b_ctx_in,
> b_ctx_out,
> > +                                               qos_map);
> > +            if (!handled) {
> > +                return false;
> > +            }
> >          }
> >      }
> >
> > -    if (any_changes) {
> > -        ovsdb_idl_txn_add_comment(
> > -            ovnsb_idl_txn,
> > -            "ovn-controller: removing all port bindings for '%s'",
> > -            chassis_rec->name);
> > +    return true;
> > +}
> > +
> > +/* Returns true if the port binding changes resulted in local binding
> > + * updates, false otherwise.
> > + */
> > +bool
> > +binding_handle_port_binding_changes(struct binding_ctx_in *b_ctx_in,
> > +                                    struct binding_ctx_out *b_ctx_out,
> > +                                    bool *changed)
> > +{
> > +    bool handled = true;
> > +    *changed = false;
> > +
> > +    const struct sbrec_port_binding *pb;
> > +
> > +    /* Run the tracked port binding loop twice. One to handle deleted
> > +     * changes. And another to handle add/update changes.
> > +     * This will ensure correctness.
> > +     */
> > +    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
> > +
> b_ctx_in->port_binding_table) {
> > +        if (!sbrec_port_binding_is_deleted(pb)) {
> > +            continue;
> > +        }
> > +
> > +        enum en_lport_type lport_type = get_lport_type(pb);
> > +        if (lport_type == LP_VIF || lport_type == LP_VIRTUAL) {
> > +            handled = handle_deleted_vif_lport(pb, lport_type, b_ctx_in,
> > +                                                b_ctx_out, changed);
> > +        } else {
> > +            handle_deleted_lport(pb, b_ctx_in, b_ctx_out);
> > +        }
> > +
> > +        if (!handled) {
> > +            break;
> > +        }
> >      }
> >
> > -    return !any_changes;
> > +    if (!handled) {
> > +        return false;
> > +    }
> > +
> > +    struct hmap qos_map = HMAP_INITIALIZER(&qos_map);
> > +    struct hmap *qos_map_ptr =
> > +        sset_is_empty(b_ctx_out->egress_ifaces) ? NULL : &qos_map;
> > +
> > +    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
> > +
> b_ctx_in->port_binding_table) {
> > +        /* Loop to handle create and update changes only. */
> > +        if (sbrec_port_binding_is_deleted(pb)) {
> > +            continue;
> > +        }
> > +
> > +        enum en_lport_type lport_type = get_lport_type(pb);
> > +
> > +        struct local_datapath *ld =
> > +            get_local_datapath(b_ctx_out->local_datapaths,
> > +                               pb->datapath->tunnel_key);
> > +
> > +        switch (lport_type) {
> > +        case LP_VIF:
> > +        case LP_VIRTUAL:
> > +            handled = handle_updated_vif_lport(pb, lport_type, b_ctx_in,
> > +                                               b_ctx_out, qos_map_ptr,
> > +                                               changed);
> > +            break;
> > +
> > +        case LP_PATCH:
> > +        case LP_LOCALPORT:
> > +        case LP_VTEP:
> > +            *changed = true;
> > +            update_local_lport_ids(b_ctx_out->local_lport_ids, pb);
> > +            if (lport_type ==  LP_PATCH) {
> > +                /* Add the peer datapath to the local datapaths if it's
> > +                    * not present yet.
> > +                    */
> > +                if (ld) {
> > +                    add_local_datapath_peer_port(pb, b_ctx_in,
> > +                                                    b_ctx_out, ld);
> > +                }
> > +            }
> > +            break;
> > +
> > +        case LP_L2GATEWAY:
> > +            *changed = true;
> > +            handled = consider_l2gw_lport(pb, b_ctx_in, b_ctx_out);
> > +            break;
> > +
> > +        case LP_L3GATEWAY:
> > +            *changed = true;
> > +            handled = consider_l3gw_lport(pb, b_ctx_in, b_ctx_out);
> > +            break;
> > +
> > +        case LP_CHASSISREDIRECT:
> > +            *changed = true;
> > +            handled = consider_cr_lport(pb, b_ctx_in, b_ctx_out);
> > +            break;
> > +
> > +        case LP_EXTERNAL:
> > +            *changed = true;
> > +            handled = consider_external_lport(pb, b_ctx_in, b_ctx_out);
> > +            break;
> > +
> > +        case LP_LOCALNET: {
> > +            *changed = true;
> > +            consider_localnet_lport(pb, b_ctx_in, b_ctx_out,
> qos_map_ptr);
> > +
> > +            struct shash bridge_mappings =
> > +                SHASH_INITIALIZER(&bridge_mappings);
> > +            add_ovs_bridge_mappings(b_ctx_in->ovs_table,
> > +                                    b_ctx_in->bridge_table,
> > +                                    &bridge_mappings);
> > +            update_ld_localnet_port(pb, &bridge_mappings,
> > +                                    b_ctx_out->egress_ifaces,
> > +                                    b_ctx_out->local_datapaths);
> > +            shash_destroy(&bridge_mappings);
> > +            break;
> > +        }
> > +
> > +        case LP_REMOTE:
> > +        case LP_UNKNOWN:
> > +            break;
> > +        }
> > +
> > +        if (!handled) {
> > +            break;
> > +        }
> > +    }
> > +
> > +    if (handled && qos_map_ptr && set_noop_qos(b_ctx_in->ovs_idl_txn,
> > +                                               b_ctx_in->port_table,
> > +                                               b_ctx_in->qos_table,
> > +
> b_ctx_out->egress_ifaces)) {
> > +        const char *entry;
> > +        SSET_FOR_EACH (entry, b_ctx_out->egress_ifaces) {
> > +            setup_qos(entry, &qos_map);
> > +        }
> > +    }
> > +
> > +    destroy_qos_map(&qos_map);
> > +    return handled;
> >  }
> > diff --git a/controller/binding.h b/controller/binding.h
> > index 9affc9a96..f7ace6faf 100644
> > --- a/controller/binding.h
> > +++ b/controller/binding.h
> > @@ -57,6 +57,7 @@ struct binding_ctx_out {
> >      struct sset *local_lports;
> >      struct sset *local_lport_ids;
> >      struct sset *egress_ifaces;
> > +    struct smap *local_iface_ids;
> >  };
> >
> >  void binding_register_ovs_idl(struct ovsdb_idl *);
> > @@ -64,9 +65,13 @@ void binding_run(struct binding_ctx_in *, struct
> binding_ctx_out *);
> >  bool binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn,
> >                       const struct sbrec_port_binding_table *,
> >                       const struct sbrec_chassis *);
> > -bool binding_evaluate_port_binding_changes(struct binding_ctx_in *,
> > -                                           struct binding_ctx_out *);
> >
> >  void local_bindings_init(struct shash *local_bindings);
> >  void local_bindings_destroy(struct shash *local_bindings);
> > +bool binding_handle_ovs_interface_changes(struct binding_ctx_in *,
> > +                                          struct binding_ctx_out *,
> > +                                          bool *changed);
> > +bool binding_handle_port_binding_changes(struct binding_ctx_in *,
> > +                                         struct binding_ctx_out *,
> > +                                         bool *changed);
> >  #endif /* controller/binding.h */
> > diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
> > index 6b759966b..08b074415 100644
> > --- a/controller/ovn-controller.c
> > +++ b/controller/ovn-controller.c
> > @@ -753,6 +753,7 @@ enum sb_engine_node {
> >      OVS_NODE(open_vswitch, "open_vswitch") \
> >      OVS_NODE(bridge, "bridge") \
> >      OVS_NODE(port, "port") \
> > +    OVS_NODE(interface, "interface") \
> >      OVS_NODE(qos, "qos")
> >
> >  enum ovs_engine_node {
> > @@ -974,6 +975,7 @@ struct ed_type_runtime_data {
> >      struct sset active_tunnels;
> >
> >      struct sset egress_ifaces;
> > +    struct smap local_iface_ids;
> >  };
> >
> >  static void *
> > @@ -987,6 +989,7 @@ en_runtime_data_init(struct engine_node *node
> OVS_UNUSED,
> >      sset_init(&data->local_lport_ids);
> >      sset_init(&data->active_tunnels);
> >      sset_init(&data->egress_ifaces);
> > +    smap_init(&data->local_iface_ids);
> >      local_bindings_init(&data->local_bindings);
> >      return data;
> >  }
> > @@ -1000,6 +1003,7 @@ en_runtime_data_cleanup(void *data)
> >      sset_destroy(&rt_data->local_lport_ids);
> >      sset_destroy(&rt_data->active_tunnels);
> >      sset_destroy(&rt_data->egress_ifaces);
> > +    smap_destroy(&rt_data->local_iface_ids);
> >      struct local_datapath *cur_node, *next_node;
> >      HMAP_FOR_EACH_SAFE (cur_node, next_node, hmap_node,
> >                          &rt_data->local_datapaths) {
> > @@ -1041,6 +1045,10 @@ init_binding_ctx(struct engine_node *node,
> >          (struct ovsrec_port_table *)EN_OVSDB_GET(
> >              engine_get_input("OVS_port", node));
> >
> > +    struct ovsrec_interface_table *iface_table =
> > +        (struct ovsrec_interface_table *)EN_OVSDB_GET(
> > +            engine_get_input("OVS_interface", node));
> > +
> >      struct ovsrec_qos_table *qos_table =
> >          (struct ovsrec_qos_table *)EN_OVSDB_GET(
> >              engine_get_input("OVS_qos", node));
> > @@ -1070,6 +1078,7 @@ init_binding_ctx(struct engine_node *node,
> >      b_ctx_in->sbrec_port_binding_by_datapath =
> sbrec_port_binding_by_datapath;
> >      b_ctx_in->sbrec_port_binding_by_name = sbrec_port_binding_by_name;
> >      b_ctx_in->port_table = port_table;
> > +    b_ctx_in->iface_table = iface_table;
> >      b_ctx_in->qos_table = qos_table;
> >      b_ctx_in->port_binding_table = pb_table;
> >      b_ctx_in->br_int = br_int;
> > @@ -1083,6 +1092,7 @@ init_binding_ctx(struct engine_node *node,
> >      b_ctx_out->local_lport_ids = &rt_data->local_lport_ids;
> >      b_ctx_out->egress_ifaces = &rt_data->egress_ifaces;
> >      b_ctx_out->local_bindings = &rt_data->local_bindings;
> > +    b_ctx_out->local_iface_ids = &rt_data->local_iface_ids;
> >  }
> >
> >  static void
> > @@ -1111,10 +1121,12 @@ en_runtime_data_run(struct engine_node *node,
> void *data)
> >          sset_destroy(local_lport_ids);
> >          sset_destroy(active_tunnels);
> >          sset_destroy(&rt_data->egress_ifaces);
> > +        smap_destroy(&rt_data->local_iface_ids);
> >          sset_init(local_lports);
> >          sset_init(local_lport_ids);
> >          sset_init(active_tunnels);
> >          sset_init(&rt_data->egress_ifaces);
> > +        smap_init(&rt_data->local_iface_ids);
> >          local_bindings_init(&rt_data->local_bindings);
> >      }
> >
> > @@ -1140,6 +1152,34 @@ en_runtime_data_run(struct engine_node *node, void
> *data)
> >      engine_set_node_state(node, EN_UPDATED);
> >  }
> >
> > +static bool
> > +runtime_data_ovs_interface_handler(struct engine_node *node, void *data)
> > +{
> > +    struct ed_type_runtime_data *rt_data = data;
> > +    struct binding_ctx_in b_ctx_in;
> > +    struct binding_ctx_out b_ctx_out;
> > +    init_binding_ctx(node, rt_data, &b_ctx_in, &b_ctx_out);
> > +
> > +    bool changed = false;
> > +    if (!binding_handle_ovs_interface_changes(&b_ctx_in, &b_ctx_out,
> > +                                              &changed)) {
> > +        return false;
> > +    }
> > +
> > +    if (changed) {
> > +        engine_set_node_state(node, EN_UPDATED);
> > +    }
> > +
> > +    return true;
> > +}
> > +
> > +static bool
> > +runtime_data_noop_handler(struct engine_node *node OVS_UNUSED,
> > +                          void *data OVS_UNUSED)
> > +{
> > +    return true;
> > +}
> > +
> >  static bool
> >  runtime_data_sb_port_binding_handler(struct engine_node *node, void
> *data)
> >  {
> > @@ -1147,11 +1187,21 @@ runtime_data_sb_port_binding_handler(struct
> engine_node *node, void *data)
> >      struct binding_ctx_in b_ctx_in;
> >      struct binding_ctx_out b_ctx_out;
> >      init_binding_ctx(node, rt_data, &b_ctx_in, &b_ctx_out);
> > +    if (!b_ctx_in.chassis_rec) {
> > +        return false;
> > +    }
> > +
> > +    bool changed = false;
> > +    if (!binding_handle_port_binding_changes(&b_ctx_in, &b_ctx_out,
> > +                                             &changed)) {
> > +        return false;
> > +    }
> >
> > -    bool changed = binding_evaluate_port_binding_changes(&b_ctx_in,
> > -                                                         &b_ctx_out);
> > +    if (changed) {
> > +        engine_set_node_state(node, EN_UPDATED);
> > +    }
> >
> > -    return !changed;
> > +    return true;
> >  }
> >
> >  /* Connection tracking zones. */
> > @@ -1894,7 +1944,10 @@ main(int argc, char *argv[])
> >
> >      engine_add_input(&en_runtime_data, &en_ovs_open_vswitch, NULL);
> >      engine_add_input(&en_runtime_data, &en_ovs_bridge, NULL);
> > -    engine_add_input(&en_runtime_data, &en_ovs_port, NULL);
> > +    engine_add_input(&en_runtime_data, &en_ovs_port,
> > +                     runtime_data_noop_handler);
> > +    engine_add_input(&en_runtime_data, &en_ovs_interface,
> > +                     runtime_data_ovs_interface_handler);
> >      engine_add_input(&en_runtime_data, &en_ovs_qos, NULL);
> >
> >      engine_add_input(&en_runtime_data, &en_sb_chassis, NULL);
> > diff --git a/tests/ovn-performance.at b/tests/ovn-performance.at
> > index a8a15f8fe..5dfc6f7ca 100644
> > --- a/tests/ovn-performance.at
> > +++ b/tests/ovn-performance.at
> > @@ -239,6 +239,19 @@ for i in 1 2; do
> >      ovn_attach n1 br-phys 192.168.0.$i
> >  done
> >
> > +# Wait for the tunnel ports to be created and up.
> > +# Otherwise this may affect the lflow_run count.
> > +
> > +OVS_WAIT_UNTIL([
> > +    test $(as hv1 ovs-vsctl list interface ovn-hv2-0 | \
> > +grep tunnel_egress_iface_carrier=up | wc -l) -eq 1
> > +])
> > +
> > +OVS_WAIT_UNTIL([
> > +    test $(as hv2 ovs-vsctl list interface ovn-hv1-0 | \
> > +grep tunnel_egress_iface_carrier=up | wc -l) -eq 1
> > +])
> > +
> >  # Add router lr1
> >  OVN_CONTROLLER_EXPECT_HIT(
> >      [hv1 hv2], [lflow_run],
> > --
> > 2.26.2
> >
> > _______________________________________________
> > dev mailing list
> > dev@openvswitch.org
> > https://mail.openvswitch.org/mailman/listinfo/ovs-dev
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
>
Han Zhou May 21, 2020, 7:46 p.m. UTC | #3
On Thu, May 21, 2020 at 3:49 AM Numan Siddique <numans@ovn.org> wrote:
>
>
>
> On Thu, May 21, 2020 at 6:57 AM Han Zhou <hzhou@ovn.org> wrote:
>>
>> Hi Numan, please see my comments below.
>
>
> Thanks for the review. Please see below for a few comments.
>
>
>>
>>
>> On Wed, May 20, 2020 at 12:41 PM <numans@ovn.org> wrote:
>> >
>> > From: Numan Siddique <numans@ovn.org>
>> >
>> > This patch handles SB port binding changes and OVS interface changes
>> > incrementally in the runtime_data stage which otherwise would have
>> > resulted in calls to binding_run().
>> >
>> > Prior to this patch, port binding changes were handled differently.
>> > The changes were only evaluated to see if they need full recompute
>> > or they can be ignored.
>> >
>> > With this patch, the runtime data is updated with the changes (both
>> > SB port binding and OVS interface) and ports are claimed/released in
>> > the handlers itself, avoiding the need to trigger recompute of runtime
>> > data stage.
>> >
>> > Acked-by: Mark Michelson <mmichels@redhat.com>
>> > Signed-off-by: Numan Siddique <numans@ovn.org>
>> > ---
>> >  controller/binding.c        | 906 +++++++++++++++++++++++++++++++-----
>> >  controller/binding.h        |   9 +-
>> >  controller/ovn-controller.c |  61 ++-
>> >  tests/ovn-performance.at    |  13 +
>> >  4 files changed, 855 insertions(+), 134 deletions(-)
>> >
>> > diff --git a/controller/binding.c b/controller/binding.c
>> > index 2997fcae8..d5e8e4773 100644
>> > --- a/controller/binding.c
>> > +++ b/controller/binding.c
>> > @@ -360,17 +360,6 @@ destroy_qos_map(struct hmap *qos_map)
>> >      hmap_destroy(qos_map);
>> >  }
>> >
>> > -static void
>> > -update_local_lport_ids(struct sset *local_lport_ids,
>> > -                       const struct sbrec_port_binding *binding_rec)
>> > -{
>> > -        char buf[16];
>> > -        snprintf(buf, sizeof(buf), "%"PRId64"_%"PRId64,
>> > -                 binding_rec->datapath->tunnel_key,
>> > -                 binding_rec->tunnel_key);
>> > -        sset_add(local_lport_ids, buf);
>> > -}
>> > -
>> >  /*
>> >   * Get the encap from the chassis for this port. The interface
>> >   * may have an external_ids:encap-ip=<encap-ip> set; if so we
>> > @@ -449,10 +438,10 @@ is_network_plugged(const struct
sbrec_port_binding
>> *binding_rec,
>> >  }
>> >
>> >  static void
>> > -consider_localnet_port(const struct sbrec_port_binding *binding_rec,
>> > -                       struct shash *bridge_mappings,
>> > -                       struct sset *egress_ifaces,
>> > -                       struct hmap *local_datapaths)
>> > +update_ld_localnet_port(const struct sbrec_port_binding *binding_rec,
>> > +                        struct shash *bridge_mappings,
>> > +                        struct sset *egress_ifaces,
>> > +                        struct hmap *local_datapaths)
>> >  {
>> >      /* Ignore localnet ports for unplugged networks. */
>> >      if (!is_network_plugged(binding_rec, bridge_mappings)) {
>> > @@ -482,6 +471,28 @@ consider_localnet_port(const struct
>> sbrec_port_binding *binding_rec,
>> >      ld->localnet_port = binding_rec;
>> >  }
>> >
>> > +static void
>> > +update_local_lport_ids(struct sset *local_lport_ids,
>> > +                       const struct sbrec_port_binding *binding_rec)
>> > +{
>> > +        char buf[16];
>> > +        snprintf(buf, sizeof(buf), "%"PRId64"_%"PRId64,
>> > +                 binding_rec->datapath->tunnel_key,
>> > +                 binding_rec->tunnel_key);
>> > +        sset_add(local_lport_ids, buf);
>> > +}
>> > +
>> > +static void
>> > +remove_local_lport_ids(const struct sbrec_port_binding *binding_rec,
>> > +                       struct sset *local_lport_ids)
>> > +{
>> > +        char buf[16];
>> > +        snprintf(buf, sizeof(buf), "%"PRId64"_%"PRId64,
>> > +                 binding_rec->datapath->tunnel_key,
>> > +                 binding_rec->tunnel_key);
>> > +        sset_find_and_delete(local_lport_ids, buf);
>> > +}
>> > +
>> >  /* Local bindings. binding.c module binds the logical port
(represented
>> by
>> >   * Port_Binding rows) and sets the 'chassis' column when it is sees
the
>> >   * OVS interface row (of type "" or "internal") with the
>> > @@ -599,6 +610,14 @@ local_bindings_destroy(struct shash
*local_bindings)
>> >      shash_destroy(local_bindings);
>> >  }
>> >
>> > +static
>> > +void local_binding_delete(struct shash *local_bindings,
>> > +                          struct local_binding *lbinding)
>> > +{
>> > +    shash_find_and_delete(local_bindings, lbinding->name);
>> > +    local_binding_destroy(lbinding);
>> > +}
>> > +
>> >  static void
>> >  local_binding_add_child(struct local_binding *lbinding,
>> >                          struct local_binding *child)
>> > @@ -625,6 +644,7 @@ is_lport_container(const struct sbrec_port_binding
>> *pb)
>> >      return !pb->type[0] && pb->parent_port && pb->parent_port[0];
>> >  }
>> >
>> > +/* Corresponds to each Port_Binding.type. */
>> >  enum en_lport_type {
>> >      LP_UNKNOWN,
>> >      LP_VIF,
>> > @@ -670,12 +690,20 @@ get_lport_type(const struct sbrec_port_binding
*pb)
>> >      return LP_UNKNOWN;
>> >  }
>> >
>> > -static void
>> > +/* Returns false if lport is not claimed due to 'sb_readonly'.
>> > + * Returns true otherwise.
>> > + */
>> > +static bool
>> >  claim_lport(const struct sbrec_port_binding *pb,
>> >              const struct sbrec_chassis *chassis_rec,
>> > -            const struct ovsrec_interface *iface_rec)
>> > +            const struct ovsrec_interface *iface_rec,
>> > +            bool sb_readonly)
>> >  {
>> >      if (pb->chassis != chassis_rec) {
>> > +        if (sb_readonly) {
>> > +            return false;
>> > +        }
>> > +
>> >          if (pb->chassis) {
>> >              VLOG_INFO("Changing chassis for lport %s from %s to %s.",
>> >                      pb->logical_port, pb->chassis->name,
>> > @@ -694,26 +722,48 @@ claim_lport(const struct sbrec_port_binding *pb,
>> >      struct sbrec_encap *encap_rec =
>> >          sbrec_get_port_encap(chassis_rec, iface_rec);
>> >      if (encap_rec && pb->encap != encap_rec) {
>> > +        if (sb_readonly) {
>> > +            return false;
>> > +        }
>> >          sbrec_port_binding_set_encap(pb, encap_rec);
>> >      }
>> > +
>> > +    return true;
>> >  }
>> >
>> > -static void
>> > -release_lport(const struct sbrec_port_binding *pb)
>> > +/* Returns false if lport is not released due to 'sb_readonly'.
>> > + * Returns true otherwise.
>> > + */
>> > +static bool
>> > +release_lport(const struct sbrec_port_binding *pb, bool sb_readonly)
>> >  {
>> >      if (!pb) {
>> > -        return;
>> > +        return true;
>> >      }
>> >
>> > -    VLOG_INFO("Releasing lport %s from this chassis.",
pb->logical_port);
>> >      if (pb->encap) {
>> > +        if (sb_readonly) {
>> > +            return false;
>> > +        }
>> >          sbrec_port_binding_set_encap(pb, NULL);
>> >      }
>> > -    sbrec_port_binding_set_chassis(pb, NULL);
>> > +
>> > +    if (pb->chassis) {
>> > +        if (sb_readonly) {
>> > +            return false;
>> > +        }
>> > +        sbrec_port_binding_set_chassis(pb, NULL);
>> > +    }
>> >
>> >      if (pb->virtual_parent) {
>> > +        if (sb_readonly) {
>> > +            return false;
>> > +        }
>> >          sbrec_port_binding_set_virtual_parent(pb, NULL);
>> >      }
>> > +
>> > +    VLOG_INFO("Releasing lport %s from this chassis.",
pb->logical_port);
>> > +    return true;
>> >  }
>> >
>> >  static bool
>> > @@ -738,7 +788,41 @@ can_bind_on_this_chassis(const struct
sbrec_chassis
>> *chassis_rec,
>> >             || !strcmp(requested_chassis, chassis_rec->hostname);
>> >  }
>> >
>> > -static void
>> > +static bool
>> > +release_local_binding_children(const struct sbrec_chassis
*chassis_rec,
>> > +                               struct local_binding *lbinding,
>> > +                               bool sb_readonly)
>> > +{
>> > +    struct shash_node *node;
>> > +    SHASH_FOR_EACH (node, &lbinding->children) {
>> > +        struct local_binding *l = node->data;
>> > +        if (is_lbinding_this_chassis(l, chassis_rec)) {
>> > +            if (!release_lport(l->pb, sb_readonly)) {
>> > +                return false;
>> > +            }
>> > +        }
>> > +    }
>> > +
>> > +    return true;
>> > +}
>> > +
>> > +static bool
>> > +release_local_binding(const struct sbrec_chassis *chassis_rec,
>> > +                      struct local_binding *lbinding, bool
sb_readonly)
>> > +{
>> > +    if (!release_local_binding_children(chassis_rec, lbinding,
>> > +                                        sb_readonly)) {
>> > +        return false;
>> > +    }
>> > +
>> > +    if (is_lbinding_this_chassis(lbinding, chassis_rec)) {
>> > +        return release_lport(lbinding->pb, sb_readonly);
>> > +    }
>> > +
>> > +    return true;
>> > +}
>> > +
>> > +static bool
>> >  consider_vif_lport_(const struct sbrec_port_binding *pb,
>> >                      bool can_bind, const char *vif_chassis,
>> >                      struct binding_ctx_in *b_ctx_in,
>> > @@ -750,7 +834,10 @@ consider_vif_lport_(const struct
sbrec_port_binding
>> *pb,
>> >      if (lbinding_set) {
>> >          if (can_bind) {
>> >              /* We can claim the lport. */
>> > -            claim_lport(pb, b_ctx_in->chassis_rec, lbinding->iface);
>> > +            if (!claim_lport(pb, b_ctx_in->chassis_rec,
lbinding->iface,
>> > +                             !b_ctx_in->ovnsb_idl_txn)){
>> > +                return false;
>> > +            }
>> >
>> >
 add_local_datapath(b_ctx_in->sbrec_datapath_binding_by_key,
>> >                              b_ctx_in->sbrec_port_binding_by_datapath,
>> > @@ -775,13 +862,14 @@ consider_vif_lport_(const struct
sbrec_port_binding
>> *pb,
>> >      if (pb->chassis == b_ctx_in->chassis_rec) {
>> >          /* Release the lport if there is no lbinding. */
>> >          if (!lbinding_set || !can_bind) {
>> > -            release_lport(pb);
>> > +            return release_lport(pb, !b_ctx_in->ovnsb_idl_txn);
>> >          }
>> >      }
>> >
>> > +    return true;
>> >  }
>> >
>> > -static void
>> > +static bool
>> >  consider_vif_lport(const struct sbrec_port_binding *pb,
>> >                     struct binding_ctx_in *b_ctx_in,
>> >                     struct binding_ctx_out *b_ctx_out,
>> > @@ -801,11 +889,11 @@ consider_vif_lport(const struct
sbrec_port_binding
>> *pb,
>> >          lbinding->pb = pb;
>> >      }
>> >
>> > -    consider_vif_lport_(pb, can_bind, vif_chassis, b_ctx_in,
>> > -                        b_ctx_out, lbinding, qos_map);
>> > +    return consider_vif_lport_(pb, can_bind, vif_chassis, b_ctx_in,
>> > +                               b_ctx_out, lbinding, qos_map);
>> >  }
>> >
>> > -static void
>> > +static bool
>> >  consider_container_lport(const struct sbrec_port_binding *pb,
>> >                           struct binding_ctx_in *b_ctx_in,
>> >                           struct binding_ctx_out *b_ctx_out,
>> > @@ -815,7 +903,39 @@ consider_container_lport(const struct
>> sbrec_port_binding *pb,
>> >      parent_lbinding = local_binding_find(b_ctx_out->local_bindings,
>> >                                           pb->parent_port);
>> >
>> > -    if (parent_lbinding && !parent_lbinding->pb) {
>> > +    if (!parent_lbinding) {
>> > +        /* There is no local_binding for parent port. Create it
>> > +         * without OVS interface row. This is the only exception
>> > +         * for creating the 'struct local_binding' object without
>> > +         * corresponding OVS interface row.
>> > +         *
>> > +         * This is required for the following reasons:
>> > +         *   - If a logical port P1 is created and then
>> > +         *     few container ports - C1, C2, .. are created first by
CMS.
>> > +         *   - And later when OVS interface row  is created for P1,
then
>> > +         *     we want the these container ports also be claimed by
the
>> > +         *     chassis.
>> > +         * */
>>
>> It seems the assumption is, for all container PBs, their parent PBs
should
>> always have local_binding created, no matter if they are bound to current
>> chassis.
>
>
> That's right.
>
>>
>> However, at least some of the code in this patch would violate this
>> assumption, e.g. handle_iface_delete() will call local_binding_delete()
and
>> delete the parent binding and all the children container bindings.
>
>
> I missed this part. I'll handle it in next version.
>
>>
>> It is also better to document this clearly in the comment of struct
>> local_binding for what should be expected in the local_bindings.
>
>
> I've added comments for the local_bindings struct. But it looks like it's
not
> sufficient. I'll add more .
>
>> My
>> understanding is, for each binding_run() or change_handler execution, the
>> expected items left in it is:
>> 1) For each interface that has iface-id configured.
>
> Yes.
>
>>
>> 2) For each container PBs and their parent PBs, no matter if they are
bound
>> to this chassis.
>
>
> Yes.
>
>>
>> 3) For some of the non VIF PBs ...
>
>
> Only for the "virtual" PB if it's parent is bound.
>
>>
>> Is this correct?
>>
>> > +        parent_lbinding = local_binding_create(pb->parent_port, NULL,
>> NULL,
>> > +                                               BT_VIF);
>> > +        local_binding_add(b_ctx_out->local_bindings, parent_lbinding);
>> > +    }
>> > +
>> > +    struct local_binding *container_lbinding =
>> > +        local_binding_find_child(parent_lbinding, pb->logical_port);
>> > +
>> > +    if (!container_lbinding) {
>> > +        container_lbinding = local_binding_create(pb->logical_port,
>> > +
 parent_lbinding->iface,
>> > +                                                  pb, BT_CONTAINER);
>> > +        local_binding_add_child(parent_lbinding, container_lbinding);
>> > +    } else {
>> > +        ovs_assert(container_lbinding->type == BT_CONTAINER);
>> > +        container_lbinding->pb = pb;
>> > +        container_lbinding->iface = parent_lbinding->iface;
>> > +    }
>> > +
>> > +    if (!parent_lbinding->pb) {
>> >          parent_lbinding->pb = lport_lookup_by_name(
>> >              b_ctx_in->sbrec_port_binding_by_name, pb->parent_port);
>> >
>> > @@ -824,37 +944,28 @@ consider_container_lport(const struct
>> sbrec_port_binding *pb,
>> >               * So call consider_vif_lport() to process it first. */
>> >              consider_vif_lport(parent_lbinding->pb, b_ctx_in,
b_ctx_out,
>> >                                 parent_lbinding, qos_map);
>> > -        }
>> > -    }
>> > +        } else {
>> > +            /* The parent lport doesn't exist. Call release_lport() to
>> > +             * release the container lport, if it was bound earlier.
*/
>> > +            if (is_lbinding_this_chassis(container_lbinding,
>> > +                                         b_ctx_in->chassis_rec)) {
>> > +               return release_lport(pb, !b_ctx_in->ovnsb_idl_txn);
>> > +            }
>> >
>> > -    if (!parent_lbinding || !parent_lbinding->pb) {
>> > -        /* Call release_lport, to release the container lport, if
>> > -         * it was bound earlier. */
>> > -        if (pb->chassis == b_ctx_in->chassis_rec) {
>> > -            release_lport(pb);
>> > +            return true;
>> >          }
>> > -        return;
>> >      }
>> >
>> > -    struct local_binding *container_lbinding =
>> > -        local_binding_find_child(parent_lbinding, pb->logical_port);
>> > -    ovs_assert(!container_lbinding);
>> > -
>> > -    container_lbinding = local_binding_create(pb->logical_port,
>> > -                                              parent_lbinding->iface,
>> > -                                              pb, BT_CONTAINER);
>> > -    local_binding_add_child(parent_lbinding, container_lbinding);
>> > -
>> >      const char *vif_chassis = smap_get(&parent_lbinding->pb->options,
>> >                                         "requested-chassis");
>> >      bool can_bind = can_bind_on_this_chassis(b_ctx_in->chassis_rec,
>> >                                               vif_chassis);
>> >
>> > -    consider_vif_lport_(pb, can_bind, vif_chassis, b_ctx_in,
b_ctx_out,
>> > -                        container_lbinding, qos_map);
>> > +    return consider_vif_lport_(pb, can_bind, vif_chassis, b_ctx_in,
>> b_ctx_out,
>> > +                               container_lbinding, qos_map);
>> >  }
>> >
>> > -static void
>> > +static bool
>> >  consider_virtual_lport(const struct sbrec_port_binding *pb,
>> >                         struct binding_ctx_in *b_ctx_in,
>> >                         struct binding_ctx_out *b_ctx_out,
>> > @@ -881,11 +992,16 @@ consider_virtual_lport(const struct
>> sbrec_port_binding *pb,
>> >      if (is_lbinding_this_chassis(parent_lbinding,
>> b_ctx_in->chassis_rec)) {
>> >          virtual_lbinding =
>> >              local_binding_find_child(parent_lbinding,
pb->logical_port);
>> > -        ovs_assert(!virtual_lbinding);
>> > -        virtual_lbinding = local_binding_create(pb->logical_port,
>> > -
 parent_lbinding->iface,
>> > -                                                pb, BT_VIRTUAL);
>> > -        local_binding_add_child(parent_lbinding, virtual_lbinding);
>> > +        if (!virtual_lbinding) {
>> > +            virtual_lbinding = local_binding_create(pb->logical_port,
>> > +
>>  parent_lbinding->iface,
>> > +                                                    pb, BT_VIRTUAL);
>> > +            local_binding_add_child(parent_lbinding,
virtual_lbinding);
>> > +        } else {
>> > +            ovs_assert(virtual_lbinding->type == BT_VIRTUAL);
>> > +            virtual_lbinding->pb = pb;
>> > +            virtual_lbinding->iface = parent_lbinding->iface;
>> > +        }
>> >      }
>> >
>> >      return consider_vif_lport_(pb, true, NULL, b_ctx_in, b_ctx_out,
>> > @@ -895,14 +1011,13 @@ consider_virtual_lport(const struct
>> sbrec_port_binding *pb,
>> >  /* Considers either claiming the lport or releasing the lport
>> >   * for non VIF lports.
>> >   */
>> > -static void
>> > +static bool
>> >  consider_nonvif_lport_(const struct sbrec_port_binding *pb,
>> >                         bool our_chassis,
>> >                         bool has_local_l3gateway,
>> >                         struct binding_ctx_in *b_ctx_in,
>> >                         struct binding_ctx_out *b_ctx_out)
>> >  {
>> > -    ovs_assert(b_ctx_in->ovnsb_idl_txn);
>> >      if (our_chassis) {
>> >          sset_add(b_ctx_out->local_lports, pb->logical_port);
>> >          add_local_datapath(b_ctx_in->sbrec_datapath_binding_by_key,
>> > @@ -912,13 +1027,16 @@ consider_nonvif_lport_(const struct
>> sbrec_port_binding *pb,
>> >                             b_ctx_out->local_datapaths);
>> >
>> >          update_local_lport_ids(b_ctx_out->local_lport_ids, pb);
>> > -        claim_lport(pb, b_ctx_in->chassis_rec, NULL);
>> > +        return claim_lport(pb, b_ctx_in->chassis_rec, NULL,
>> > +                           !b_ctx_in->ovnsb_idl_txn);
>> >      } else if (pb->chassis == b_ctx_in->chassis_rec) {
>> > -        release_lport(pb);
>> > +        return release_lport(pb, !b_ctx_in->ovnsb_idl_txn);
>> >      }
>> > +
>> > +    return true;
>> >  }
>> >
>> > -static void
>> > +static bool
>> >  consider_l2gw_lport(const struct sbrec_port_binding *pb,
>> >                      struct binding_ctx_in *b_ctx_in,
>> >                      struct binding_ctx_out *b_ctx_out)
>> > @@ -927,10 +1045,10 @@ consider_l2gw_lport(const struct
>> sbrec_port_binding *pb,
>> >      bool our_chassis = chassis_id && !strcmp(chassis_id,
>> >
>> b_ctx_in->chassis_rec->name);
>> >
>> > -    consider_nonvif_lport_(pb, our_chassis, false, b_ctx_in,
b_ctx_out);
>> > +    return consider_nonvif_lport_(pb, our_chassis, false, b_ctx_in,
>> b_ctx_out);
>> >  }
>> >
>> > -static void
>> > +static bool
>> >  consider_l3gw_lport(const struct sbrec_port_binding *pb,
>> >                      struct binding_ctx_in *b_ctx_in,
>> >                      struct binding_ctx_out *b_ctx_out)
>> > @@ -939,7 +1057,7 @@ consider_l3gw_lport(const struct
sbrec_port_binding
>> *pb,
>> >      bool our_chassis = chassis_id && !strcmp(chassis_id,
>> >
>> b_ctx_in->chassis_rec->name);
>> >
>> > -    consider_nonvif_lport_(pb, our_chassis, true, b_ctx_in,
b_ctx_out);
>> > +    return consider_nonvif_lport_(pb, our_chassis, true, b_ctx_in,
>> b_ctx_out);
>> >  }
>> >
>> >  static void
>> > @@ -958,7 +1076,7 @@ consider_localnet_lport(const struct
>> sbrec_port_binding *pb,
>> >      update_local_lport_ids(b_ctx_out->local_lport_ids, pb);
>> >  }
>> >
>> > -static void
>> > +static bool
>> >  consider_ha_lport(const struct sbrec_port_binding *pb,
>> >                    struct binding_ctx_in *b_ctx_in,
>> >                    struct binding_ctx_out *b_ctx_out)
>> > @@ -986,18 +1104,18 @@ consider_ha_lport(const struct
sbrec_port_binding
>> *pb,
>> >                             b_ctx_out->local_datapaths);
>> >      }
>> >
>> > -    consider_nonvif_lport_(pb, our_chassis, false, b_ctx_in,
b_ctx_out);
>> > +    return consider_nonvif_lport_(pb, our_chassis, false, b_ctx_in,
>> b_ctx_out);
>> >  }
>> >
>> > -static void
>> > +static bool
>> >  consider_cr_lport(const struct sbrec_port_binding *pb,
>> >                    struct binding_ctx_in *b_ctx_in,
>> >                    struct binding_ctx_out *b_ctx_out)
>> >  {
>> > -    consider_ha_lport(pb, b_ctx_in, b_ctx_out);
>> > +    return consider_ha_lport(pb, b_ctx_in, b_ctx_out);
>> >  }
>> >
>> > -static void
>> > +static bool
>> >  consider_external_lport(const struct sbrec_port_binding *pb,
>> >                          struct binding_ctx_in *b_ctx_in,
>> >                          struct binding_ctx_out *b_ctx_out)
>> > @@ -1050,6 +1168,8 @@ build_local_bindings(struct binding_ctx_in
>> *b_ctx_in,
>> >                  }
>> >
>> >                  sset_add(b_ctx_out->local_lports, iface_id);
>> > +                smap_replace(b_ctx_out->local_iface_ids,
iface_rec->name,
>> > +                             iface_id);
>> >              }
>> >
>> >              /* Check if this is a tunnel interface. */
>> > @@ -1165,9 +1285,9 @@ binding_run(struct binding_ctx_in *b_ctx_in,
struct
>> binding_ctx_out *b_ctx_out)
>> >       * corresponding local datapath accordingly. */
>> >      struct localnet_lport *lnet_lport;
>> >      LIST_FOR_EACH_POP (lnet_lport, list_node, &localnet_lports) {
>> > -        consider_localnet_port(lnet_lport->pb, &bridge_mappings,
>> > -                               b_ctx_out->egress_ifaces,
>> > -                               b_ctx_out->local_datapaths);
>> > +        update_ld_localnet_port(lnet_lport->pb, &bridge_mappings,
>> > +                                b_ctx_out->egress_ifaces,
>> > +                                b_ctx_out->local_datapaths);
>> >          free(lnet_lport);
>> >      }
>> >
>> > @@ -1185,95 +1305,625 @@ binding_run(struct binding_ctx_in *b_ctx_in,
>> struct binding_ctx_out *b_ctx_out)
>> >      destroy_qos_map(&qos_map);
>> >  }
>> >
>> > -/* Returns true if port-binding changes potentially require flow
changes
>> on
>> > - * the current chassis. Returns false if we are sure there is no
impact.
>> */
>> > +/* Returns true if the database is all cleaned up, false if more work
is
>> > + * required. */
>> >  bool
>> > -binding_evaluate_port_binding_changes(struct binding_ctx_in *b_ctx_in,
>> > -                                      struct binding_ctx_out
*b_ctx_out)
>> > +binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn,
>> > +                const struct sbrec_port_binding_table
>> *port_binding_table,
>> > +                const struct sbrec_chassis *chassis_rec)
>> >  {
>> > -    if (!b_ctx_in->chassis_rec) {
>> > +    if (!ovnsb_idl_txn) {
>> > +        return false;
>> > +    }
>> > +    if (!chassis_rec) {
>> >          return true;
>> >      }
>> >
>> > -    bool changed = false;
>> > -
>> >      const struct sbrec_port_binding *binding_rec;
>> > -    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (binding_rec,
>> > -
>> b_ctx_in->port_binding_table) {
>> > -        /* XXX: currently OVSDB change tracking doesn't support
getting
>> old
>> > -         * data when the operation is update, so if a port-binding
moved
>> from
>> > -         * this chassis to another, there is no easy way to find out
the
>> > -         * change. To workaround this problem, we just makes sure if
>> > -         * any port *related to* this chassis has any change, then
>> trigger
>> > -         * recompute.
>> > -         *
>> > -         * - If a regular VIF is unbound from this chassis, the local
>> ovsdb
>> > -         *   interface table will be updated, which will trigger
>> recompute.
>> > -         *
>> > -         * - If the port is not a regular VIF, always trigger
recompute.
>> */
>> > -        if (binding_rec->chassis == b_ctx_in->chassis_rec) {
>> > -            changed = true;
>> > +    bool any_changes = false;
>> > +    SBREC_PORT_BINDING_TABLE_FOR_EACH (binding_rec,
port_binding_table) {
>> > +        if (binding_rec->chassis == chassis_rec) {
>> > +            if (binding_rec->encap) {
>> > +                sbrec_port_binding_set_encap(binding_rec, NULL);
>> > +            }
>> > +            sbrec_port_binding_set_chassis(binding_rec, NULL);
>> > +            any_changes = true;
>> > +        }
>> > +    }
>> > +
>> > +    if (any_changes) {
>> > +        ovsdb_idl_txn_add_comment(
>> > +            ovnsb_idl_txn,
>> > +            "ovn-controller: removing all port bindings for '%s'",
>> > +            chassis_rec->name);
>> > +    }
>> > +
>> > +    return !any_changes;
>> > +}
>> > +
>> > +static void
>> > +add_local_datapath_peer_port(const struct sbrec_port_binding *pb,
>> > +                             struct binding_ctx_in *b_ctx_in,
>> > +                             struct binding_ctx_out *b_ctx_out,
>> > +                             struct local_datapath *ld)
>> > +{
>> > +    const char *peer_name = smap_get(&pb->options, "peer");
>> > +    if (strcmp(pb->type, "patch") || !peer_name) {
>> > +        return;
>> > +    }
>> > +
>> > +    const struct sbrec_port_binding *peer;
>> > +    peer = lport_lookup_by_name(b_ctx_in->sbrec_port_binding_by_name,
>> > +                                peer_name);
>> > +
>> > +    if (!peer || !peer->datapath) {
>> > +        return;
>> > +    }
>> > +
>> > +    bool present = false;
>> > +    for (size_t i = 0; i < ld->n_peer_ports; i++) {
>> > +        if (ld->peer_ports[i].local == pb) {
>> > +            present = true;
>> >              break;
>> >          }
>> > +    }
>> >
>> > -        if (!strcmp(binding_rec->type, "remote")) {
>> > -            continue;
>> > +    if (!present) {
>> > +        ld->n_peer_ports++;
>> > +        if (ld->n_peer_ports > ld->n_allocated_peer_ports) {
>> > +            ld->peer_ports =
>> > +                x2nrealloc(ld->peer_ports,
>> > +                           &ld->n_allocated_peer_ports,
>> > +                           sizeof *ld->peer_ports);
>> > +        }
>> > +        ld->peer_ports[ld->n_peer_ports - 1].local = pb;
>> > +        ld->peer_ports[ld->n_peer_ports - 1].remote = peer;
>> > +    }
>> > +
>> > +    struct local_datapath *peer_ld =
>> > +        get_local_datapath(b_ctx_out->local_datapaths,
>> > +                           peer->datapath->tunnel_key);
>> > +    if (!peer_ld) {
>> > +        add_local_datapath__(b_ctx_in->sbrec_datapath_binding_by_key,
>> > +                             b_ctx_in->sbrec_port_binding_by_datapath,
>> > +                             b_ctx_in->sbrec_port_binding_by_name,
>> > +                             peer->datapath, false,
>> > +                             1, b_ctx_out->local_datapaths);
>> > +        return;
>> > +    }
>> > +
>> > +    for (size_t i = 0; i < peer_ld->n_peer_ports; i++) {
>> > +        if (peer_ld->peer_ports[i].local == peer) {
>> > +            return;
>> >          }
>> > +    }
>> >
>> > -        if (strcmp(binding_rec->type, "")) {
>> > -            changed = true;
>> > +    peer_ld->n_peer_ports++;
>> > +    if (peer_ld->n_peer_ports > peer_ld->n_allocated_peer_ports) {
>> > +        peer_ld->peer_ports =
>> > +            x2nrealloc(peer_ld->peer_ports,
>> > +                        &peer_ld->n_allocated_peer_ports,
>> > +                        sizeof *peer_ld->peer_ports);
>> > +    }
>> > +    peer_ld->peer_ports[peer_ld->n_peer_ports - 1].local = peer;
>> > +    peer_ld->peer_ports[peer_ld->n_peer_ports - 1].remote = pb;
>> > +}
>> > +
>> > +static void
>> > +remove_local_datapath_peer_port(const struct sbrec_port_binding *pb,
>> > +                                struct local_datapath *ld,
>> > +                                struct hmap *local_datapaths)
>> > +{
>> > +    size_t i = 0;
>> > +    for (i = 0; i < ld->n_peer_ports; i++) {
>> > +        if (ld->peer_ports[i].local == pb) {
>> >              break;
>> >          }
>> > +    }
>> > +
>> > +    if (i == ld->n_peer_ports) {
>> > +        return;
>> > +    }
>> > +
>> > +    const struct sbrec_port_binding *peer = ld->peer_ports[i].remote;
>> >
>> > -        struct local_binding *lbinding = NULL;
>> > -        if (!binding_rec->parent_port ||
!binding_rec->parent_port[0]) {
>> > -            lbinding = local_binding_find(b_ctx_out->local_bindings,
>> > -                                          binding_rec->logical_port);
>> > +    /* Possible improvement: We can shrint the allocated peer ports
>> > +     * if (ld->n_peer_ports < ld->n_allocated_peer_ports / 2).
>> > +     */
>> > +    ld->peer_ports[i].local = ld->peer_ports[ld->n_peer_ports -
1].local;
>> > +    ld->peer_ports[i].remote = ld->peer_ports[ld->n_peer_ports -
>> 1].remote;
>> > +    ld->n_peer_ports--;
>> > +
>> > +    struct local_datapath *peer_ld =
>> > +        get_local_datapath(local_datapaths,
peer->datapath->tunnel_key);
>> > +    if (peer_ld) {
>> > +        remove_local_datapath_peer_port(peer, peer_ld,
local_datapaths);
>>
>> Sorry that I don't understand this part. peer's peer is the "pb" that is
>> passed in the parameter of the current function call, so this recursive
>> call would only try to remove the "pb", which is already removed (before
>> calling this function). So I think it is useless.
>
>
> Let's say there is sw0-lr0 of sw0 connected to the peer lr0-sw0 of lr0.
>
> When this function is called for sw0-lr0, it removes the peer ports
sw0-lr0 from
> the 'sw0' ld and then it calls recursively for lr0.
>
> Yes you're right. It will again call for sw0-lr0 of sw0. But that would
be a no-op as
> the for loop in the beginning cannot find sw0-lr0  in 'sw0' ld.. Even
though it
> is useless are there any issues with it ? Since its recursion and it has
proper return
> conditions, I think it should be fine.
>
> Another option is duplicating the code for removing the peer.
>

OK, I thought it is useless, but it seems needed.

>
>>
>> > +    }
>> > +}
>> > +
>> > +static void
>> > +remove_pb_from_local_datapath(const struct sbrec_port_binding *pb,
>> > +                              const struct sbrec_chassis *chassis_rec,
>> > +                              struct binding_ctx_out *b_ctx_out,
>> > +                              struct local_datapath *ld)
>> > +{
>> > +    remove_local_lport_ids(pb, b_ctx_out->local_lport_ids);
>> > +    if (!strcmp(pb->type, "patch") ||
>> > +        !strcmp(pb->type, "l3gateway")) {
>> > +        remove_local_datapath_peer_port(pb, ld,
>> b_ctx_out->local_datapaths);
>> > +    } else if (!strcmp(pb->type, "localnet")) {
>> > +        if (ld->localnet_port &&
!strcmp(ld->localnet_port->logical_port,
>> > +                                         pb->logical_port)) {
>> > +            ld->localnet_port = NULL;
>> > +        }
>> > +    } else if (!strcmp(pb->type, "l3gateway")) {
>> > +        const char *chassis_id = smap_get(&pb->options,
>> > +                                          "l3gateway-chassis");
>> > +        if (chassis_id && !strcmp(chassis_id, chassis_rec->name)) {
>> > +            ld->has_local_l3gateway = false;
>> > +        }
>> > +    }
>>
>> I think we need to remove ld from the local_datapaths if the current pb
is
>> the only reason for that ld to be exist on this chassis. Also, all the
>> related lds connected by patch ports should be removed as well, if there
>> are no other pbs causing them to be added to this chassis. This is the
>> reverse of add_local_datapath__(). It doesn't seem to be easy without
going
>> through all local_lbindings. Maybe this can be done in
>> binding_handle_ovs_interface_changes() and
>> binding_handle_port_binding_changes() out of the loop.
>>
>
> I left this unaddressed intentionally as I think it will complicate the
code.
>
> Patch 3 of this series handles I-P for datapath and when a datapath is
deleted,
> it removes the datapath from local_datapaths if it is present.
> I think this should be Ok as we will eventually free it. If we really
want to address this,
> I'm fine with it but I'd prefer a follow up patch to do this.
>
OK, maybe a comment can be added to make it clear for future improvement.
This changes the behavior of current implementation. Now unrelated
datapaths are removed when certain ports are unbound, and the flows in the
HV can be largely reduced, but with this change it may not reduce any more
until a recompute is triggered. It is not a big problem I think.

>
>>
>> > +}
>> > +
>> > +static bool
>> > +handle_iface_add(const struct ovsrec_interface *iface_rec,
>> > +                 const char *iface_id,
>> > +                 struct binding_ctx_in *b_ctx_in,
>> > +                 struct binding_ctx_out *b_ctx_out,
>> > +                 struct hmap *qos_map)
>> > +{
>> > +    sset_add(b_ctx_out->local_lports, iface_id);
>> > +    smap_replace(b_ctx_out->local_iface_ids, iface_rec->name,
iface_id);
>> > +
>> > +    struct local_binding *lbinding =
>> > +        local_binding_find(b_ctx_out->local_bindings, iface_id);
>> > +
>> > +    if (!lbinding) {
>> > +        lbinding = local_binding_create(iface_id, iface_rec, NULL,
>> BT_VIF);
>> > +        local_binding_add(b_ctx_out->local_bindings, lbinding);
>> > +    } else {
>> > +        lbinding->iface = iface_rec;
>> > +    }
>> > +
>> > +    if (!lbinding->pb || strcmp(lbinding->name,
>> lbinding->pb->logical_port)) {
>> > +        lbinding->pb = lport_lookup_by_name(
>> > +            b_ctx_in->sbrec_port_binding_by_name, lbinding->name);
>> > +        if (lbinding->pb && !strcmp(lbinding->pb->type, "virtual")) {
>>
>> Since we never bind "virtual" ports, this should be a misconfiguration,
and
>> it is better to log a warning message to be more clear.
>>
>> > +            lbinding->pb = NULL;
>> > +        }
>> > +    }
>> > +
>> > +    if (lbinding->pb) {
>> > +        if (!consider_vif_lport(lbinding->pb, b_ctx_in, b_ctx_out,
>> > +                                lbinding, qos_map)) {
>> > +            return false;
>> > +        }
>> > +    }
>> > +
>> > +    /* Update the child local_binding's iface (if any children) and
try
>> to
>> > +     *  claim the container lbindings. */
>> > +    struct shash_node *node;
>> > +    SHASH_FOR_EACH (node, &lbinding->children) {
>> > +        struct local_binding *child = node->data;
>> > +        child->iface = iface_rec;
>> > +        if (child->type == BT_CONTAINER) {
>> > +            if (!consider_container_lport(child->pb, b_ctx_in,
b_ctx_out,
>> > +                                          qos_map)) {
>> > +                return false;
>> > +            }
>> > +        }
>> > +    }
>> > +
>> > +    return true;
>> > +}
>> > +
>> > +static bool
>> > +handle_iface_delete(const char *lport_name, const char *iface_name,
>> > +                    struct binding_ctx_in *b_ctx_in,
>> > +                    struct binding_ctx_out *b_ctx_out, bool *changed)
>> > +{
>> > +    struct local_binding *lbinding;
>> > +    lbinding = local_binding_find(b_ctx_out->local_bindings,
>> > +                                  lport_name);
>> > +    if (lbinding && lbinding->pb &&
>> > +        lbinding->pb->chassis == b_ctx_in->chassis_rec) {
>> > +
>> > +        if (!release_local_binding(b_ctx_in->chassis_rec, lbinding,
>> > +                                   !b_ctx_in->ovnsb_idl_txn)) {
>> > +            return false;
>> > +        }
>> > +
>> > +        struct local_datapath *ld =
>> > +            get_local_datapath(b_ctx_out->local_datapaths,
>> > +                                lbinding->pb->datapath->tunnel_key);
>> > +        if (ld) {
>> > +            remove_pb_from_local_datapath(lbinding->pb,
>> > +                                            b_ctx_in->chassis_rec,
>> > +                                            b_ctx_out, ld);
>> > +        }
>> > +        local_binding_delete(b_ctx_out->local_bindings, lbinding);
>> > +        *changed = true;
>> > +    }
>> > +
>> > +    sset_find_and_delete(b_ctx_out->local_lports, lport_name);
>> > +    smap_remove(b_ctx_out->local_iface_ids, iface_name);
>> > +
>> > +    return true;
>> > +}
>> > +
>> > +static bool
>> > +is_iface_vif(const struct ovsrec_interface *iface_rec)
>> > +{
>> > +    if (iface_rec->type && iface_rec->type[0] &&
>> > +        strcmp(iface_rec->type, "internal")) {
>> > +        return false;
>> > +    }
>> > +
>> > +    return true;
>> > +}
>> > +
>> > +/* Returns true if the ovs interface changes were handled
successfully,
>> > + * false otherwise.
>> > + */
>> > +bool
>> > +binding_handle_ovs_interface_changes(struct binding_ctx_in *b_ctx_in,
>> > +                                     struct binding_ctx_out
*b_ctx_out,
>> > +                                     bool *changed)
>> > +{
>> > +    if (!b_ctx_in->chassis_rec) {
>> > +        return false;
>> > +    }
>> > +
>> > +    bool handled = true;
>> > +    *changed = false;
>> > +
>> > +    /* Run the tracked interfaces loop twice. One to handle deleted
>> > +     * changes. And another to handle add/update changes.
>> > +     * This will ensure correctness.
>> > +     */
>> > +    const struct ovsrec_interface *iface_rec;
>> > +    OVSREC_INTERFACE_TABLE_FOR_EACH_TRACKED (iface_rec,
>> > +                                             b_ctx_in->iface_table) {
>> > +        if (!is_iface_vif(iface_rec)) {
>> > +            /* Right now are not handling ovs_interface changes of
>> > +             * other types. This can be enhanced to handle of
>> > +             * types - patch and tunnel. */
>> > +            handled = false;
>> > +            break;
>> > +        }
>> > +
>> > +        const char *iface_id = smap_get(&iface_rec->external_ids,
>> "iface-id");
>> > +        const char *old_iface_id =
smap_get(b_ctx_out->local_iface_ids,
>> > +                                            iface_rec->name);
>> > +        const char *cleared_iface_id = NULL;
>> > +        if (!ovsrec_interface_is_deleted(iface_rec)) {
>> > +            if (iface_id) {
>> > +                /* Check if iface_id is changed. If so we need to
>> > +                 * release the old port binding and associate this
>> > +                 * inteface to new port binding. */
>> > +                if (old_iface_id && strcmp(iface_id, old_iface_id)) {
>> > +                    cleared_iface_id = old_iface_id;
>> > +                }
>> > +            } else if (old_iface_id) {
>> > +                cleared_iface_id = old_iface_id;
>> > +            }
>> >          } else {
>> > -            lbinding = local_binding_find(b_ctx_out->local_bindings,
>> > -                                          binding_rec->parent_port);
>> > +            cleared_iface_id = iface_id;
>> > +            iface_id = NULL;
>> >          }
>> >
>> > -        if (lbinding) {
>> > -            changed = true;
>> > +        if (cleared_iface_id) {
>> > +            handled = handle_iface_delete(cleared_iface_id,
>> iface_rec->name,
>> > +                                          b_ctx_in, b_ctx_out,
changed);
>> > +        }
>> > +
>> > +        if (!handled) {
>> >              break;
>> >          }
>> >      }
>> >
>> > -    return changed;
>> > +    if (!handled) {
>> > +        return false;
>> > +    }
>> > +
>> > +    struct hmap qos_map = HMAP_INITIALIZER(&qos_map);
>> > +    struct hmap *qos_map_ptr =
>> > +        sset_is_empty(b_ctx_out->egress_ifaces) ? NULL : &qos_map;
>> > +
>> > +    OVSREC_INTERFACE_TABLE_FOR_EACH_TRACKED (iface_rec,
>> > +                                             b_ctx_in->iface_table) {
>> > +        /* Loop to handle create and update changes only. */
>>
>> Thanks for splitting the loops, but here are still some problems.
>>
>> 1. The first loop handled: a) interface deletion. b) interface update
with
>> old iface-id cleared. So in this second loop it should handle all the
rest
>> cases: a) interface add. b) interface update without iface-id change. It
>> seems b) is treated as just interface add, which may be wrong: if
iface-id
>> didn't change but some other fields of the interface record is changed,
it
>> is not handled properly.
>
>
> If other fields change, what should we handle ? Can ofport change ?
> I'll see if ofport can be changed and what happens when it changes.
>
> For other fields like name, mtu etc do we need to handle anything ?
> I think we only care about the external_ids:iface-id and the ofport.
> The function handle_iface_add() even if it is called for other changes,
> it will be a no-op.
>
> Also from the tracking we can't figure out what fields change right ?
>
>
Yes, I think it is hard to check with fields are changed and handle it
accordingly. That's why I suggest to make it simple: if there is an update,
always handle it like a deletion and then creation. This way, we dont need
to consider all the corner cases and we are sure it is correct.

>
>>
>>
>> 2. Some other condition checks should be done for this loop, such as: if
>> (!is_iface_vif(iface_rec)) ...
>
>
> This is not required because the first loop checks for it and if there is
any
> non vif interface change, then this function returns false, before the
> second loop.
>
OK.

>>
>>
>> > +        if (ovsrec_interface_is_deleted(iface_rec)) {
>> > +            continue;
>> > +        }
>> > +
>> > +        const char *iface_id = smap_get(&iface_rec->external_ids,
>> "iface-id");
>> > +        int64_t ofport = iface_rec->n_ofport ? *iface_rec->ofport : 0;
>> > +        if (iface_id && ofport > 0) {
>>
>> What if iface-id didn't change, but ofport changed from non-zero to 0?
How
>> should we handle the change?
>>
>
> Good point. I think we should consider this as  handle_iface_delete.
>
> I'll rename the function handle_iface_delete() to handle_iface_release()
> and handle_iface_add() to handle_iface_claim() to be more clear.
>
>> A general approach can be: whenever there is an update
>> (!ovsrec_interface_is_new()), we firstly handle it as interface delete,
and
>> then handle an interface add.
>
>
> But why delete and add if can determine that it is going to be either one
> i.e release or claim and I think we can definitely determine.
> Also in patch 6/7 where we do tracking, it becomes a bit complicated
> if we handle it as delete and then add as we may add 2 such tracking
> data.
>
It is for simplicity and correctness. Please see the comment above.

> Thanks
> Numan
>
>>
>> > +            *changed = true;
>> > +            handled = handle_iface_add(iface_rec, iface_id, b_ctx_in,
>> > +                                       b_ctx_out, qos_map_ptr);
>> > +            if (!handled) {
>> > +                break;
>> > +            }
>> > +        }
>> > +    }
>> > +
>> > +    if (handled && qos_map_ptr && set_noop_qos(b_ctx_in->ovs_idl_txn,
>> > +                                               b_ctx_in->port_table,
>> > +                                               b_ctx_in->qos_table,
>> > +
>> b_ctx_out->egress_ifaces)) {
>> > +        const char *entry;
>> > +        SSET_FOR_EACH (entry, b_ctx_out->egress_ifaces) {
>> > +            setup_qos(entry, &qos_map);
>> > +        }
>> > +    }
>> > +
>> > +    destroy_qos_map(&qos_map);
>> > +    return handled;
>> >  }
>> >
>> > -/* Returns true if the database is all cleaned up, false if more work
is
>> > - * required. */
>> > -bool
>> > -binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn,
>> > -                const struct sbrec_port_binding_table
>> *port_binding_table,
>> > -                const struct sbrec_chassis *chassis_rec)
>> > +static void
>> > +handle_deleted_lport(const struct sbrec_port_binding *pb,
>> > +                     struct binding_ctx_in *b_ctx_in,
>> > +                     struct binding_ctx_out *b_ctx_out)
>> >  {
>> > -    if (!ovnsb_idl_txn) {
>> > +    struct local_datapath *ld =
>> > +        get_local_datapath(b_ctx_out->local_datapaths,
>> > +                           pb->datapath->tunnel_key);
>> > +    if (ld) {
>> > +        remove_pb_from_local_datapath(pb, b_ctx_in->chassis_rec,
>> > +                                      b_ctx_out, ld);
>> > +    }
>> > +}
>> > +
>> > +static struct local_binding *
>> > +get_lbinding_for_lport(const struct sbrec_port_binding *pb,
>> > +                       enum en_lport_type lport_type,
>> > +                       struct binding_ctx_out *b_ctx_out)
>> > +{
>> > +    ovs_assert(lport_type == LP_VIF || lport_type == LP_VIRTUAL);
>> > +
>> > +    if (lport_type == LP_VIF && !is_lport_container(pb)) {
>> > +        return local_binding_find(b_ctx_out->local_bindings,
>> pb->logical_port);
>> > +    }
>> > +
>> > +    struct local_binding *parent_lbinding = NULL;
>> > +
>> > +    if (lport_type == LP_VIRTUAL) {
>> > +        parent_lbinding =
local_binding_find(b_ctx_out->local_bindings,
>> > +                                             pb->virtual_parent);
>> > +    } else {
>> > +        parent_lbinding =
local_binding_find(b_ctx_out->local_bindings,
>> > +                                             pb->parent_port);
>> > +    }
>> > +
>> > +    return parent_lbinding
>> > +           ? local_binding_find(&parent_lbinding->children,
>> pb->logical_port)
>> > +           : NULL;
>> > +}
>> > +
>> > +static bool
>> > +handle_deleted_vif_lport(const struct sbrec_port_binding *pb,
>> > +                         enum en_lport_type lport_type,
>> > +                         struct binding_ctx_in *b_ctx_in,
>> > +                         struct binding_ctx_out *b_ctx_out,
>> > +                         bool *changed)
>> > +{
>> > +    struct local_binding *lbinding =
>> > +        get_lbinding_for_lport(pb, lport_type, b_ctx_out);
>> > +
>> > +    if (lbinding) {
>> > +        lbinding->pb = NULL;
>> > +        /* The port_binding 'pb' is deleted. So there is no need to
>> > +         * clear the 'chassis' column of 'pb'. But we need to do
>> > +         * for the local_binding's children. */
>> > +        if (lbinding->type == BT_VIF &&
>> > +                !release_local_binding_children(b_ctx_in->chassis_rec,
>> > +                                                lbinding,
>> > +
>>  !b_ctx_in->ovnsb_idl_txn)) {
>> > +            return false;
>> > +        }
>> > +
>> > +        *changed = true;
>> > +    }
>> > +
>> > +    handle_deleted_lport(pb, b_ctx_in, b_ctx_out);
>> > +    return true;
>> > +}
>> > +
>> > +static bool
>> > +handle_updated_vif_lport(const struct sbrec_port_binding *pb,
>> > +                         enum en_lport_type lport_type,
>> > +                         struct binding_ctx_in *b_ctx_in,
>> > +                         struct binding_ctx_out *b_ctx_out,
>> > +                         struct hmap *qos_map,
>> > +                         bool *changed)
>> > +{
>> > +    bool claimed = (pb->chassis == b_ctx_in->chassis_rec);
>> > +    bool handled = true;
>> > +
>> > +    if (lport_type == LP_VIRTUAL) {
>> > +        handled = consider_virtual_lport(pb, b_ctx_in, b_ctx_out,
>> qos_map);
>> > +    } else if (lport_type == LP_VIF && is_lport_container(pb)) {
>> > +        handled = consider_container_lport(pb, b_ctx_in, b_ctx_out,
>> qos_map);
>> > +    } else {
>> > +        handled = consider_vif_lport(pb, b_ctx_in, b_ctx_out, NULL,
>> qos_map);
>> > +    }
>> > +
>> > +    if (!handled) {
>> >          return false;
>> >      }
>> > -    if (!chassis_rec) {
>> > +
>> > +    bool now_claimed = (pb->chassis == b_ctx_in->chassis_rec);
>> > +    bool changed_ = (claimed != now_claimed);
>> > +
>> > +    if (changed_) {
>> > +        *changed = true;
>> > +    }
>> > +
>> > +    if (lport_type == LP_VIRTUAL ||
>> > +        (lport_type == LP_VIF && is_lport_container(pb)) ||
!changed_) {
>> >          return true;
>> >      }
>> >
>> > -    const struct sbrec_port_binding *binding_rec;
>> > -    bool any_changes = false;
>> > -    SBREC_PORT_BINDING_TABLE_FOR_EACH (binding_rec,
port_binding_table) {
>> > -        if (binding_rec->chassis == chassis_rec) {
>> > -            if (binding_rec->encap)
>> > -                sbrec_port_binding_set_encap(binding_rec, NULL);
>> > -            sbrec_port_binding_set_chassis(binding_rec, NULL);
>> > -            any_changes = true;
>> > +    struct local_binding *lbinding =
>> > +        local_binding_find(b_ctx_out->local_bindings,
pb->logical_port);
>> > +
>> > +    ovs_assert(lbinding);
>> > +
>> > +    struct shash_node *node;
>> > +    SHASH_FOR_EACH (node, &lbinding->children) {
>> > +        struct local_binding *child = node->data;
>> > +        if (child->type == BT_CONTAINER) {
>> > +            handled = consider_container_lport(child->pb, b_ctx_in,
>> b_ctx_out,
>> > +                                               qos_map);
>> > +            if (!handled) {
>> > +                return false;
>> > +            }
>> >          }
>> >      }
>> >
>> > -    if (any_changes) {
>> > -        ovsdb_idl_txn_add_comment(
>> > -            ovnsb_idl_txn,
>> > -            "ovn-controller: removing all port bindings for '%s'",
>> > -            chassis_rec->name);
>> > +    return true;
>> > +}
>> > +
>> > +/* Returns true if the port binding changes resulted in local binding
>> > + * updates, false otherwise.
>> > + */
>> > +bool
>> > +binding_handle_port_binding_changes(struct binding_ctx_in *b_ctx_in,
>> > +                                    struct binding_ctx_out *b_ctx_out,
>> > +                                    bool *changed)
>> > +{
>> > +    bool handled = true;
>> > +    *changed = false;
>> > +
>> > +    const struct sbrec_port_binding *pb;
>> > +
>> > +    /* Run the tracked port binding loop twice. One to handle deleted
>> > +     * changes. And another to handle add/update changes.
>> > +     * This will ensure correctness.
>> > +     */
>> > +    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
>> > +
>> b_ctx_in->port_binding_table) {
>> > +        if (!sbrec_port_binding_is_deleted(pb)) {
>> > +            continue;
>> > +        }
>> > +
>> > +        enum en_lport_type lport_type = get_lport_type(pb);
>> > +        if (lport_type == LP_VIF || lport_type == LP_VIRTUAL) {
>> > +            handled = handle_deleted_vif_lport(pb, lport_type,
b_ctx_in,
>> > +                                                b_ctx_out, changed);
>> > +        } else {
>> > +            handle_deleted_lport(pb, b_ctx_in, b_ctx_out);
>> > +        }
>> > +
>> > +        if (!handled) {
>> > +            break;
>> > +        }
>> >      }
>> >
>> > -    return !any_changes;
>> > +    if (!handled) {
>> > +        return false;
>> > +    }
>> > +
>> > +    struct hmap qos_map = HMAP_INITIALIZER(&qos_map);
>> > +    struct hmap *qos_map_ptr =
>> > +        sset_is_empty(b_ctx_out->egress_ifaces) ? NULL : &qos_map;
>> > +
>> > +    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
>> > +
>> b_ctx_in->port_binding_table) {
>> > +        /* Loop to handle create and update changes only. */
>> > +        if (sbrec_port_binding_is_deleted(pb)) {
>> > +            continue;
>> > +        }
>> > +
>> > +        enum en_lport_type lport_type = get_lport_type(pb);
>> > +
>> > +        struct local_datapath *ld =
>> > +            get_local_datapath(b_ctx_out->local_datapaths,
>> > +                               pb->datapath->tunnel_key);
>> > +
>> > +        switch (lport_type) {
>> > +        case LP_VIF:
>> > +        case LP_VIRTUAL:
>> > +            handled = handle_updated_vif_lport(pb, lport_type,
b_ctx_in,
>> > +                                               b_ctx_out, qos_map_ptr,
>> > +                                               changed);
>> > +            break;
>> > +
>> > +        case LP_PATCH:
>> > +        case LP_LOCALPORT:
>> > +        case LP_VTEP:
>> > +            *changed = true;
>> > +            update_local_lport_ids(b_ctx_out->local_lport_ids, pb);
>> > +            if (lport_type ==  LP_PATCH) {
>> > +                /* Add the peer datapath to the local datapaths if
it's
>> > +                    * not present yet.
>> > +                    */
>> > +                if (ld) {
>> > +                    add_local_datapath_peer_port(pb, b_ctx_in,
>> > +                                                    b_ctx_out, ld);
>> > +                }
>> > +            }
>> > +            break;
>> > +
>> > +        case LP_L2GATEWAY:
>> > +            *changed = true;
>> > +            handled = consider_l2gw_lport(pb, b_ctx_in, b_ctx_out);
>> > +            break;
>> > +
>> > +        case LP_L3GATEWAY:
>> > +            *changed = true;
>> > +            handled = consider_l3gw_lport(pb, b_ctx_in, b_ctx_out);
>> > +            break;
>> > +
>> > +        case LP_CHASSISREDIRECT:
>> > +            *changed = true;
>> > +            handled = consider_cr_lport(pb, b_ctx_in, b_ctx_out);
>> > +            break;
>> > +
>> > +        case LP_EXTERNAL:
>> > +            *changed = true;
>> > +            handled = consider_external_lport(pb, b_ctx_in,
b_ctx_out);
>> > +            break;
>> > +
>> > +        case LP_LOCALNET: {
>> > +            *changed = true;
>> > +            consider_localnet_lport(pb, b_ctx_in, b_ctx_out,
>> qos_map_ptr);
>> > +
>> > +            struct shash bridge_mappings =
>> > +                SHASH_INITIALIZER(&bridge_mappings);
>> > +            add_ovs_bridge_mappings(b_ctx_in->ovs_table,
>> > +                                    b_ctx_in->bridge_table,
>> > +                                    &bridge_mappings);
>> > +            update_ld_localnet_port(pb, &bridge_mappings,
>> > +                                    b_ctx_out->egress_ifaces,
>> > +                                    b_ctx_out->local_datapaths);
>> > +            shash_destroy(&bridge_mappings);
>> > +            break;
>> > +        }
>> > +
>> > +        case LP_REMOTE:
>> > +        case LP_UNKNOWN:
>> > +            break;
>> > +        }
>> > +
>> > +        if (!handled) {
>> > +            break;
>> > +        }
>> > +    }
>> > +
>> > +    if (handled && qos_map_ptr && set_noop_qos(b_ctx_in->ovs_idl_txn,
>> > +                                               b_ctx_in->port_table,
>> > +                                               b_ctx_in->qos_table,
>> > +
>> b_ctx_out->egress_ifaces)) {
>> > +        const char *entry;
>> > +        SSET_FOR_EACH (entry, b_ctx_out->egress_ifaces) {
>> > +            setup_qos(entry, &qos_map);
>> > +        }
>> > +    }
>> > +
>> > +    destroy_qos_map(&qos_map);
>> > +    return handled;
>> >  }
>> > diff --git a/controller/binding.h b/controller/binding.h
>> > index 9affc9a96..f7ace6faf 100644
>> > --- a/controller/binding.h
>> > +++ b/controller/binding.h
>> > @@ -57,6 +57,7 @@ struct binding_ctx_out {
>> >      struct sset *local_lports;
>> >      struct sset *local_lport_ids;
>> >      struct sset *egress_ifaces;
>> > +    struct smap *local_iface_ids;
>> >  };
>> >
>> >  void binding_register_ovs_idl(struct ovsdb_idl *);
>> > @@ -64,9 +65,13 @@ void binding_run(struct binding_ctx_in *, struct
>> binding_ctx_out *);
>> >  bool binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn,
>> >                       const struct sbrec_port_binding_table *,
>> >                       const struct sbrec_chassis *);
>> > -bool binding_evaluate_port_binding_changes(struct binding_ctx_in *,
>> > -                                           struct binding_ctx_out *);
>> >
>> >  void local_bindings_init(struct shash *local_bindings);
>> >  void local_bindings_destroy(struct shash *local_bindings);
>> > +bool binding_handle_ovs_interface_changes(struct binding_ctx_in *,
>> > +                                          struct binding_ctx_out *,
>> > +                                          bool *changed);
>> > +bool binding_handle_port_binding_changes(struct binding_ctx_in *,
>> > +                                         struct binding_ctx_out *,
>> > +                                         bool *changed);
>> >  #endif /* controller/binding.h */
>> > diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
>> > index 6b759966b..08b074415 100644
>> > --- a/controller/ovn-controller.c
>> > +++ b/controller/ovn-controller.c
>> > @@ -753,6 +753,7 @@ enum sb_engine_node {
>> >      OVS_NODE(open_vswitch, "open_vswitch") \
>> >      OVS_NODE(bridge, "bridge") \
>> >      OVS_NODE(port, "port") \
>> > +    OVS_NODE(interface, "interface") \
>> >      OVS_NODE(qos, "qos")
>> >
>> >  enum ovs_engine_node {
>> > @@ -974,6 +975,7 @@ struct ed_type_runtime_data {
>> >      struct sset active_tunnels;
>> >
>> >      struct sset egress_ifaces;
>> > +    struct smap local_iface_ids;
>> >  };
>> >
>> >  static void *
>> > @@ -987,6 +989,7 @@ en_runtime_data_init(struct engine_node *node
>> OVS_UNUSED,
>> >      sset_init(&data->local_lport_ids);
>> >      sset_init(&data->active_tunnels);
>> >      sset_init(&data->egress_ifaces);
>> > +    smap_init(&data->local_iface_ids);
>> >      local_bindings_init(&data->local_bindings);
>> >      return data;
>> >  }
>> > @@ -1000,6 +1003,7 @@ en_runtime_data_cleanup(void *data)
>> >      sset_destroy(&rt_data->local_lport_ids);
>> >      sset_destroy(&rt_data->active_tunnels);
>> >      sset_destroy(&rt_data->egress_ifaces);
>> > +    smap_destroy(&rt_data->local_iface_ids);
>> >      struct local_datapath *cur_node, *next_node;
>> >      HMAP_FOR_EACH_SAFE (cur_node, next_node, hmap_node,
>> >                          &rt_data->local_datapaths) {
>> > @@ -1041,6 +1045,10 @@ init_binding_ctx(struct engine_node *node,
>> >          (struct ovsrec_port_table *)EN_OVSDB_GET(
>> >              engine_get_input("OVS_port", node));
>> >
>> > +    struct ovsrec_interface_table *iface_table =
>> > +        (struct ovsrec_interface_table *)EN_OVSDB_GET(
>> > +            engine_get_input("OVS_interface", node));
>> > +
>> >      struct ovsrec_qos_table *qos_table =
>> >          (struct ovsrec_qos_table *)EN_OVSDB_GET(
>> >              engine_get_input("OVS_qos", node));
>> > @@ -1070,6 +1078,7 @@ init_binding_ctx(struct engine_node *node,
>> >      b_ctx_in->sbrec_port_binding_by_datapath =
>> sbrec_port_binding_by_datapath;
>> >      b_ctx_in->sbrec_port_binding_by_name = sbrec_port_binding_by_name;
>> >      b_ctx_in->port_table = port_table;
>> > +    b_ctx_in->iface_table = iface_table;
>> >      b_ctx_in->qos_table = qos_table;
>> >      b_ctx_in->port_binding_table = pb_table;
>> >      b_ctx_in->br_int = br_int;
>> > @@ -1083,6 +1092,7 @@ init_binding_ctx(struct engine_node *node,
>> >      b_ctx_out->local_lport_ids = &rt_data->local_lport_ids;
>> >      b_ctx_out->egress_ifaces = &rt_data->egress_ifaces;
>> >      b_ctx_out->local_bindings = &rt_data->local_bindings;
>> > +    b_ctx_out->local_iface_ids = &rt_data->local_iface_ids;
>> >  }
>> >
>> >  static void
>> > @@ -1111,10 +1121,12 @@ en_runtime_data_run(struct engine_node *node,
>> void *data)
>> >          sset_destroy(local_lport_ids);
>> >          sset_destroy(active_tunnels);
>> >          sset_destroy(&rt_data->egress_ifaces);
>> > +        smap_destroy(&rt_data->local_iface_ids);
>> >          sset_init(local_lports);
>> >          sset_init(local_lport_ids);
>> >          sset_init(active_tunnels);
>> >          sset_init(&rt_data->egress_ifaces);
>> > +        smap_init(&rt_data->local_iface_ids);
>> >          local_bindings_init(&rt_data->local_bindings);
>> >      }
>> >
>> > @@ -1140,6 +1152,34 @@ en_runtime_data_run(struct engine_node *node,
void
>> *data)
>> >      engine_set_node_state(node, EN_UPDATED);
>> >  }
>> >
>> > +static bool
>> > +runtime_data_ovs_interface_handler(struct engine_node *node, void
*data)
>> > +{
>> > +    struct ed_type_runtime_data *rt_data = data;
>> > +    struct binding_ctx_in b_ctx_in;
>> > +    struct binding_ctx_out b_ctx_out;
>> > +    init_binding_ctx(node, rt_data, &b_ctx_in, &b_ctx_out);
>> > +
>> > +    bool changed = false;
>> > +    if (!binding_handle_ovs_interface_changes(&b_ctx_in, &b_ctx_out,
>> > +                                              &changed)) {
>> > +        return false;
>> > +    }
>> > +
>> > +    if (changed) {
>> > +        engine_set_node_state(node, EN_UPDATED);
>> > +    }
>> > +
>> > +    return true;
>> > +}
>> > +
>> > +static bool
>> > +runtime_data_noop_handler(struct engine_node *node OVS_UNUSED,
>> > +                          void *data OVS_UNUSED)
>> > +{
>> > +    return true;
>> > +}
>> > +
>> >  static bool
>> >  runtime_data_sb_port_binding_handler(struct engine_node *node, void
>> *data)
>> >  {
>> > @@ -1147,11 +1187,21 @@ runtime_data_sb_port_binding_handler(struct
>> engine_node *node, void *data)
>> >      struct binding_ctx_in b_ctx_in;
>> >      struct binding_ctx_out b_ctx_out;
>> >      init_binding_ctx(node, rt_data, &b_ctx_in, &b_ctx_out);
>> > +    if (!b_ctx_in.chassis_rec) {
>> > +        return false;
>> > +    }
>> > +
>> > +    bool changed = false;
>> > +    if (!binding_handle_port_binding_changes(&b_ctx_in, &b_ctx_out,
>> > +                                             &changed)) {
>> > +        return false;
>> > +    }
>> >
>> > -    bool changed = binding_evaluate_port_binding_changes(&b_ctx_in,
>> > -                                                         &b_ctx_out);
>> > +    if (changed) {
>> > +        engine_set_node_state(node, EN_UPDATED);
>> > +    }
>> >
>> > -    return !changed;
>> > +    return true;
>> >  }
>> >
>> >  /* Connection tracking zones. */
>> > @@ -1894,7 +1944,10 @@ main(int argc, char *argv[])
>> >
>> >      engine_add_input(&en_runtime_data, &en_ovs_open_vswitch, NULL);
>> >      engine_add_input(&en_runtime_data, &en_ovs_bridge, NULL);
>> > -    engine_add_input(&en_runtime_data, &en_ovs_port, NULL);
>> > +    engine_add_input(&en_runtime_data, &en_ovs_port,
>> > +                     runtime_data_noop_handler);
>> > +    engine_add_input(&en_runtime_data, &en_ovs_interface,
>> > +                     runtime_data_ovs_interface_handler);
>> >      engine_add_input(&en_runtime_data, &en_ovs_qos, NULL);
>> >
>> >      engine_add_input(&en_runtime_data, &en_sb_chassis, NULL);
>> > diff --git a/tests/ovn-performance.at b/tests/ovn-performance.at
>> > index a8a15f8fe..5dfc6f7ca 100644
>> > --- a/tests/ovn-performance.at
>> > +++ b/tests/ovn-performance.at
>> > @@ -239,6 +239,19 @@ for i in 1 2; do
>> >      ovn_attach n1 br-phys 192.168.0.$i
>> >  done
>> >
>> > +# Wait for the tunnel ports to be created and up.
>> > +# Otherwise this may affect the lflow_run count.
>> > +
>> > +OVS_WAIT_UNTIL([
>> > +    test $(as hv1 ovs-vsctl list interface ovn-hv2-0 | \
>> > +grep tunnel_egress_iface_carrier=up | wc -l) -eq 1
>> > +])
>> > +
>> > +OVS_WAIT_UNTIL([
>> > +    test $(as hv2 ovs-vsctl list interface ovn-hv1-0 | \
>> > +grep tunnel_egress_iface_carrier=up | wc -l) -eq 1
>> > +])
>> > +
>> >  # Add router lr1
>> >  OVN_CONTROLLER_EXPECT_HIT(
>> >      [hv1 hv2], [lflow_run],
>> > --
>> > 2.26.2
>> >
>> > _______________________________________________
>> > dev mailing list
>> > dev@openvswitch.org
>> > https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>> _______________________________________________
>> dev mailing list
>> dev@openvswitch.org
>> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>>
Numan Siddique May 22, 2020, 8:51 a.m. UTC | #4
On Fri, May 22, 2020 at 1:17 AM Han Zhou <hzhou@ovn.org> wrote:

> On Thu, May 21, 2020 at 3:49 AM Numan Siddique <numans@ovn.org> wrote:
> >
> >
> >
> > On Thu, May 21, 2020 at 6:57 AM Han Zhou <hzhou@ovn.org> wrote:
> >>
> >> Hi Numan, please see my comments below.
> >
> >
> > Thanks for the review. Please see below for a few comments.
> >
> >
> >>
> >>
> >> On Wed, May 20, 2020 at 12:41 PM <numans@ovn.org> wrote:
> >> >
> >> > From: Numan Siddique <numans@ovn.org>
> >> >
> >> > This patch handles SB port binding changes and OVS interface changes
> >> > incrementally in the runtime_data stage which otherwise would have
> >> > resulted in calls to binding_run().
> >> >
> >> > Prior to this patch, port binding changes were handled differently.
> >> > The changes were only evaluated to see if they need full recompute
> >> > or they can be ignored.
> >> >
> >> > With this patch, the runtime data is updated with the changes (both
> >> > SB port binding and OVS interface) and ports are claimed/released in
> >> > the handlers itself, avoiding the need to trigger recompute of runtime
> >> > data stage.
> >> >
> >> > Acked-by: Mark Michelson <mmichels@redhat.com>
> >> > Signed-off-by: Numan Siddique <numans@ovn.org>
> >> > ---
> >> >  controller/binding.c        | 906
> +++++++++++++++++++++++++++++++-----
> >> >  controller/binding.h        |   9 +-
> >> >  controller/ovn-controller.c |  61 ++-
> >> >  tests/ovn-performance.at    |  13 +
> >> >  4 files changed, 855 insertions(+), 134 deletions(-)
> >> >
> >> > diff --git a/controller/binding.c b/controller/binding.c
> >> > index 2997fcae8..d5e8e4773 100644
> >> > --- a/controller/binding.c
> >> > +++ b/controller/binding.c
> >> > @@ -360,17 +360,6 @@ destroy_qos_map(struct hmap *qos_map)
> >> >      hmap_destroy(qos_map);
> >> >  }
> >> >
> >> > -static void
> >> > -update_local_lport_ids(struct sset *local_lport_ids,
> >> > -                       const struct sbrec_port_binding *binding_rec)
> >> > -{
> >> > -        char buf[16];
> >> > -        snprintf(buf, sizeof(buf), "%"PRId64"_%"PRId64,
> >> > -                 binding_rec->datapath->tunnel_key,
> >> > -                 binding_rec->tunnel_key);
> >> > -        sset_add(local_lport_ids, buf);
> >> > -}
> >> > -
> >> >  /*
> >> >   * Get the encap from the chassis for this port. The interface
> >> >   * may have an external_ids:encap-ip=<encap-ip> set; if so we
> >> > @@ -449,10 +438,10 @@ is_network_plugged(const struct
> sbrec_port_binding
> >> *binding_rec,
> >> >  }
> >> >
> >> >  static void
> >> > -consider_localnet_port(const struct sbrec_port_binding *binding_rec,
> >> > -                       struct shash *bridge_mappings,
> >> > -                       struct sset *egress_ifaces,
> >> > -                       struct hmap *local_datapaths)
> >> > +update_ld_localnet_port(const struct sbrec_port_binding *binding_rec,
> >> > +                        struct shash *bridge_mappings,
> >> > +                        struct sset *egress_ifaces,
> >> > +                        struct hmap *local_datapaths)
> >> >  {
> >> >      /* Ignore localnet ports for unplugged networks. */
> >> >      if (!is_network_plugged(binding_rec, bridge_mappings)) {
> >> > @@ -482,6 +471,28 @@ consider_localnet_port(const struct
> >> sbrec_port_binding *binding_rec,
> >> >      ld->localnet_port = binding_rec;
> >> >  }
> >> >
> >> > +static void
> >> > +update_local_lport_ids(struct sset *local_lport_ids,
> >> > +                       const struct sbrec_port_binding *binding_rec)
> >> > +{
> >> > +        char buf[16];
> >> > +        snprintf(buf, sizeof(buf), "%"PRId64"_%"PRId64,
> >> > +                 binding_rec->datapath->tunnel_key,
> >> > +                 binding_rec->tunnel_key);
> >> > +        sset_add(local_lport_ids, buf);
> >> > +}
> >> > +
> >> > +static void
> >> > +remove_local_lport_ids(const struct sbrec_port_binding *binding_rec,
> >> > +                       struct sset *local_lport_ids)
> >> > +{
> >> > +        char buf[16];
> >> > +        snprintf(buf, sizeof(buf), "%"PRId64"_%"PRId64,
> >> > +                 binding_rec->datapath->tunnel_key,
> >> > +                 binding_rec->tunnel_key);
> >> > +        sset_find_and_delete(local_lport_ids, buf);
> >> > +}
> >> > +
> >> >  /* Local bindings. binding.c module binds the logical port
> (represented
> >> by
> >> >   * Port_Binding rows) and sets the 'chassis' column when it is sees
> the
> >> >   * OVS interface row (of type "" or "internal") with the
> >> > @@ -599,6 +610,14 @@ local_bindings_destroy(struct shash
> *local_bindings)
> >> >      shash_destroy(local_bindings);
> >> >  }
> >> >
> >> > +static
> >> > +void local_binding_delete(struct shash *local_bindings,
> >> > +                          struct local_binding *lbinding)
> >> > +{
> >> > +    shash_find_and_delete(local_bindings, lbinding->name);
> >> > +    local_binding_destroy(lbinding);
> >> > +}
> >> > +
> >> >  static void
> >> >  local_binding_add_child(struct local_binding *lbinding,
> >> >                          struct local_binding *child)
> >> > @@ -625,6 +644,7 @@ is_lport_container(const struct sbrec_port_binding
> >> *pb)
> >> >      return !pb->type[0] && pb->parent_port && pb->parent_port[0];
> >> >  }
> >> >
> >> > +/* Corresponds to each Port_Binding.type. */
> >> >  enum en_lport_type {
> >> >      LP_UNKNOWN,
> >> >      LP_VIF,
> >> > @@ -670,12 +690,20 @@ get_lport_type(const struct sbrec_port_binding
> *pb)
> >> >      return LP_UNKNOWN;
> >> >  }
> >> >
> >> > -static void
> >> > +/* Returns false if lport is not claimed due to 'sb_readonly'.
> >> > + * Returns true otherwise.
> >> > + */
> >> > +static bool
> >> >  claim_lport(const struct sbrec_port_binding *pb,
> >> >              const struct sbrec_chassis *chassis_rec,
> >> > -            const struct ovsrec_interface *iface_rec)
> >> > +            const struct ovsrec_interface *iface_rec,
> >> > +            bool sb_readonly)
> >> >  {
> >> >      if (pb->chassis != chassis_rec) {
> >> > +        if (sb_readonly) {
> >> > +            return false;
> >> > +        }
> >> > +
> >> >          if (pb->chassis) {
> >> >              VLOG_INFO("Changing chassis for lport %s from %s to %s.",
> >> >                      pb->logical_port, pb->chassis->name,
> >> > @@ -694,26 +722,48 @@ claim_lport(const struct sbrec_port_binding *pb,
> >> >      struct sbrec_encap *encap_rec =
> >> >          sbrec_get_port_encap(chassis_rec, iface_rec);
> >> >      if (encap_rec && pb->encap != encap_rec) {
> >> > +        if (sb_readonly) {
> >> > +            return false;
> >> > +        }
> >> >          sbrec_port_binding_set_encap(pb, encap_rec);
> >> >      }
> >> > +
> >> > +    return true;
> >> >  }
> >> >
> >> > -static void
> >> > -release_lport(const struct sbrec_port_binding *pb)
> >> > +/* Returns false if lport is not released due to 'sb_readonly'.
> >> > + * Returns true otherwise.
> >> > + */
> >> > +static bool
> >> > +release_lport(const struct sbrec_port_binding *pb, bool sb_readonly)
> >> >  {
> >> >      if (!pb) {
> >> > -        return;
> >> > +        return true;
> >> >      }
> >> >
> >> > -    VLOG_INFO("Releasing lport %s from this chassis.",
> pb->logical_port);
> >> >      if (pb->encap) {
> >> > +        if (sb_readonly) {
> >> > +            return false;
> >> > +        }
> >> >          sbrec_port_binding_set_encap(pb, NULL);
> >> >      }
> >> > -    sbrec_port_binding_set_chassis(pb, NULL);
> >> > +
> >> > +    if (pb->chassis) {
> >> > +        if (sb_readonly) {
> >> > +            return false;
> >> > +        }
> >> > +        sbrec_port_binding_set_chassis(pb, NULL);
> >> > +    }
> >> >
> >> >      if (pb->virtual_parent) {
> >> > +        if (sb_readonly) {
> >> > +            return false;
> >> > +        }
> >> >          sbrec_port_binding_set_virtual_parent(pb, NULL);
> >> >      }
> >> > +
> >> > +    VLOG_INFO("Releasing lport %s from this chassis.",
> pb->logical_port);
> >> > +    return true;
> >> >  }
> >> >
> >> >  static bool
> >> > @@ -738,7 +788,41 @@ can_bind_on_this_chassis(const struct
> sbrec_chassis
> >> *chassis_rec,
> >> >             || !strcmp(requested_chassis, chassis_rec->hostname);
> >> >  }
> >> >
> >> > -static void
> >> > +static bool
> >> > +release_local_binding_children(const struct sbrec_chassis
> *chassis_rec,
> >> > +                               struct local_binding *lbinding,
> >> > +                               bool sb_readonly)
> >> > +{
> >> > +    struct shash_node *node;
> >> > +    SHASH_FOR_EACH (node, &lbinding->children) {
> >> > +        struct local_binding *l = node->data;
> >> > +        if (is_lbinding_this_chassis(l, chassis_rec)) {
> >> > +            if (!release_lport(l->pb, sb_readonly)) {
> >> > +                return false;
> >> > +            }
> >> > +        }
> >> > +    }
> >> > +
> >> > +    return true;
> >> > +}
> >> > +
> >> > +static bool
> >> > +release_local_binding(const struct sbrec_chassis *chassis_rec,
> >> > +                      struct local_binding *lbinding, bool
> sb_readonly)
> >> > +{
> >> > +    if (!release_local_binding_children(chassis_rec, lbinding,
> >> > +                                        sb_readonly)) {
> >> > +        return false;
> >> > +    }
> >> > +
> >> > +    if (is_lbinding_this_chassis(lbinding, chassis_rec)) {
> >> > +        return release_lport(lbinding->pb, sb_readonly);
> >> > +    }
> >> > +
> >> > +    return true;
> >> > +}
> >> > +
> >> > +static bool
> >> >  consider_vif_lport_(const struct sbrec_port_binding *pb,
> >> >                      bool can_bind, const char *vif_chassis,
> >> >                      struct binding_ctx_in *b_ctx_in,
> >> > @@ -750,7 +834,10 @@ consider_vif_lport_(const struct
> sbrec_port_binding
> >> *pb,
> >> >      if (lbinding_set) {
> >> >          if (can_bind) {
> >> >              /* We can claim the lport. */
> >> > -            claim_lport(pb, b_ctx_in->chassis_rec, lbinding->iface);
> >> > +            if (!claim_lport(pb, b_ctx_in->chassis_rec,
> lbinding->iface,
> >> > +                             !b_ctx_in->ovnsb_idl_txn)){
> >> > +                return false;
> >> > +            }
> >> >
> >> >
>  add_local_datapath(b_ctx_in->sbrec_datapath_binding_by_key,
> >> >                              b_ctx_in->sbrec_port_binding_by_datapath,
> >> > @@ -775,13 +862,14 @@ consider_vif_lport_(const struct
> sbrec_port_binding
> >> *pb,
> >> >      if (pb->chassis == b_ctx_in->chassis_rec) {
> >> >          /* Release the lport if there is no lbinding. */
> >> >          if (!lbinding_set || !can_bind) {
> >> > -            release_lport(pb);
> >> > +            return release_lport(pb, !b_ctx_in->ovnsb_idl_txn);
> >> >          }
> >> >      }
> >> >
> >> > +    return true;
> >> >  }
> >> >
> >> > -static void
> >> > +static bool
> >> >  consider_vif_lport(const struct sbrec_port_binding *pb,
> >> >                     struct binding_ctx_in *b_ctx_in,
> >> >                     struct binding_ctx_out *b_ctx_out,
> >> > @@ -801,11 +889,11 @@ consider_vif_lport(const struct
> sbrec_port_binding
> >> *pb,
> >> >          lbinding->pb = pb;
> >> >      }
> >> >
> >> > -    consider_vif_lport_(pb, can_bind, vif_chassis, b_ctx_in,
> >> > -                        b_ctx_out, lbinding, qos_map);
> >> > +    return consider_vif_lport_(pb, can_bind, vif_chassis, b_ctx_in,
> >> > +                               b_ctx_out, lbinding, qos_map);
> >> >  }
> >> >
> >> > -static void
> >> > +static bool
> >> >  consider_container_lport(const struct sbrec_port_binding *pb,
> >> >                           struct binding_ctx_in *b_ctx_in,
> >> >                           struct binding_ctx_out *b_ctx_out,
> >> > @@ -815,7 +903,39 @@ consider_container_lport(const struct
> >> sbrec_port_binding *pb,
> >> >      parent_lbinding = local_binding_find(b_ctx_out->local_bindings,
> >> >                                           pb->parent_port);
> >> >
> >> > -    if (parent_lbinding && !parent_lbinding->pb) {
> >> > +    if (!parent_lbinding) {
> >> > +        /* There is no local_binding for parent port. Create it
> >> > +         * without OVS interface row. This is the only exception
> >> > +         * for creating the 'struct local_binding' object without
> >> > +         * corresponding OVS interface row.
> >> > +         *
> >> > +         * This is required for the following reasons:
> >> > +         *   - If a logical port P1 is created and then
> >> > +         *     few container ports - C1, C2, .. are created first by
> CMS.
> >> > +         *   - And later when OVS interface row  is created for P1,
> then
> >> > +         *     we want the these container ports also be claimed by
> the
> >> > +         *     chassis.
> >> > +         * */
> >>
> >> It seems the assumption is, for all container PBs, their parent PBs
> should
> >> always have local_binding created, no matter if they are bound to
> current
> >> chassis.
> >
> >
> > That's right.
> >
> >>
> >> However, at least some of the code in this patch would violate this
> >> assumption, e.g. handle_iface_delete() will call local_binding_delete()
> and
> >> delete the parent binding and all the children container bindings.
> >
> >
> > I missed this part. I'll handle it in next version.
> >
> >>
> >> It is also better to document this clearly in the comment of struct
> >> local_binding for what should be expected in the local_bindings.
> >
> >
> > I've added comments for the local_bindings struct. But it looks like it's
> not
> > sufficient. I'll add more .
> >
> >> My
> >> understanding is, for each binding_run() or change_handler execution,
> the
> >> expected items left in it is:
> >> 1) For each interface that has iface-id configured.
> >
> > Yes.
> >
> >>
> >> 2) For each container PBs and their parent PBs, no matter if they are
> bound
> >> to this chassis.
> >
> >
> > Yes.
> >
> >>
> >> 3) For some of the non VIF PBs ...
> >
> >
> > Only for the "virtual" PB if it's parent is bound.
> >
> >>
> >> Is this correct?
> >>
> >> > +        parent_lbinding = local_binding_create(pb->parent_port, NULL,
> >> NULL,
> >> > +                                               BT_VIF);
> >> > +        local_binding_add(b_ctx_out->local_bindings,
> parent_lbinding);
> >> > +    }
> >> > +
> >> > +    struct local_binding *container_lbinding =
> >> > +        local_binding_find_child(parent_lbinding, pb->logical_port);
> >> > +
> >> > +    if (!container_lbinding) {
> >> > +        container_lbinding = local_binding_create(pb->logical_port,
> >> > +
>  parent_lbinding->iface,
> >> > +                                                  pb, BT_CONTAINER);
> >> > +        local_binding_add_child(parent_lbinding, container_lbinding);
> >> > +    } else {
> >> > +        ovs_assert(container_lbinding->type == BT_CONTAINER);
> >> > +        container_lbinding->pb = pb;
> >> > +        container_lbinding->iface = parent_lbinding->iface;
> >> > +    }
> >> > +
> >> > +    if (!parent_lbinding->pb) {
> >> >          parent_lbinding->pb = lport_lookup_by_name(
> >> >              b_ctx_in->sbrec_port_binding_by_name, pb->parent_port);
> >> >
> >> > @@ -824,37 +944,28 @@ consider_container_lport(const struct
> >> sbrec_port_binding *pb,
> >> >               * So call consider_vif_lport() to process it first. */
> >> >              consider_vif_lport(parent_lbinding->pb, b_ctx_in,
> b_ctx_out,
> >> >                                 parent_lbinding, qos_map);
> >> > -        }
> >> > -    }
> >> > +        } else {
> >> > +            /* The parent lport doesn't exist. Call release_lport()
> to
> >> > +             * release the container lport, if it was bound earlier.
> */
> >> > +            if (is_lbinding_this_chassis(container_lbinding,
> >> > +                                         b_ctx_in->chassis_rec)) {
> >> > +               return release_lport(pb, !b_ctx_in->ovnsb_idl_txn);
> >> > +            }
> >> >
> >> > -    if (!parent_lbinding || !parent_lbinding->pb) {
> >> > -        /* Call release_lport, to release the container lport, if
> >> > -         * it was bound earlier. */
> >> > -        if (pb->chassis == b_ctx_in->chassis_rec) {
> >> > -            release_lport(pb);
> >> > +            return true;
> >> >          }
> >> > -        return;
> >> >      }
> >> >
> >> > -    struct local_binding *container_lbinding =
> >> > -        local_binding_find_child(parent_lbinding, pb->logical_port);
> >> > -    ovs_assert(!container_lbinding);
> >> > -
> >> > -    container_lbinding = local_binding_create(pb->logical_port,
> >> > -                                              parent_lbinding->iface,
> >> > -                                              pb, BT_CONTAINER);
> >> > -    local_binding_add_child(parent_lbinding, container_lbinding);
> >> > -
> >> >      const char *vif_chassis = smap_get(&parent_lbinding->pb->options,
> >> >                                         "requested-chassis");
> >> >      bool can_bind = can_bind_on_this_chassis(b_ctx_in->chassis_rec,
> >> >                                               vif_chassis);
> >> >
> >> > -    consider_vif_lport_(pb, can_bind, vif_chassis, b_ctx_in,
> b_ctx_out,
> >> > -                        container_lbinding, qos_map);
> >> > +    return consider_vif_lport_(pb, can_bind, vif_chassis, b_ctx_in,
> >> b_ctx_out,
> >> > +                               container_lbinding, qos_map);
> >> >  }
> >> >
> >> > -static void
> >> > +static bool
> >> >  consider_virtual_lport(const struct sbrec_port_binding *pb,
> >> >                         struct binding_ctx_in *b_ctx_in,
> >> >                         struct binding_ctx_out *b_ctx_out,
> >> > @@ -881,11 +992,16 @@ consider_virtual_lport(const struct
> >> sbrec_port_binding *pb,
> >> >      if (is_lbinding_this_chassis(parent_lbinding,
> >> b_ctx_in->chassis_rec)) {
> >> >          virtual_lbinding =
> >> >              local_binding_find_child(parent_lbinding,
> pb->logical_port);
> >> > -        ovs_assert(!virtual_lbinding);
> >> > -        virtual_lbinding = local_binding_create(pb->logical_port,
> >> > -
>  parent_lbinding->iface,
> >> > -                                                pb, BT_VIRTUAL);
> >> > -        local_binding_add_child(parent_lbinding, virtual_lbinding);
> >> > +        if (!virtual_lbinding) {
> >> > +            virtual_lbinding = local_binding_create(pb->logical_port,
> >> > +
> >>  parent_lbinding->iface,
> >> > +                                                    pb, BT_VIRTUAL);
> >> > +            local_binding_add_child(parent_lbinding,
> virtual_lbinding);
> >> > +        } else {
> >> > +            ovs_assert(virtual_lbinding->type == BT_VIRTUAL);
> >> > +            virtual_lbinding->pb = pb;
> >> > +            virtual_lbinding->iface = parent_lbinding->iface;
> >> > +        }
> >> >      }
> >> >
> >> >      return consider_vif_lport_(pb, true, NULL, b_ctx_in, b_ctx_out,
> >> > @@ -895,14 +1011,13 @@ consider_virtual_lport(const struct
> >> sbrec_port_binding *pb,
> >> >  /* Considers either claiming the lport or releasing the lport
> >> >   * for non VIF lports.
> >> >   */
> >> > -static void
> >> > +static bool
> >> >  consider_nonvif_lport_(const struct sbrec_port_binding *pb,
> >> >                         bool our_chassis,
> >> >                         bool has_local_l3gateway,
> >> >                         struct binding_ctx_in *b_ctx_in,
> >> >                         struct binding_ctx_out *b_ctx_out)
> >> >  {
> >> > -    ovs_assert(b_ctx_in->ovnsb_idl_txn);
> >> >      if (our_chassis) {
> >> >          sset_add(b_ctx_out->local_lports, pb->logical_port);
> >> >          add_local_datapath(b_ctx_in->sbrec_datapath_binding_by_key,
> >> > @@ -912,13 +1027,16 @@ consider_nonvif_lport_(const struct
> >> sbrec_port_binding *pb,
> >> >                             b_ctx_out->local_datapaths);
> >> >
> >> >          update_local_lport_ids(b_ctx_out->local_lport_ids, pb);
> >> > -        claim_lport(pb, b_ctx_in->chassis_rec, NULL);
> >> > +        return claim_lport(pb, b_ctx_in->chassis_rec, NULL,
> >> > +                           !b_ctx_in->ovnsb_idl_txn);
> >> >      } else if (pb->chassis == b_ctx_in->chassis_rec) {
> >> > -        release_lport(pb);
> >> > +        return release_lport(pb, !b_ctx_in->ovnsb_idl_txn);
> >> >      }
> >> > +
> >> > +    return true;
> >> >  }
> >> >
> >> > -static void
> >> > +static bool
> >> >  consider_l2gw_lport(const struct sbrec_port_binding *pb,
> >> >                      struct binding_ctx_in *b_ctx_in,
> >> >                      struct binding_ctx_out *b_ctx_out)
> >> > @@ -927,10 +1045,10 @@ consider_l2gw_lport(const struct
> >> sbrec_port_binding *pb,
> >> >      bool our_chassis = chassis_id && !strcmp(chassis_id,
> >> >
> >> b_ctx_in->chassis_rec->name);
> >> >
> >> > -    consider_nonvif_lport_(pb, our_chassis, false, b_ctx_in,
> b_ctx_out);
> >> > +    return consider_nonvif_lport_(pb, our_chassis, false, b_ctx_in,
> >> b_ctx_out);
> >> >  }
> >> >
> >> > -static void
> >> > +static bool
> >> >  consider_l3gw_lport(const struct sbrec_port_binding *pb,
> >> >                      struct binding_ctx_in *b_ctx_in,
> >> >                      struct binding_ctx_out *b_ctx_out)
> >> > @@ -939,7 +1057,7 @@ consider_l3gw_lport(const struct
> sbrec_port_binding
> >> *pb,
> >> >      bool our_chassis = chassis_id && !strcmp(chassis_id,
> >> >
> >> b_ctx_in->chassis_rec->name);
> >> >
> >> > -    consider_nonvif_lport_(pb, our_chassis, true, b_ctx_in,
> b_ctx_out);
> >> > +    return consider_nonvif_lport_(pb, our_chassis, true, b_ctx_in,
> >> b_ctx_out);
> >> >  }
> >> >
> >> >  static void
> >> > @@ -958,7 +1076,7 @@ consider_localnet_lport(const struct
> >> sbrec_port_binding *pb,
> >> >      update_local_lport_ids(b_ctx_out->local_lport_ids, pb);
> >> >  }
> >> >
> >> > -static void
> >> > +static bool
> >> >  consider_ha_lport(const struct sbrec_port_binding *pb,
> >> >                    struct binding_ctx_in *b_ctx_in,
> >> >                    struct binding_ctx_out *b_ctx_out)
> >> > @@ -986,18 +1104,18 @@ consider_ha_lport(const struct
> sbrec_port_binding
> >> *pb,
> >> >                             b_ctx_out->local_datapaths);
> >> >      }
> >> >
> >> > -    consider_nonvif_lport_(pb, our_chassis, false, b_ctx_in,
> b_ctx_out);
> >> > +    return consider_nonvif_lport_(pb, our_chassis, false, b_ctx_in,
> >> b_ctx_out);
> >> >  }
> >> >
> >> > -static void
> >> > +static bool
> >> >  consider_cr_lport(const struct sbrec_port_binding *pb,
> >> >                    struct binding_ctx_in *b_ctx_in,
> >> >                    struct binding_ctx_out *b_ctx_out)
> >> >  {
> >> > -    consider_ha_lport(pb, b_ctx_in, b_ctx_out);
> >> > +    return consider_ha_lport(pb, b_ctx_in, b_ctx_out);
> >> >  }
> >> >
> >> > -static void
> >> > +static bool
> >> >  consider_external_lport(const struct sbrec_port_binding *pb,
> >> >                          struct binding_ctx_in *b_ctx_in,
> >> >                          struct binding_ctx_out *b_ctx_out)
> >> > @@ -1050,6 +1168,8 @@ build_local_bindings(struct binding_ctx_in
> >> *b_ctx_in,
> >> >                  }
> >> >
> >> >                  sset_add(b_ctx_out->local_lports, iface_id);
> >> > +                smap_replace(b_ctx_out->local_iface_ids,
> iface_rec->name,
> >> > +                             iface_id);
> >> >              }
> >> >
> >> >              /* Check if this is a tunnel interface. */
> >> > @@ -1165,9 +1285,9 @@ binding_run(struct binding_ctx_in *b_ctx_in,
> struct
> >> binding_ctx_out *b_ctx_out)
> >> >       * corresponding local datapath accordingly. */
> >> >      struct localnet_lport *lnet_lport;
> >> >      LIST_FOR_EACH_POP (lnet_lport, list_node, &localnet_lports) {
> >> > -        consider_localnet_port(lnet_lport->pb, &bridge_mappings,
> >> > -                               b_ctx_out->egress_ifaces,
> >> > -                               b_ctx_out->local_datapaths);
> >> > +        update_ld_localnet_port(lnet_lport->pb, &bridge_mappings,
> >> > +                                b_ctx_out->egress_ifaces,
> >> > +                                b_ctx_out->local_datapaths);
> >> >          free(lnet_lport);
> >> >      }
> >> >
> >> > @@ -1185,95 +1305,625 @@ binding_run(struct binding_ctx_in *b_ctx_in,
> >> struct binding_ctx_out *b_ctx_out)
> >> >      destroy_qos_map(&qos_map);
> >> >  }
> >> >
> >> > -/* Returns true if port-binding changes potentially require flow
> changes
> >> on
> >> > - * the current chassis. Returns false if we are sure there is no
> impact.
> >> */
> >> > +/* Returns true if the database is all cleaned up, false if more work
> is
> >> > + * required. */
> >> >  bool
> >> > -binding_evaluate_port_binding_changes(struct binding_ctx_in
> *b_ctx_in,
> >> > -                                      struct binding_ctx_out
> *b_ctx_out)
> >> > +binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn,
> >> > +                const struct sbrec_port_binding_table
> >> *port_binding_table,
> >> > +                const struct sbrec_chassis *chassis_rec)
> >> >  {
> >> > -    if (!b_ctx_in->chassis_rec) {
> >> > +    if (!ovnsb_idl_txn) {
> >> > +        return false;
> >> > +    }
> >> > +    if (!chassis_rec) {
> >> >          return true;
> >> >      }
> >> >
> >> > -    bool changed = false;
> >> > -
> >> >      const struct sbrec_port_binding *binding_rec;
> >> > -    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (binding_rec,
> >> > -
> >> b_ctx_in->port_binding_table) {
> >> > -        /* XXX: currently OVSDB change tracking doesn't support
> getting
> >> old
> >> > -         * data when the operation is update, so if a port-binding
> moved
> >> from
> >> > -         * this chassis to another, there is no easy way to find out
> the
> >> > -         * change. To workaround this problem, we just makes sure if
> >> > -         * any port *related to* this chassis has any change, then
> >> trigger
> >> > -         * recompute.
> >> > -         *
> >> > -         * - If a regular VIF is unbound from this chassis, the local
> >> ovsdb
> >> > -         *   interface table will be updated, which will trigger
> >> recompute.
> >> > -         *
> >> > -         * - If the port is not a regular VIF, always trigger
> recompute.
> >> */
> >> > -        if (binding_rec->chassis == b_ctx_in->chassis_rec) {
> >> > -            changed = true;
> >> > +    bool any_changes = false;
> >> > +    SBREC_PORT_BINDING_TABLE_FOR_EACH (binding_rec,
> port_binding_table) {
> >> > +        if (binding_rec->chassis == chassis_rec) {
> >> > +            if (binding_rec->encap) {
> >> > +                sbrec_port_binding_set_encap(binding_rec, NULL);
> >> > +            }
> >> > +            sbrec_port_binding_set_chassis(binding_rec, NULL);
> >> > +            any_changes = true;
> >> > +        }
> >> > +    }
> >> > +
> >> > +    if (any_changes) {
> >> > +        ovsdb_idl_txn_add_comment(
> >> > +            ovnsb_idl_txn,
> >> > +            "ovn-controller: removing all port bindings for '%s'",
> >> > +            chassis_rec->name);
> >> > +    }
> >> > +
> >> > +    return !any_changes;
> >> > +}
> >> > +
> >> > +static void
> >> > +add_local_datapath_peer_port(const struct sbrec_port_binding *pb,
> >> > +                             struct binding_ctx_in *b_ctx_in,
> >> > +                             struct binding_ctx_out *b_ctx_out,
> >> > +                             struct local_datapath *ld)
> >> > +{
> >> > +    const char *peer_name = smap_get(&pb->options, "peer");
> >> > +    if (strcmp(pb->type, "patch") || !peer_name) {
> >> > +        return;
> >> > +    }
> >> > +
> >> > +    const struct sbrec_port_binding *peer;
> >> > +    peer = lport_lookup_by_name(b_ctx_in->sbrec_port_binding_by_name,
> >> > +                                peer_name);
> >> > +
> >> > +    if (!peer || !peer->datapath) {
> >> > +        return;
> >> > +    }
> >> > +
> >> > +    bool present = false;
> >> > +    for (size_t i = 0; i < ld->n_peer_ports; i++) {
> >> > +        if (ld->peer_ports[i].local == pb) {
> >> > +            present = true;
> >> >              break;
> >> >          }
> >> > +    }
> >> >
> >> > -        if (!strcmp(binding_rec->type, "remote")) {
> >> > -            continue;
> >> > +    if (!present) {
> >> > +        ld->n_peer_ports++;
> >> > +        if (ld->n_peer_ports > ld->n_allocated_peer_ports) {
> >> > +            ld->peer_ports =
> >> > +                x2nrealloc(ld->peer_ports,
> >> > +                           &ld->n_allocated_peer_ports,
> >> > +                           sizeof *ld->peer_ports);
> >> > +        }
> >> > +        ld->peer_ports[ld->n_peer_ports - 1].local = pb;
> >> > +        ld->peer_ports[ld->n_peer_ports - 1].remote = peer;
> >> > +    }
> >> > +
> >> > +    struct local_datapath *peer_ld =
> >> > +        get_local_datapath(b_ctx_out->local_datapaths,
> >> > +                           peer->datapath->tunnel_key);
> >> > +    if (!peer_ld) {
> >> > +        add_local_datapath__(b_ctx_in->sbrec_datapath_binding_by_key,
> >> > +
>  b_ctx_in->sbrec_port_binding_by_datapath,
> >> > +                             b_ctx_in->sbrec_port_binding_by_name,
> >> > +                             peer->datapath, false,
> >> > +                             1, b_ctx_out->local_datapaths);
> >> > +        return;
> >> > +    }
> >> > +
> >> > +    for (size_t i = 0; i < peer_ld->n_peer_ports; i++) {
> >> > +        if (peer_ld->peer_ports[i].local == peer) {
> >> > +            return;
> >> >          }
> >> > +    }
> >> >
> >> > -        if (strcmp(binding_rec->type, "")) {
> >> > -            changed = true;
> >> > +    peer_ld->n_peer_ports++;
> >> > +    if (peer_ld->n_peer_ports > peer_ld->n_allocated_peer_ports) {
> >> > +        peer_ld->peer_ports =
> >> > +            x2nrealloc(peer_ld->peer_ports,
> >> > +                        &peer_ld->n_allocated_peer_ports,
> >> > +                        sizeof *peer_ld->peer_ports);
> >> > +    }
> >> > +    peer_ld->peer_ports[peer_ld->n_peer_ports - 1].local = peer;
> >> > +    peer_ld->peer_ports[peer_ld->n_peer_ports - 1].remote = pb;
> >> > +}
> >> > +
> >> > +static void
> >> > +remove_local_datapath_peer_port(const struct sbrec_port_binding *pb,
> >> > +                                struct local_datapath *ld,
> >> > +                                struct hmap *local_datapaths)
> >> > +{
> >> > +    size_t i = 0;
> >> > +    for (i = 0; i < ld->n_peer_ports; i++) {
> >> > +        if (ld->peer_ports[i].local == pb) {
> >> >              break;
> >> >          }
> >> > +    }
> >> > +
> >> > +    if (i == ld->n_peer_ports) {
> >> > +        return;
> >> > +    }
> >> > +
> >> > +    const struct sbrec_port_binding *peer = ld->peer_ports[i].remote;
> >> >
> >> > -        struct local_binding *lbinding = NULL;
> >> > -        if (!binding_rec->parent_port ||
> !binding_rec->parent_port[0]) {
> >> > -            lbinding = local_binding_find(b_ctx_out->local_bindings,
> >> > -                                          binding_rec->logical_port);
> >> > +    /* Possible improvement: We can shrint the allocated peer ports
> >> > +     * if (ld->n_peer_ports < ld->n_allocated_peer_ports / 2).
> >> > +     */
> >> > +    ld->peer_ports[i].local = ld->peer_ports[ld->n_peer_ports -
> 1].local;
> >> > +    ld->peer_ports[i].remote = ld->peer_ports[ld->n_peer_ports -
> >> 1].remote;
> >> > +    ld->n_peer_ports--;
> >> > +
> >> > +    struct local_datapath *peer_ld =
> >> > +        get_local_datapath(local_datapaths,
> peer->datapath->tunnel_key);
> >> > +    if (peer_ld) {
> >> > +        remove_local_datapath_peer_port(peer, peer_ld,
> local_datapaths);
> >>
> >> Sorry that I don't understand this part. peer's peer is the "pb" that is
> >> passed in the parameter of the current function call, so this recursive
> >> call would only try to remove the "pb", which is already removed (before
> >> calling this function). So I think it is useless.
> >
> >
> > Let's say there is sw0-lr0 of sw0 connected to the peer lr0-sw0 of lr0.
> >
> > When this function is called for sw0-lr0, it removes the peer ports
> sw0-lr0 from
> > the 'sw0' ld and then it calls recursively for lr0.
> >
> > Yes you're right. It will again call for sw0-lr0 of sw0. But that would
> be a no-op as
> > the for loop in the beginning cannot find sw0-lr0  in 'sw0' ld.. Even
> though it
> > is useless are there any issues with it ? Since its recursion and it has
> proper return
> > conditions, I think it should be fine.
> >
> > Another option is duplicating the code for removing the peer.
> >
>
> OK, I thought it is useless, but it seems needed.
>
> >
> >>
> >> > +    }
> >> > +}
> >> > +
> >> > +static void
> >> > +remove_pb_from_local_datapath(const struct sbrec_port_binding *pb,
> >> > +                              const struct sbrec_chassis
> *chassis_rec,
> >> > +                              struct binding_ctx_out *b_ctx_out,
> >> > +                              struct local_datapath *ld)
> >> > +{
> >> > +    remove_local_lport_ids(pb, b_ctx_out->local_lport_ids);
> >> > +    if (!strcmp(pb->type, "patch") ||
> >> > +        !strcmp(pb->type, "l3gateway")) {
> >> > +        remove_local_datapath_peer_port(pb, ld,
> >> b_ctx_out->local_datapaths);
> >> > +    } else if (!strcmp(pb->type, "localnet")) {
> >> > +        if (ld->localnet_port &&
> !strcmp(ld->localnet_port->logical_port,
> >> > +                                         pb->logical_port)) {
> >> > +            ld->localnet_port = NULL;
> >> > +        }
> >> > +    } else if (!strcmp(pb->type, "l3gateway")) {
> >> > +        const char *chassis_id = smap_get(&pb->options,
> >> > +                                          "l3gateway-chassis");
> >> > +        if (chassis_id && !strcmp(chassis_id, chassis_rec->name)) {
> >> > +            ld->has_local_l3gateway = false;
> >> > +        }
> >> > +    }
> >>
> >> I think we need to remove ld from the local_datapaths if the current pb
> is
> >> the only reason for that ld to be exist on this chassis. Also, all the
> >> related lds connected by patch ports should be removed as well, if there
> >> are no other pbs causing them to be added to this chassis. This is the
> >> reverse of add_local_datapath__(). It doesn't seem to be easy without
> going
> >> through all local_lbindings. Maybe this can be done in
> >> binding_handle_ovs_interface_changes() and
> >> binding_handle_port_binding_changes() out of the loop.
> >>
> >
> > I left this unaddressed intentionally as I think it will complicate the
> code.
> >
> > Patch 3 of this series handles I-P for datapath and when a datapath is
> deleted,
> > it removes the datapath from local_datapaths if it is present.
> > I think this should be Ok as we will eventually free it. If we really
> want to address this,
> > I'm fine with it but I'd prefer a follow up patch to do this.
> >
> OK, maybe a comment can be added to make it clear for future improvement.
> This changes the behavior of current implementation. Now unrelated
> datapaths are removed when certain ports are unbound, and the flows in the
> HV can be largely reduced, but with this change it may not reduce any more
> until a recompute is triggered. It is not a big problem I think.
>

Sure. I'll add a comment about it. I was also thinking of adding the same
in TODO.rst.
But it looks like TODO.rst needs some cleaning first.


> >
> >>
> >> > +}
> >> > +
> >> > +static bool
> >> > +handle_iface_add(const struct ovsrec_interface *iface_rec,
> >> > +                 const char *iface_id,
> >> > +                 struct binding_ctx_in *b_ctx_in,
> >> > +                 struct binding_ctx_out *b_ctx_out,
> >> > +                 struct hmap *qos_map)
> >> > +{
> >> > +    sset_add(b_ctx_out->local_lports, iface_id);
> >> > +    smap_replace(b_ctx_out->local_iface_ids, iface_rec->name,
> iface_id);
> >> > +
> >> > +    struct local_binding *lbinding =
> >> > +        local_binding_find(b_ctx_out->local_bindings, iface_id);
> >> > +
> >> > +    if (!lbinding) {
> >> > +        lbinding = local_binding_create(iface_id, iface_rec, NULL,
> >> BT_VIF);
> >> > +        local_binding_add(b_ctx_out->local_bindings, lbinding);
> >> > +    } else {
> >> > +        lbinding->iface = iface_rec;
> >> > +    }
> >> > +
> >> > +    if (!lbinding->pb || strcmp(lbinding->name,
> >> lbinding->pb->logical_port)) {
> >> > +        lbinding->pb = lport_lookup_by_name(
> >> > +            b_ctx_in->sbrec_port_binding_by_name, lbinding->name);
> >> > +        if (lbinding->pb && !strcmp(lbinding->pb->type, "virtual")) {
> >>
> >> Since we never bind "virtual" ports, this should be a misconfiguration,
> and
> >> it is better to log a warning message to be more clear.
> >>
> >> > +            lbinding->pb = NULL;
> >> > +        }
> >> > +    }
> >> > +
> >> > +    if (lbinding->pb) {
> >> > +        if (!consider_vif_lport(lbinding->pb, b_ctx_in, b_ctx_out,
> >> > +                                lbinding, qos_map)) {
> >> > +            return false;
> >> > +        }
> >> > +    }
> >> > +
> >> > +    /* Update the child local_binding's iface (if any children) and
> try
> >> to
> >> > +     *  claim the container lbindings. */
> >> > +    struct shash_node *node;
> >> > +    SHASH_FOR_EACH (node, &lbinding->children) {
> >> > +        struct local_binding *child = node->data;
> >> > +        child->iface = iface_rec;
> >> > +        if (child->type == BT_CONTAINER) {
> >> > +            if (!consider_container_lport(child->pb, b_ctx_in,
> b_ctx_out,
> >> > +                                          qos_map)) {
> >> > +                return false;
> >> > +            }
> >> > +        }
> >> > +    }
> >> > +
> >> > +    return true;
> >> > +}
> >> > +
> >> > +static bool
> >> > +handle_iface_delete(const char *lport_name, const char *iface_name,
> >> > +                    struct binding_ctx_in *b_ctx_in,
> >> > +                    struct binding_ctx_out *b_ctx_out, bool *changed)
> >> > +{
> >> > +    struct local_binding *lbinding;
> >> > +    lbinding = local_binding_find(b_ctx_out->local_bindings,
> >> > +                                  lport_name);
> >> > +    if (lbinding && lbinding->pb &&
> >> > +        lbinding->pb->chassis == b_ctx_in->chassis_rec) {
> >> > +
> >> > +        if (!release_local_binding(b_ctx_in->chassis_rec, lbinding,
> >> > +                                   !b_ctx_in->ovnsb_idl_txn)) {
> >> > +            return false;
> >> > +        }
> >> > +
> >> > +        struct local_datapath *ld =
> >> > +            get_local_datapath(b_ctx_out->local_datapaths,
> >> > +                                lbinding->pb->datapath->tunnel_key);
> >> > +        if (ld) {
> >> > +            remove_pb_from_local_datapath(lbinding->pb,
> >> > +                                            b_ctx_in->chassis_rec,
> >> > +                                            b_ctx_out, ld);
> >> > +        }
> >> > +        local_binding_delete(b_ctx_out->local_bindings, lbinding);
> >> > +        *changed = true;
> >> > +    }
> >> > +
> >> > +    sset_find_and_delete(b_ctx_out->local_lports, lport_name);
> >> > +    smap_remove(b_ctx_out->local_iface_ids, iface_name);
> >> > +
> >> > +    return true;
> >> > +}
> >> > +
> >> > +static bool
> >> > +is_iface_vif(const struct ovsrec_interface *iface_rec)
> >> > +{
> >> > +    if (iface_rec->type && iface_rec->type[0] &&
> >> > +        strcmp(iface_rec->type, "internal")) {
> >> > +        return false;
> >> > +    }
> >> > +
> >> > +    return true;
> >> > +}
> >> > +
> >> > +/* Returns true if the ovs interface changes were handled
> successfully,
> >> > + * false otherwise.
> >> > + */
> >> > +bool
> >> > +binding_handle_ovs_interface_changes(struct binding_ctx_in *b_ctx_in,
> >> > +                                     struct binding_ctx_out
> *b_ctx_out,
> >> > +                                     bool *changed)
> >> > +{
> >> > +    if (!b_ctx_in->chassis_rec) {
> >> > +        return false;
> >> > +    }
> >> > +
> >> > +    bool handled = true;
> >> > +    *changed = false;
> >> > +
> >> > +    /* Run the tracked interfaces loop twice. One to handle deleted
> >> > +     * changes. And another to handle add/update changes.
> >> > +     * This will ensure correctness.
> >> > +     */
> >> > +    const struct ovsrec_interface *iface_rec;
> >> > +    OVSREC_INTERFACE_TABLE_FOR_EACH_TRACKED (iface_rec,
> >> > +                                             b_ctx_in->iface_table) {
> >> > +        if (!is_iface_vif(iface_rec)) {
> >> > +            /* Right now are not handling ovs_interface changes of
> >> > +             * other types. This can be enhanced to handle of
> >> > +             * types - patch and tunnel. */
> >> > +            handled = false;
> >> > +            break;
> >> > +        }
> >> > +
> >> > +        const char *iface_id = smap_get(&iface_rec->external_ids,
> >> "iface-id");
> >> > +        const char *old_iface_id =
> smap_get(b_ctx_out->local_iface_ids,
> >> > +                                            iface_rec->name);
> >> > +        const char *cleared_iface_id = NULL;
> >> > +        if (!ovsrec_interface_is_deleted(iface_rec)) {
> >> > +            if (iface_id) {
> >> > +                /* Check if iface_id is changed. If so we need to
> >> > +                 * release the old port binding and associate this
> >> > +                 * inteface to new port binding. */
> >> > +                if (old_iface_id && strcmp(iface_id, old_iface_id)) {
> >> > +                    cleared_iface_id = old_iface_id;
> >> > +                }
> >> > +            } else if (old_iface_id) {
> >> > +                cleared_iface_id = old_iface_id;
> >> > +            }
> >> >          } else {
> >> > -            lbinding = local_binding_find(b_ctx_out->local_bindings,
> >> > -                                          binding_rec->parent_port);
> >> > +            cleared_iface_id = iface_id;
> >> > +            iface_id = NULL;
> >> >          }
> >> >
> >> > -        if (lbinding) {
> >> > -            changed = true;
> >> > +        if (cleared_iface_id) {
> >> > +            handled = handle_iface_delete(cleared_iface_id,
> >> iface_rec->name,
> >> > +                                          b_ctx_in, b_ctx_out,
> changed);
> >> > +        }
> >> > +
> >> > +        if (!handled) {
> >> >              break;
> >> >          }
> >> >      }
> >> >
> >> > -    return changed;
> >> > +    if (!handled) {
> >> > +        return false;
> >> > +    }
> >> > +
> >> > +    struct hmap qos_map = HMAP_INITIALIZER(&qos_map);
> >> > +    struct hmap *qos_map_ptr =
> >> > +        sset_is_empty(b_ctx_out->egress_ifaces) ? NULL : &qos_map;
> >> > +
> >> > +    OVSREC_INTERFACE_TABLE_FOR_EACH_TRACKED (iface_rec,
> >> > +                                             b_ctx_in->iface_table) {
> >> > +        /* Loop to handle create and update changes only. */
> >>
> >> Thanks for splitting the loops, but here are still some problems.
> >>
> >> 1. The first loop handled: a) interface deletion. b) interface update
> with
> >> old iface-id cleared. So in this second loop it should handle all the
> rest
> >> cases: a) interface add. b) interface update without iface-id change. It
> >> seems b) is treated as just interface add, which may be wrong: if
> iface-id
> >> didn't change but some other fields of the interface record is changed,
> it
> >> is not handled properly.
> >
> >
> > If other fields change, what should we handle ? Can ofport change ?
> > I'll see if ofport can be changed and what happens when it changes.
> >
> > For other fields like name, mtu etc do we need to handle anything ?
> > I think we only care about the external_ids:iface-id and the ofport.
> > The function handle_iface_add() even if it is called for other changes,
> > it will be a no-op.
> >
> > Also from the tracking we can't figure out what fields change right ?
> >
> >
> Yes, I think it is hard to check with fields are changed and handle it
> accordingly. That's why I suggest to make it simple: if there is an update,
> always handle it like a deletion and then creation. This way, we dont need
> to consider all the corner cases and we are sure it is correct.
>

In this particular case of binding, I don't think there are any corner
cases..

We consider an OVS interface for binding if the below 2 conditions meet

1. external_ids:iface-id is set
2. ofport is > 0.

Otherwise we need to consider a release if it was already claimed.

When  an ovs interface is updated like below
 $ovs-vsctl set interface p1 external_ids:foo=bar

We don't need to handle it like a deletion and then a creation. Because we
can
easily see that the above conditions are met,

It also creates unnecessary deletion and re-adding of OVS flows related to
that
logical port when we handle flow computation for changed runtime data.

If we handle this way, there won't be any runtime data changes.

Thanks
Numan


> >
> >>
> >>
> >> 2. Some other condition checks should be done for this loop, such as: if
> >> (!is_iface_vif(iface_rec)) ...
> >
> >
> > This is not required because the first loop checks for it and if there is
> any
> > non vif interface change, then this function returns false, before the
> > second loop.
> >
> OK.
>
> >>
> >>
> >> > +        if (ovsrec_interface_is_deleted(iface_rec)) {
> >> > +            continue;
> >> > +        }
> >> > +
> >> > +        const char *iface_id = smap_get(&iface_rec->external_ids,
> >> "iface-id");
> >> > +        int64_t ofport = iface_rec->n_ofport ? *iface_rec->ofport :
> 0;
> >> > +        if (iface_id && ofport > 0) {
> >>
> >> What if iface-id didn't change, but ofport changed from non-zero to 0?
> How
> >> should we handle the change?
> >>
> >
> > Good point. I think we should consider this as  handle_iface_delete.
> >
> > I'll rename the function handle_iface_delete() to handle_iface_release()
> > and handle_iface_add() to handle_iface_claim() to be more clear.
> >
> >> A general approach can be: whenever there is an update
> >> (!ovsrec_interface_is_new()), we firstly handle it as interface delete,
> and
> >> then handle an interface add.
> >
> >
> > But why delete and add if can determine that it is going to be either one
> > i.e release or claim and I think we can definitely determine.
> > Also in patch 6/7 where we do tracking, it becomes a bit complicated
> > if we handle it as delete and then add as we may add 2 such tracking
> > data.
> >
> It is for simplicity and correctness. Please see the comment above.
>
> > Thanks
> > Numan
> >
> >>
> >> > +            *changed = true;
> >> > +            handled = handle_iface_add(iface_rec, iface_id, b_ctx_in,
> >> > +                                       b_ctx_out, qos_map_ptr);
> >> > +            if (!handled) {
> >> > +                break;
> >> > +            }
> >> > +        }
> >> > +    }
> >> > +
> >> > +    if (handled && qos_map_ptr && set_noop_qos(b_ctx_in->ovs_idl_txn,
> >> > +                                               b_ctx_in->port_table,
> >> > +                                               b_ctx_in->qos_table,
> >> > +
> >> b_ctx_out->egress_ifaces)) {
> >> > +        const char *entry;
> >> > +        SSET_FOR_EACH (entry, b_ctx_out->egress_ifaces) {
> >> > +            setup_qos(entry, &qos_map);
> >> > +        }
> >> > +    }
> >> > +
> >> > +    destroy_qos_map(&qos_map);
> >> > +    return handled;
> >> >  }
> >> >
> >> > -/* Returns true if the database is all cleaned up, false if more work
> is
> >> > - * required. */
> >> > -bool
> >> > -binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn,
> >> > -                const struct sbrec_port_binding_table
> >> *port_binding_table,
> >> > -                const struct sbrec_chassis *chassis_rec)
> >> > +static void
> >> > +handle_deleted_lport(const struct sbrec_port_binding *pb,
> >> > +                     struct binding_ctx_in *b_ctx_in,
> >> > +                     struct binding_ctx_out *b_ctx_out)
> >> >  {
> >> > -    if (!ovnsb_idl_txn) {
> >> > +    struct local_datapath *ld =
> >> > +        get_local_datapath(b_ctx_out->local_datapaths,
> >> > +                           pb->datapath->tunnel_key);
> >> > +    if (ld) {
> >> > +        remove_pb_from_local_datapath(pb, b_ctx_in->chassis_rec,
> >> > +                                      b_ctx_out, ld);
> >> > +    }
> >> > +}
> >> > +
> >> > +static struct local_binding *
> >> > +get_lbinding_for_lport(const struct sbrec_port_binding *pb,
> >> > +                       enum en_lport_type lport_type,
> >> > +                       struct binding_ctx_out *b_ctx_out)
> >> > +{
> >> > +    ovs_assert(lport_type == LP_VIF || lport_type == LP_VIRTUAL);
> >> > +
> >> > +    if (lport_type == LP_VIF && !is_lport_container(pb)) {
> >> > +        return local_binding_find(b_ctx_out->local_bindings,
> >> pb->logical_port);
> >> > +    }
> >> > +
> >> > +    struct local_binding *parent_lbinding = NULL;
> >> > +
> >> > +    if (lport_type == LP_VIRTUAL) {
> >> > +        parent_lbinding =
> local_binding_find(b_ctx_out->local_bindings,
> >> > +                                             pb->virtual_parent);
> >> > +    } else {
> >> > +        parent_lbinding =
> local_binding_find(b_ctx_out->local_bindings,
> >> > +                                             pb->parent_port);
> >> > +    }
> >> > +
> >> > +    return parent_lbinding
> >> > +           ? local_binding_find(&parent_lbinding->children,
> >> pb->logical_port)
> >> > +           : NULL;
> >> > +}
> >> > +
> >> > +static bool
> >> > +handle_deleted_vif_lport(const struct sbrec_port_binding *pb,
> >> > +                         enum en_lport_type lport_type,
> >> > +                         struct binding_ctx_in *b_ctx_in,
> >> > +                         struct binding_ctx_out *b_ctx_out,
> >> > +                         bool *changed)
> >> > +{
> >> > +    struct local_binding *lbinding =
> >> > +        get_lbinding_for_lport(pb, lport_type, b_ctx_out);
> >> > +
> >> > +    if (lbinding) {
> >> > +        lbinding->pb = NULL;
> >> > +        /* The port_binding 'pb' is deleted. So there is no need to
> >> > +         * clear the 'chassis' column of 'pb'. But we need to do
> >> > +         * for the local_binding's children. */
> >> > +        if (lbinding->type == BT_VIF &&
> >> > +
> !release_local_binding_children(b_ctx_in->chassis_rec,
> >> > +                                                lbinding,
> >> > +
> >>  !b_ctx_in->ovnsb_idl_txn)) {
> >> > +            return false;
> >> > +        }
> >> > +
> >> > +        *changed = true;
> >> > +    }
> >> > +
> >> > +    handle_deleted_lport(pb, b_ctx_in, b_ctx_out);
> >> > +    return true;
> >> > +}
> >> > +
> >> > +static bool
> >> > +handle_updated_vif_lport(const struct sbrec_port_binding *pb,
> >> > +                         enum en_lport_type lport_type,
> >> > +                         struct binding_ctx_in *b_ctx_in,
> >> > +                         struct binding_ctx_out *b_ctx_out,
> >> > +                         struct hmap *qos_map,
> >> > +                         bool *changed)
> >> > +{
> >> > +    bool claimed = (pb->chassis == b_ctx_in->chassis_rec);
> >> > +    bool handled = true;
> >> > +
> >> > +    if (lport_type == LP_VIRTUAL) {
> >> > +        handled = consider_virtual_lport(pb, b_ctx_in, b_ctx_out,
> >> qos_map);
> >> > +    } else if (lport_type == LP_VIF && is_lport_container(pb)) {
> >> > +        handled = consider_container_lport(pb, b_ctx_in, b_ctx_out,
> >> qos_map);
> >> > +    } else {
> >> > +        handled = consider_vif_lport(pb, b_ctx_in, b_ctx_out, NULL,
> >> qos_map);
> >> > +    }
> >> > +
> >> > +    if (!handled) {
> >> >          return false;
> >> >      }
> >> > -    if (!chassis_rec) {
> >> > +
> >> > +    bool now_claimed = (pb->chassis == b_ctx_in->chassis_rec);
> >> > +    bool changed_ = (claimed != now_claimed);
> >> > +
> >> > +    if (changed_) {
> >> > +        *changed = true;
> >> > +    }
> >> > +
> >> > +    if (lport_type == LP_VIRTUAL ||
> >> > +        (lport_type == LP_VIF && is_lport_container(pb)) ||
> !changed_) {
> >> >          return true;
> >> >      }
> >> >
> >> > -    const struct sbrec_port_binding *binding_rec;
> >> > -    bool any_changes = false;
> >> > -    SBREC_PORT_BINDING_TABLE_FOR_EACH (binding_rec,
> port_binding_table) {
> >> > -        if (binding_rec->chassis == chassis_rec) {
> >> > -            if (binding_rec->encap)
> >> > -                sbrec_port_binding_set_encap(binding_rec, NULL);
> >> > -            sbrec_port_binding_set_chassis(binding_rec, NULL);
> >> > -            any_changes = true;
> >> > +    struct local_binding *lbinding =
> >> > +        local_binding_find(b_ctx_out->local_bindings,
> pb->logical_port);
> >> > +
> >> > +    ovs_assert(lbinding);
> >> > +
> >> > +    struct shash_node *node;
> >> > +    SHASH_FOR_EACH (node, &lbinding->children) {
> >> > +        struct local_binding *child = node->data;
> >> > +        if (child->type == BT_CONTAINER) {
> >> > +            handled = consider_container_lport(child->pb, b_ctx_in,
> >> b_ctx_out,
> >> > +                                               qos_map);
> >> > +            if (!handled) {
> >> > +                return false;
> >> > +            }
> >> >          }
> >> >      }
> >> >
> >> > -    if (any_changes) {
> >> > -        ovsdb_idl_txn_add_comment(
> >> > -            ovnsb_idl_txn,
> >> > -            "ovn-controller: removing all port bindings for '%s'",
> >> > -            chassis_rec->name);
> >> > +    return true;
> >> > +}
> >> > +
> >> > +/* Returns true if the port binding changes resulted in local binding
> >> > + * updates, false otherwise.
> >> > + */
> >> > +bool
> >> > +binding_handle_port_binding_changes(struct binding_ctx_in *b_ctx_in,
> >> > +                                    struct binding_ctx_out
> *b_ctx_out,
> >> > +                                    bool *changed)
> >> > +{
> >> > +    bool handled = true;
> >> > +    *changed = false;
> >> > +
> >> > +    const struct sbrec_port_binding *pb;
> >> > +
> >> > +    /* Run the tracked port binding loop twice. One to handle deleted
> >> > +     * changes. And another to handle add/update changes.
> >> > +     * This will ensure correctness.
> >> > +     */
> >> > +    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
> >> > +
> >> b_ctx_in->port_binding_table) {
> >> > +        if (!sbrec_port_binding_is_deleted(pb)) {
> >> > +            continue;
> >> > +        }
> >> > +
> >> > +        enum en_lport_type lport_type = get_lport_type(pb);
> >> > +        if (lport_type == LP_VIF || lport_type == LP_VIRTUAL) {
> >> > +            handled = handle_deleted_vif_lport(pb, lport_type,
> b_ctx_in,
> >> > +                                                b_ctx_out, changed);
> >> > +        } else {
> >> > +            handle_deleted_lport(pb, b_ctx_in, b_ctx_out);
> >> > +        }
> >> > +
> >> > +        if (!handled) {
> >> > +            break;
> >> > +        }
> >> >      }
> >> >
> >> > -    return !any_changes;
> >> > +    if (!handled) {
> >> > +        return false;
> >> > +    }
> >> > +
> >> > +    struct hmap qos_map = HMAP_INITIALIZER(&qos_map);
> >> > +    struct hmap *qos_map_ptr =
> >> > +        sset_is_empty(b_ctx_out->egress_ifaces) ? NULL : &qos_map;
> >> > +
> >> > +    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
> >> > +
> >> b_ctx_in->port_binding_table) {
> >> > +        /* Loop to handle create and update changes only. */
> >> > +        if (sbrec_port_binding_is_deleted(pb)) {
> >> > +            continue;
> >> > +        }
> >> > +
> >> > +        enum en_lport_type lport_type = get_lport_type(pb);
> >> > +
> >> > +        struct local_datapath *ld =
> >> > +            get_local_datapath(b_ctx_out->local_datapaths,
> >> > +                               pb->datapath->tunnel_key);
> >> > +
> >> > +        switch (lport_type) {
> >> > +        case LP_VIF:
> >> > +        case LP_VIRTUAL:
> >> > +            handled = handle_updated_vif_lport(pb, lport_type,
> b_ctx_in,
> >> > +                                               b_ctx_out,
> qos_map_ptr,
> >> > +                                               changed);
> >> > +            break;
> >> > +
> >> > +        case LP_PATCH:
> >> > +        case LP_LOCALPORT:
> >> > +        case LP_VTEP:
> >> > +            *changed = true;
> >> > +            update_local_lport_ids(b_ctx_out->local_lport_ids, pb);
> >> > +            if (lport_type ==  LP_PATCH) {
> >> > +                /* Add the peer datapath to the local datapaths if
> it's
> >> > +                    * not present yet.
> >> > +                    */
> >> > +                if (ld) {
> >> > +                    add_local_datapath_peer_port(pb, b_ctx_in,
> >> > +                                                    b_ctx_out, ld);
> >> > +                }
> >> > +            }
> >> > +            break;
> >> > +
> >> > +        case LP_L2GATEWAY:
> >> > +            *changed = true;
> >> > +            handled = consider_l2gw_lport(pb, b_ctx_in, b_ctx_out);
> >> > +            break;
> >> > +
> >> > +        case LP_L3GATEWAY:
> >> > +            *changed = true;
> >> > +            handled = consider_l3gw_lport(pb, b_ctx_in, b_ctx_out);
> >> > +            break;
> >> > +
> >> > +        case LP_CHASSISREDIRECT:
> >> > +            *changed = true;
> >> > +            handled = consider_cr_lport(pb, b_ctx_in, b_ctx_out);
> >> > +            break;
> >> > +
> >> > +        case LP_EXTERNAL:
> >> > +            *changed = true;
> >> > +            handled = consider_external_lport(pb, b_ctx_in,
> b_ctx_out);
> >> > +            break;
> >> > +
> >> > +        case LP_LOCALNET: {
> >> > +            *changed = true;
> >> > +            consider_localnet_lport(pb, b_ctx_in, b_ctx_out,
> >> qos_map_ptr);
> >> > +
> >> > +            struct shash bridge_mappings =
> >> > +                SHASH_INITIALIZER(&bridge_mappings);
> >> > +            add_ovs_bridge_mappings(b_ctx_in->ovs_table,
> >> > +                                    b_ctx_in->bridge_table,
> >> > +                                    &bridge_mappings);
> >> > +            update_ld_localnet_port(pb, &bridge_mappings,
> >> > +                                    b_ctx_out->egress_ifaces,
> >> > +                                    b_ctx_out->local_datapaths);
> >> > +            shash_destroy(&bridge_mappings);
> >> > +            break;
> >> > +        }
> >> > +
> >> > +        case LP_REMOTE:
> >> > +        case LP_UNKNOWN:
> >> > +            break;
> >> > +        }
> >> > +
> >> > +        if (!handled) {
> >> > +            break;
> >> > +        }
> >> > +    }
> >> > +
> >> > +    if (handled && qos_map_ptr && set_noop_qos(b_ctx_in->ovs_idl_txn,
> >> > +                                               b_ctx_in->port_table,
> >> > +                                               b_ctx_in->qos_table,
> >> > +
> >> b_ctx_out->egress_ifaces)) {
> >> > +        const char *entry;
> >> > +        SSET_FOR_EACH (entry, b_ctx_out->egress_ifaces) {
> >> > +            setup_qos(entry, &qos_map);
> >> > +        }
> >> > +    }
> >> > +
> >> > +    destroy_qos_map(&qos_map);
> >> > +    return handled;
> >> >  }
> >> > diff --git a/controller/binding.h b/controller/binding.h
> >> > index 9affc9a96..f7ace6faf 100644
> >> > --- a/controller/binding.h
> >> > +++ b/controller/binding.h
> >> > @@ -57,6 +57,7 @@ struct binding_ctx_out {
> >> >      struct sset *local_lports;
> >> >      struct sset *local_lport_ids;
> >> >      struct sset *egress_ifaces;
> >> > +    struct smap *local_iface_ids;
> >> >  };
> >> >
> >> >  void binding_register_ovs_idl(struct ovsdb_idl *);
> >> > @@ -64,9 +65,13 @@ void binding_run(struct binding_ctx_in *, struct
> >> binding_ctx_out *);
> >> >  bool binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn,
> >> >                       const struct sbrec_port_binding_table *,
> >> >                       const struct sbrec_chassis *);
> >> > -bool binding_evaluate_port_binding_changes(struct binding_ctx_in *,
> >> > -                                           struct binding_ctx_out *);
> >> >
> >> >  void local_bindings_init(struct shash *local_bindings);
> >> >  void local_bindings_destroy(struct shash *local_bindings);
> >> > +bool binding_handle_ovs_interface_changes(struct binding_ctx_in *,
> >> > +                                          struct binding_ctx_out *,
> >> > +                                          bool *changed);
> >> > +bool binding_handle_port_binding_changes(struct binding_ctx_in *,
> >> > +                                         struct binding_ctx_out *,
> >> > +                                         bool *changed);
> >> >  #endif /* controller/binding.h */
> >> > diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
> >> > index 6b759966b..08b074415 100644
> >> > --- a/controller/ovn-controller.c
> >> > +++ b/controller/ovn-controller.c
> >> > @@ -753,6 +753,7 @@ enum sb_engine_node {
> >> >      OVS_NODE(open_vswitch, "open_vswitch") \
> >> >      OVS_NODE(bridge, "bridge") \
> >> >      OVS_NODE(port, "port") \
> >> > +    OVS_NODE(interface, "interface") \
> >> >      OVS_NODE(qos, "qos")
> >> >
> >> >  enum ovs_engine_node {
> >> > @@ -974,6 +975,7 @@ struct ed_type_runtime_data {
> >> >      struct sset active_tunnels;
> >> >
> >> >      struct sset egress_ifaces;
> >> > +    struct smap local_iface_ids;
> >> >  };
> >> >
> >> >  static void *
> >> > @@ -987,6 +989,7 @@ en_runtime_data_init(struct engine_node *node
> >> OVS_UNUSED,
> >> >      sset_init(&data->local_lport_ids);
> >> >      sset_init(&data->active_tunnels);
> >> >      sset_init(&data->egress_ifaces);
> >> > +    smap_init(&data->local_iface_ids);
> >> >      local_bindings_init(&data->local_bindings);
> >> >      return data;
> >> >  }
> >> > @@ -1000,6 +1003,7 @@ en_runtime_data_cleanup(void *data)
> >> >      sset_destroy(&rt_data->local_lport_ids);
> >> >      sset_destroy(&rt_data->active_tunnels);
> >> >      sset_destroy(&rt_data->egress_ifaces);
> >> > +    smap_destroy(&rt_data->local_iface_ids);
> >> >      struct local_datapath *cur_node, *next_node;
> >> >      HMAP_FOR_EACH_SAFE (cur_node, next_node, hmap_node,
> >> >                          &rt_data->local_datapaths) {
> >> > @@ -1041,6 +1045,10 @@ init_binding_ctx(struct engine_node *node,
> >> >          (struct ovsrec_port_table *)EN_OVSDB_GET(
> >> >              engine_get_input("OVS_port", node));
> >> >
> >> > +    struct ovsrec_interface_table *iface_table =
> >> > +        (struct ovsrec_interface_table *)EN_OVSDB_GET(
> >> > +            engine_get_input("OVS_interface", node));
> >> > +
> >> >      struct ovsrec_qos_table *qos_table =
> >> >          (struct ovsrec_qos_table *)EN_OVSDB_GET(
> >> >              engine_get_input("OVS_qos", node));
> >> > @@ -1070,6 +1078,7 @@ init_binding_ctx(struct engine_node *node,
> >> >      b_ctx_in->sbrec_port_binding_by_datapath =
> >> sbrec_port_binding_by_datapath;
> >> >      b_ctx_in->sbrec_port_binding_by_name =
> sbrec_port_binding_by_name;
> >> >      b_ctx_in->port_table = port_table;
> >> > +    b_ctx_in->iface_table = iface_table;
> >> >      b_ctx_in->qos_table = qos_table;
> >> >      b_ctx_in->port_binding_table = pb_table;
> >> >      b_ctx_in->br_int = br_int;
> >> > @@ -1083,6 +1092,7 @@ init_binding_ctx(struct engine_node *node,
> >> >      b_ctx_out->local_lport_ids = &rt_data->local_lport_ids;
> >> >      b_ctx_out->egress_ifaces = &rt_data->egress_ifaces;
> >> >      b_ctx_out->local_bindings = &rt_data->local_bindings;
> >> > +    b_ctx_out->local_iface_ids = &rt_data->local_iface_ids;
> >> >  }
> >> >
> >> >  static void
> >> > @@ -1111,10 +1121,12 @@ en_runtime_data_run(struct engine_node *node,
> >> void *data)
> >> >          sset_destroy(local_lport_ids);
> >> >          sset_destroy(active_tunnels);
> >> >          sset_destroy(&rt_data->egress_ifaces);
> >> > +        smap_destroy(&rt_data->local_iface_ids);
> >> >          sset_init(local_lports);
> >> >          sset_init(local_lport_ids);
> >> >          sset_init(active_tunnels);
> >> >          sset_init(&rt_data->egress_ifaces);
> >> > +        smap_init(&rt_data->local_iface_ids);
> >> >          local_bindings_init(&rt_data->local_bindings);
> >> >      }
> >> >
> >> > @@ -1140,6 +1152,34 @@ en_runtime_data_run(struct engine_node *node,
> void
> >> *data)
> >> >      engine_set_node_state(node, EN_UPDATED);
> >> >  }
> >> >
> >> > +static bool
> >> > +runtime_data_ovs_interface_handler(struct engine_node *node, void
> *data)
> >> > +{
> >> > +    struct ed_type_runtime_data *rt_data = data;
> >> > +    struct binding_ctx_in b_ctx_in;
> >> > +    struct binding_ctx_out b_ctx_out;
> >> > +    init_binding_ctx(node, rt_data, &b_ctx_in, &b_ctx_out);
> >> > +
> >> > +    bool changed = false;
> >> > +    if (!binding_handle_ovs_interface_changes(&b_ctx_in, &b_ctx_out,
> >> > +                                              &changed)) {
> >> > +        return false;
> >> > +    }
> >> > +
> >> > +    if (changed) {
> >> > +        engine_set_node_state(node, EN_UPDATED);
> >> > +    }
> >> > +
> >> > +    return true;
> >> > +}
> >> > +
> >> > +static bool
> >> > +runtime_data_noop_handler(struct engine_node *node OVS_UNUSED,
> >> > +                          void *data OVS_UNUSED)
> >> > +{
> >> > +    return true;
> >> > +}
> >> > +
> >> >  static bool
> >> >  runtime_data_sb_port_binding_handler(struct engine_node *node, void
> >> *data)
> >> >  {
> >> > @@ -1147,11 +1187,21 @@ runtime_data_sb_port_binding_handler(struct
> >> engine_node *node, void *data)
> >> >      struct binding_ctx_in b_ctx_in;
> >> >      struct binding_ctx_out b_ctx_out;
> >> >      init_binding_ctx(node, rt_data, &b_ctx_in, &b_ctx_out);
> >> > +    if (!b_ctx_in.chassis_rec) {
> >> > +        return false;
> >> > +    }
> >> > +
> >> > +    bool changed = false;
> >> > +    if (!binding_handle_port_binding_changes(&b_ctx_in, &b_ctx_out,
> >> > +                                             &changed)) {
> >> > +        return false;
> >> > +    }
> >> >
> >> > -    bool changed = binding_evaluate_port_binding_changes(&b_ctx_in,
> >> > -                                                         &b_ctx_out);
> >> > +    if (changed) {
> >> > +        engine_set_node_state(node, EN_UPDATED);
> >> > +    }
> >> >
> >> > -    return !changed;
> >> > +    return true;
> >> >  }
> >> >
> >> >  /* Connection tracking zones. */
> >> > @@ -1894,7 +1944,10 @@ main(int argc, char *argv[])
> >> >
> >> >      engine_add_input(&en_runtime_data, &en_ovs_open_vswitch, NULL);
> >> >      engine_add_input(&en_runtime_data, &en_ovs_bridge, NULL);
> >> > -    engine_add_input(&en_runtime_data, &en_ovs_port, NULL);
> >> > +    engine_add_input(&en_runtime_data, &en_ovs_port,
> >> > +                     runtime_data_noop_handler);
> >> > +    engine_add_input(&en_runtime_data, &en_ovs_interface,
> >> > +                     runtime_data_ovs_interface_handler);
> >> >      engine_add_input(&en_runtime_data, &en_ovs_qos, NULL);
> >> >
> >> >      engine_add_input(&en_runtime_data, &en_sb_chassis, NULL);
> >> > diff --git a/tests/ovn-performance.at b/tests/ovn-performance.at
> >> > index a8a15f8fe..5dfc6f7ca 100644
> >> > --- a/tests/ovn-performance.at
> >> > +++ b/tests/ovn-performance.at
> >> > @@ -239,6 +239,19 @@ for i in 1 2; do
> >> >      ovn_attach n1 br-phys 192.168.0.$i
> >> >  done
> >> >
> >> > +# Wait for the tunnel ports to be created and up.
> >> > +# Otherwise this may affect the lflow_run count.
> >> > +
> >> > +OVS_WAIT_UNTIL([
> >> > +    test $(as hv1 ovs-vsctl list interface ovn-hv2-0 | \
> >> > +grep tunnel_egress_iface_carrier=up | wc -l) -eq 1
> >> > +])
> >> > +
> >> > +OVS_WAIT_UNTIL([
> >> > +    test $(as hv2 ovs-vsctl list interface ovn-hv1-0 | \
> >> > +grep tunnel_egress_iface_carrier=up | wc -l) -eq 1
> >> > +])
> >> > +
> >> >  # Add router lr1
> >> >  OVN_CONTROLLER_EXPECT_HIT(
> >> >      [hv1 hv2], [lflow_run],
> >> > --
> >> > 2.26.2
> >> >
> >> > _______________________________________________
> >> > dev mailing list
> >> > dev@openvswitch.org
> >> > https://mail.openvswitch.org/mailman/listinfo/ovs-dev
> >> _______________________________________________
> >> dev mailing list
> >> dev@openvswitch.org
> >> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
> >>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
>
Han Zhou May 22, 2020, 9:39 p.m. UTC | #5
On Fri, May 22, 2020 at 1:51 AM Numan Siddique <numans@ovn.org> wrote:
>
>
>
> On Fri, May 22, 2020 at 1:17 AM Han Zhou <hzhou@ovn.org> wrote:
>>
>> On Thu, May 21, 2020 at 3:49 AM Numan Siddique <numans@ovn.org> wrote:
>> >
>> >
>> >
>> > On Thu, May 21, 2020 at 6:57 AM Han Zhou <hzhou@ovn.org> wrote:
>> >>
>> >> Hi Numan, please see my comments below.
>> >
>> >
>> > Thanks for the review. Please see below for a few comments.
>> >
>> >
>> >>
>> >>
>> >> On Wed, May 20, 2020 at 12:41 PM <numans@ovn.org> wrote:
>> >> >
>> >> > From: Numan Siddique <numans@ovn.org>
>> >> >
>> >> > This patch handles SB port binding changes and OVS interface changes
>> >> > incrementally in the runtime_data stage which otherwise would have
>> >> > resulted in calls to binding_run().
>> >> >
>> >> > Prior to this patch, port binding changes were handled differently.
>> >> > The changes were only evaluated to see if they need full recompute
>> >> > or they can be ignored.
>> >> >
>> >> > With this patch, the runtime data is updated with the changes (both
>> >> > SB port binding and OVS interface) and ports are claimed/released in
>> >> > the handlers itself, avoiding the need to trigger recompute of
runtime
>> >> > data stage.
>> >> >
>> >> > Acked-by: Mark Michelson <mmichels@redhat.com>
>> >> > Signed-off-by: Numan Siddique <numans@ovn.org>
>> >> > ---
>> >> >  controller/binding.c        | 906
+++++++++++++++++++++++++++++++-----
>> >> >  controller/binding.h        |   9 +-
>> >> >  controller/ovn-controller.c |  61 ++-
>> >> >  tests/ovn-performance.at    |  13 +
>> >> >  4 files changed, 855 insertions(+), 134 deletions(-)
>> >> >
>> >> > diff --git a/controller/binding.c b/controller/binding.c
>> >> > index 2997fcae8..d5e8e4773 100644
>> >> > --- a/controller/binding.c
>> >> > +++ b/controller/binding.c
>> >> > @@ -360,17 +360,6 @@ destroy_qos_map(struct hmap *qos_map)
>> >> >      hmap_destroy(qos_map);
>> >> >  }
>> >> >
>> >> > -static void
>> >> > -update_local_lport_ids(struct sset *local_lport_ids,
>> >> > -                       const struct sbrec_port_binding
*binding_rec)
>> >> > -{
>> >> > -        char buf[16];
>> >> > -        snprintf(buf, sizeof(buf), "%"PRId64"_%"PRId64,
>> >> > -                 binding_rec->datapath->tunnel_key,
>> >> > -                 binding_rec->tunnel_key);
>> >> > -        sset_add(local_lport_ids, buf);
>> >> > -}
>> >> > -
>> >> >  /*
>> >> >   * Get the encap from the chassis for this port. The interface
>> >> >   * may have an external_ids:encap-ip=<encap-ip> set; if so we
>> >> > @@ -449,10 +438,10 @@ is_network_plugged(const struct
>> sbrec_port_binding
>> >> *binding_rec,
>> >> >  }
>> >> >
>> >> >  static void
>> >> > -consider_localnet_port(const struct sbrec_port_binding
*binding_rec,
>> >> > -                       struct shash *bridge_mappings,
>> >> > -                       struct sset *egress_ifaces,
>> >> > -                       struct hmap *local_datapaths)
>> >> > +update_ld_localnet_port(const struct sbrec_port_binding
*binding_rec,
>> >> > +                        struct shash *bridge_mappings,
>> >> > +                        struct sset *egress_ifaces,
>> >> > +                        struct hmap *local_datapaths)
>> >> >  {
>> >> >      /* Ignore localnet ports for unplugged networks. */
>> >> >      if (!is_network_plugged(binding_rec, bridge_mappings)) {
>> >> > @@ -482,6 +471,28 @@ consider_localnet_port(const struct
>> >> sbrec_port_binding *binding_rec,
>> >> >      ld->localnet_port = binding_rec;
>> >> >  }
>> >> >
>> >> > +static void
>> >> > +update_local_lport_ids(struct sset *local_lport_ids,
>> >> > +                       const struct sbrec_port_binding
*binding_rec)
>> >> > +{
>> >> > +        char buf[16];
>> >> > +        snprintf(buf, sizeof(buf), "%"PRId64"_%"PRId64,
>> >> > +                 binding_rec->datapath->tunnel_key,
>> >> > +                 binding_rec->tunnel_key);
>> >> > +        sset_add(local_lport_ids, buf);
>> >> > +}
>> >> > +
>> >> > +static void
>> >> > +remove_local_lport_ids(const struct sbrec_port_binding
*binding_rec,
>> >> > +                       struct sset *local_lport_ids)
>> >> > +{
>> >> > +        char buf[16];
>> >> > +        snprintf(buf, sizeof(buf), "%"PRId64"_%"PRId64,
>> >> > +                 binding_rec->datapath->tunnel_key,
>> >> > +                 binding_rec->tunnel_key);
>> >> > +        sset_find_and_delete(local_lport_ids, buf);
>> >> > +}
>> >> > +
>> >> >  /* Local bindings. binding.c module binds the logical port
>> (represented
>> >> by
>> >> >   * Port_Binding rows) and sets the 'chassis' column when it is sees
>> the
>> >> >   * OVS interface row (of type "" or "internal") with the
>> >> > @@ -599,6 +610,14 @@ local_bindings_destroy(struct shash
>> *local_bindings)
>> >> >      shash_destroy(local_bindings);
>> >> >  }
>> >> >
>> >> > +static
>> >> > +void local_binding_delete(struct shash *local_bindings,
>> >> > +                          struct local_binding *lbinding)
>> >> > +{
>> >> > +    shash_find_and_delete(local_bindings, lbinding->name);
>> >> > +    local_binding_destroy(lbinding);
>> >> > +}
>> >> > +
>> >> >  static void
>> >> >  local_binding_add_child(struct local_binding *lbinding,
>> >> >                          struct local_binding *child)
>> >> > @@ -625,6 +644,7 @@ is_lport_container(const struct
sbrec_port_binding
>> >> *pb)
>> >> >      return !pb->type[0] && pb->parent_port && pb->parent_port[0];
>> >> >  }
>> >> >
>> >> > +/* Corresponds to each Port_Binding.type. */
>> >> >  enum en_lport_type {
>> >> >      LP_UNKNOWN,
>> >> >      LP_VIF,
>> >> > @@ -670,12 +690,20 @@ get_lport_type(const struct sbrec_port_binding
>> *pb)
>> >> >      return LP_UNKNOWN;
>> >> >  }
>> >> >
>> >> > -static void
>> >> > +/* Returns false if lport is not claimed due to 'sb_readonly'.
>> >> > + * Returns true otherwise.
>> >> > + */
>> >> > +static bool
>> >> >  claim_lport(const struct sbrec_port_binding *pb,
>> >> >              const struct sbrec_chassis *chassis_rec,
>> >> > -            const struct ovsrec_interface *iface_rec)
>> >> > +            const struct ovsrec_interface *iface_rec,
>> >> > +            bool sb_readonly)
>> >> >  {
>> >> >      if (pb->chassis != chassis_rec) {
>> >> > +        if (sb_readonly) {
>> >> > +            return false;
>> >> > +        }
>> >> > +
>> >> >          if (pb->chassis) {
>> >> >              VLOG_INFO("Changing chassis for lport %s from %s to
%s.",
>> >> >                      pb->logical_port, pb->chassis->name,
>> >> > @@ -694,26 +722,48 @@ claim_lport(const struct sbrec_port_binding
*pb,
>> >> >      struct sbrec_encap *encap_rec =
>> >> >          sbrec_get_port_encap(chassis_rec, iface_rec);
>> >> >      if (encap_rec && pb->encap != encap_rec) {
>> >> > +        if (sb_readonly) {
>> >> > +            return false;
>> >> > +        }
>> >> >          sbrec_port_binding_set_encap(pb, encap_rec);
>> >> >      }
>> >> > +
>> >> > +    return true;
>> >> >  }
>> >> >
>> >> > -static void
>> >> > -release_lport(const struct sbrec_port_binding *pb)
>> >> > +/* Returns false if lport is not released due to 'sb_readonly'.
>> >> > + * Returns true otherwise.
>> >> > + */
>> >> > +static bool
>> >> > +release_lport(const struct sbrec_port_binding *pb, bool
sb_readonly)
>> >> >  {
>> >> >      if (!pb) {
>> >> > -        return;
>> >> > +        return true;
>> >> >      }
>> >> >
>> >> > -    VLOG_INFO("Releasing lport %s from this chassis.",
>> pb->logical_port);
>> >> >      if (pb->encap) {
>> >> > +        if (sb_readonly) {
>> >> > +            return false;
>> >> > +        }
>> >> >          sbrec_port_binding_set_encap(pb, NULL);
>> >> >      }
>> >> > -    sbrec_port_binding_set_chassis(pb, NULL);
>> >> > +
>> >> > +    if (pb->chassis) {
>> >> > +        if (sb_readonly) {
>> >> > +            return false;
>> >> > +        }
>> >> > +        sbrec_port_binding_set_chassis(pb, NULL);
>> >> > +    }
>> >> >
>> >> >      if (pb->virtual_parent) {
>> >> > +        if (sb_readonly) {
>> >> > +            return false;
>> >> > +        }
>> >> >          sbrec_port_binding_set_virtual_parent(pb, NULL);
>> >> >      }
>> >> > +
>> >> > +    VLOG_INFO("Releasing lport %s from this chassis.",
>> pb->logical_port);
>> >> > +    return true;
>> >> >  }
>> >> >
>> >> >  static bool
>> >> > @@ -738,7 +788,41 @@ can_bind_on_this_chassis(const struct
>> sbrec_chassis
>> >> *chassis_rec,
>> >> >             || !strcmp(requested_chassis, chassis_rec->hostname);
>> >> >  }
>> >> >
>> >> > -static void
>> >> > +static bool
>> >> > +release_local_binding_children(const struct sbrec_chassis
>> *chassis_rec,
>> >> > +                               struct local_binding *lbinding,
>> >> > +                               bool sb_readonly)
>> >> > +{
>> >> > +    struct shash_node *node;
>> >> > +    SHASH_FOR_EACH (node, &lbinding->children) {
>> >> > +        struct local_binding *l = node->data;
>> >> > +        if (is_lbinding_this_chassis(l, chassis_rec)) {
>> >> > +            if (!release_lport(l->pb, sb_readonly)) {
>> >> > +                return false;
>> >> > +            }
>> >> > +        }
>> >> > +    }
>> >> > +
>> >> > +    return true;
>> >> > +}
>> >> > +
>> >> > +static bool
>> >> > +release_local_binding(const struct sbrec_chassis *chassis_rec,
>> >> > +                      struct local_binding *lbinding, bool
>> sb_readonly)
>> >> > +{
>> >> > +    if (!release_local_binding_children(chassis_rec, lbinding,
>> >> > +                                        sb_readonly)) {
>> >> > +        return false;
>> >> > +    }
>> >> > +
>> >> > +    if (is_lbinding_this_chassis(lbinding, chassis_rec)) {
>> >> > +        return release_lport(lbinding->pb, sb_readonly);
>> >> > +    }
>> >> > +
>> >> > +    return true;
>> >> > +}
>> >> > +
>> >> > +static bool
>> >> >  consider_vif_lport_(const struct sbrec_port_binding *pb,
>> >> >                      bool can_bind, const char *vif_chassis,
>> >> >                      struct binding_ctx_in *b_ctx_in,
>> >> > @@ -750,7 +834,10 @@ consider_vif_lport_(const struct
>> sbrec_port_binding
>> >> *pb,
>> >> >      if (lbinding_set) {
>> >> >          if (can_bind) {
>> >> >              /* We can claim the lport. */
>> >> > -            claim_lport(pb, b_ctx_in->chassis_rec,
lbinding->iface);
>> >> > +            if (!claim_lport(pb, b_ctx_in->chassis_rec,
>> lbinding->iface,
>> >> > +                             !b_ctx_in->ovnsb_idl_txn)){
>> >> > +                return false;
>> >> > +            }
>> >> >
>> >> >
>>  add_local_datapath(b_ctx_in->sbrec_datapath_binding_by_key,
>> >> >
 b_ctx_in->sbrec_port_binding_by_datapath,
>> >> > @@ -775,13 +862,14 @@ consider_vif_lport_(const struct
>> sbrec_port_binding
>> >> *pb,
>> >> >      if (pb->chassis == b_ctx_in->chassis_rec) {
>> >> >          /* Release the lport if there is no lbinding. */
>> >> >          if (!lbinding_set || !can_bind) {
>> >> > -            release_lport(pb);
>> >> > +            return release_lport(pb, !b_ctx_in->ovnsb_idl_txn);
>> >> >          }
>> >> >      }
>> >> >
>> >> > +    return true;
>> >> >  }
>> >> >
>> >> > -static void
>> >> > +static bool
>> >> >  consider_vif_lport(const struct sbrec_port_binding *pb,
>> >> >                     struct binding_ctx_in *b_ctx_in,
>> >> >                     struct binding_ctx_out *b_ctx_out,
>> >> > @@ -801,11 +889,11 @@ consider_vif_lport(const struct
>> sbrec_port_binding
>> >> *pb,
>> >> >          lbinding->pb = pb;
>> >> >      }
>> >> >
>> >> > -    consider_vif_lport_(pb, can_bind, vif_chassis, b_ctx_in,
>> >> > -                        b_ctx_out, lbinding, qos_map);
>> >> > +    return consider_vif_lport_(pb, can_bind, vif_chassis, b_ctx_in,
>> >> > +                               b_ctx_out, lbinding, qos_map);
>> >> >  }
>> >> >
>> >> > -static void
>> >> > +static bool
>> >> >  consider_container_lport(const struct sbrec_port_binding *pb,
>> >> >                           struct binding_ctx_in *b_ctx_in,
>> >> >                           struct binding_ctx_out *b_ctx_out,
>> >> > @@ -815,7 +903,39 @@ consider_container_lport(const struct
>> >> sbrec_port_binding *pb,
>> >> >      parent_lbinding = local_binding_find(b_ctx_out->local_bindings,
>> >> >                                           pb->parent_port);
>> >> >
>> >> > -    if (parent_lbinding && !parent_lbinding->pb) {
>> >> > +    if (!parent_lbinding) {
>> >> > +        /* There is no local_binding for parent port. Create it
>> >> > +         * without OVS interface row. This is the only exception
>> >> > +         * for creating the 'struct local_binding' object without
>> >> > +         * corresponding OVS interface row.
>> >> > +         *
>> >> > +         * This is required for the following reasons:
>> >> > +         *   - If a logical port P1 is created and then
>> >> > +         *     few container ports - C1, C2, .. are created first
by
>> CMS.
>> >> > +         *   - And later when OVS interface row  is created for P1,
>> then
>> >> > +         *     we want the these container ports also be claimed by
>> the
>> >> > +         *     chassis.
>> >> > +         * */
>> >>
>> >> It seems the assumption is, for all container PBs, their parent PBs
>> should
>> >> always have local_binding created, no matter if they are bound to
current
>> >> chassis.
>> >
>> >
>> > That's right.
>> >
>> >>
>> >> However, at least some of the code in this patch would violate this
>> >> assumption, e.g. handle_iface_delete() will call
local_binding_delete()
>> and
>> >> delete the parent binding and all the children container bindings.
>> >
>> >
>> > I missed this part. I'll handle it in next version.
>> >
>> >>
>> >> It is also better to document this clearly in the comment of struct
>> >> local_binding for what should be expected in the local_bindings.
>> >
>> >
>> > I've added comments for the local_bindings struct. But it looks like
it's
>> not
>> > sufficient. I'll add more .
>> >
>> >> My
>> >> understanding is, for each binding_run() or change_handler execution,
the
>> >> expected items left in it is:
>> >> 1) For each interface that has iface-id configured.
>> >
>> > Yes.
>> >
>> >>
>> >> 2) For each container PBs and their parent PBs, no matter if they are
>> bound
>> >> to this chassis.
>> >
>> >
>> > Yes.
>> >
>> >>
>> >> 3) For some of the non VIF PBs ...
>> >
>> >
>> > Only for the "virtual" PB if it's parent is bound.
>> >
>> >>
>> >> Is this correct?
>> >>
>> >> > +        parent_lbinding = local_binding_create(pb->parent_port,
NULL,
>> >> NULL,
>> >> > +                                               BT_VIF);
>> >> > +        local_binding_add(b_ctx_out->local_bindings,
parent_lbinding);
>> >> > +    }
>> >> > +
>> >> > +    struct local_binding *container_lbinding =
>> >> > +        local_binding_find_child(parent_lbinding,
pb->logical_port);
>> >> > +
>> >> > +    if (!container_lbinding) {
>> >> > +        container_lbinding = local_binding_create(pb->logical_port,
>> >> > +
>>  parent_lbinding->iface,
>> >> > +                                                  pb,
BT_CONTAINER);
>> >> > +        local_binding_add_child(parent_lbinding,
container_lbinding);
>> >> > +    } else {
>> >> > +        ovs_assert(container_lbinding->type == BT_CONTAINER);
>> >> > +        container_lbinding->pb = pb;
>> >> > +        container_lbinding->iface = parent_lbinding->iface;
>> >> > +    }
>> >> > +
>> >> > +    if (!parent_lbinding->pb) {
>> >> >          parent_lbinding->pb = lport_lookup_by_name(
>> >> >              b_ctx_in->sbrec_port_binding_by_name, pb->parent_port);
>> >> >
>> >> > @@ -824,37 +944,28 @@ consider_container_lport(const struct
>> >> sbrec_port_binding *pb,
>> >> >               * So call consider_vif_lport() to process it first. */
>> >> >              consider_vif_lport(parent_lbinding->pb, b_ctx_in,
>> b_ctx_out,
>> >> >                                 parent_lbinding, qos_map);
>> >> > -        }
>> >> > -    }
>> >> > +        } else {
>> >> > +            /* The parent lport doesn't exist. Call
release_lport() to
>> >> > +             * release the container lport, if it was bound
earlier.
>> */
>> >> > +            if (is_lbinding_this_chassis(container_lbinding,
>> >> > +                                         b_ctx_in->chassis_rec)) {
>> >> > +               return release_lport(pb, !b_ctx_in->ovnsb_idl_txn);
>> >> > +            }
>> >> >
>> >> > -    if (!parent_lbinding || !parent_lbinding->pb) {
>> >> > -        /* Call release_lport, to release the container lport, if
>> >> > -         * it was bound earlier. */
>> >> > -        if (pb->chassis == b_ctx_in->chassis_rec) {
>> >> > -            release_lport(pb);
>> >> > +            return true;
>> >> >          }
>> >> > -        return;
>> >> >      }
>> >> >
>> >> > -    struct local_binding *container_lbinding =
>> >> > -        local_binding_find_child(parent_lbinding,
pb->logical_port);
>> >> > -    ovs_assert(!container_lbinding);
>> >> > -
>> >> > -    container_lbinding = local_binding_create(pb->logical_port,
>> >> > -
 parent_lbinding->iface,
>> >> > -                                              pb, BT_CONTAINER);
>> >> > -    local_binding_add_child(parent_lbinding, container_lbinding);
>> >> > -
>> >> >      const char *vif_chassis =
smap_get(&parent_lbinding->pb->options,
>> >> >                                         "requested-chassis");
>> >> >      bool can_bind = can_bind_on_this_chassis(b_ctx_in->chassis_rec,
>> >> >                                               vif_chassis);
>> >> >
>> >> > -    consider_vif_lport_(pb, can_bind, vif_chassis, b_ctx_in,
>> b_ctx_out,
>> >> > -                        container_lbinding, qos_map);
>> >> > +    return consider_vif_lport_(pb, can_bind, vif_chassis, b_ctx_in,
>> >> b_ctx_out,
>> >> > +                               container_lbinding, qos_map);
>> >> >  }
>> >> >
>> >> > -static void
>> >> > +static bool
>> >> >  consider_virtual_lport(const struct sbrec_port_binding *pb,
>> >> >                         struct binding_ctx_in *b_ctx_in,
>> >> >                         struct binding_ctx_out *b_ctx_out,
>> >> > @@ -881,11 +992,16 @@ consider_virtual_lport(const struct
>> >> sbrec_port_binding *pb,
>> >> >      if (is_lbinding_this_chassis(parent_lbinding,
>> >> b_ctx_in->chassis_rec)) {
>> >> >          virtual_lbinding =
>> >> >              local_binding_find_child(parent_lbinding,
>> pb->logical_port);
>> >> > -        ovs_assert(!virtual_lbinding);
>> >> > -        virtual_lbinding = local_binding_create(pb->logical_port,
>> >> > -
>>  parent_lbinding->iface,
>> >> > -                                                pb, BT_VIRTUAL);
>> >> > -        local_binding_add_child(parent_lbinding, virtual_lbinding);
>> >> > +        if (!virtual_lbinding) {
>> >> > +            virtual_lbinding =
local_binding_create(pb->logical_port,
>> >> > +
>> >>  parent_lbinding->iface,
>> >> > +                                                    pb,
BT_VIRTUAL);
>> >> > +            local_binding_add_child(parent_lbinding,
>> virtual_lbinding);
>> >> > +        } else {
>> >> > +            ovs_assert(virtual_lbinding->type == BT_VIRTUAL);
>> >> > +            virtual_lbinding->pb = pb;
>> >> > +            virtual_lbinding->iface = parent_lbinding->iface;
>> >> > +        }
>> >> >      }
>> >> >
>> >> >      return consider_vif_lport_(pb, true, NULL, b_ctx_in, b_ctx_out,
>> >> > @@ -895,14 +1011,13 @@ consider_virtual_lport(const struct
>> >> sbrec_port_binding *pb,
>> >> >  /* Considers either claiming the lport or releasing the lport
>> >> >   * for non VIF lports.
>> >> >   */
>> >> > -static void
>> >> > +static bool
>> >> >  consider_nonvif_lport_(const struct sbrec_port_binding *pb,
>> >> >                         bool our_chassis,
>> >> >                         bool has_local_l3gateway,
>> >> >                         struct binding_ctx_in *b_ctx_in,
>> >> >                         struct binding_ctx_out *b_ctx_out)
>> >> >  {
>> >> > -    ovs_assert(b_ctx_in->ovnsb_idl_txn);
>> >> >      if (our_chassis) {
>> >> >          sset_add(b_ctx_out->local_lports, pb->logical_port);
>> >> >          add_local_datapath(b_ctx_in->sbrec_datapath_binding_by_key,
>> >> > @@ -912,13 +1027,16 @@ consider_nonvif_lport_(const struct
>> >> sbrec_port_binding *pb,
>> >> >                             b_ctx_out->local_datapaths);
>> >> >
>> >> >          update_local_lport_ids(b_ctx_out->local_lport_ids, pb);
>> >> > -        claim_lport(pb, b_ctx_in->chassis_rec, NULL);
>> >> > +        return claim_lport(pb, b_ctx_in->chassis_rec, NULL,
>> >> > +                           !b_ctx_in->ovnsb_idl_txn);
>> >> >      } else if (pb->chassis == b_ctx_in->chassis_rec) {
>> >> > -        release_lport(pb);
>> >> > +        return release_lport(pb, !b_ctx_in->ovnsb_idl_txn);
>> >> >      }
>> >> > +
>> >> > +    return true;
>> >> >  }
>> >> >
>> >> > -static void
>> >> > +static bool
>> >> >  consider_l2gw_lport(const struct sbrec_port_binding *pb,
>> >> >                      struct binding_ctx_in *b_ctx_in,
>> >> >                      struct binding_ctx_out *b_ctx_out)
>> >> > @@ -927,10 +1045,10 @@ consider_l2gw_lport(const struct
>> >> sbrec_port_binding *pb,
>> >> >      bool our_chassis = chassis_id && !strcmp(chassis_id,
>> >> >
>> >> b_ctx_in->chassis_rec->name);
>> >> >
>> >> > -    consider_nonvif_lport_(pb, our_chassis, false, b_ctx_in,
>> b_ctx_out);
>> >> > +    return consider_nonvif_lport_(pb, our_chassis, false, b_ctx_in,
>> >> b_ctx_out);
>> >> >  }
>> >> >
>> >> > -static void
>> >> > +static bool
>> >> >  consider_l3gw_lport(const struct sbrec_port_binding *pb,
>> >> >                      struct binding_ctx_in *b_ctx_in,
>> >> >                      struct binding_ctx_out *b_ctx_out)
>> >> > @@ -939,7 +1057,7 @@ consider_l3gw_lport(const struct
>> sbrec_port_binding
>> >> *pb,
>> >> >      bool our_chassis = chassis_id && !strcmp(chassis_id,
>> >> >
>> >> b_ctx_in->chassis_rec->name);
>> >> >
>> >> > -    consider_nonvif_lport_(pb, our_chassis, true, b_ctx_in,
>> b_ctx_out);
>> >> > +    return consider_nonvif_lport_(pb, our_chassis, true, b_ctx_in,
>> >> b_ctx_out);
>> >> >  }
>> >> >
>> >> >  static void
>> >> > @@ -958,7 +1076,7 @@ consider_localnet_lport(const struct
>> >> sbrec_port_binding *pb,
>> >> >      update_local_lport_ids(b_ctx_out->local_lport_ids, pb);
>> >> >  }
>> >> >
>> >> > -static void
>> >> > +static bool
>> >> >  consider_ha_lport(const struct sbrec_port_binding *pb,
>> >> >                    struct binding_ctx_in *b_ctx_in,
>> >> >                    struct binding_ctx_out *b_ctx_out)
>> >> > @@ -986,18 +1104,18 @@ consider_ha_lport(const struct
>> sbrec_port_binding
>> >> *pb,
>> >> >                             b_ctx_out->local_datapaths);
>> >> >      }
>> >> >
>> >> > -    consider_nonvif_lport_(pb, our_chassis, false, b_ctx_in,
>> b_ctx_out);
>> >> > +    return consider_nonvif_lport_(pb, our_chassis, false, b_ctx_in,
>> >> b_ctx_out);
>> >> >  }
>> >> >
>> >> > -static void
>> >> > +static bool
>> >> >  consider_cr_lport(const struct sbrec_port_binding *pb,
>> >> >                    struct binding_ctx_in *b_ctx_in,
>> >> >                    struct binding_ctx_out *b_ctx_out)
>> >> >  {
>> >> > -    consider_ha_lport(pb, b_ctx_in, b_ctx_out);
>> >> > +    return consider_ha_lport(pb, b_ctx_in, b_ctx_out);
>> >> >  }
>> >> >
>> >> > -static void
>> >> > +static bool
>> >> >  consider_external_lport(const struct sbrec_port_binding *pb,
>> >> >                          struct binding_ctx_in *b_ctx_in,
>> >> >                          struct binding_ctx_out *b_ctx_out)
>> >> > @@ -1050,6 +1168,8 @@ build_local_bindings(struct binding_ctx_in
>> >> *b_ctx_in,
>> >> >                  }
>> >> >
>> >> >                  sset_add(b_ctx_out->local_lports, iface_id);
>> >> > +                smap_replace(b_ctx_out->local_iface_ids,
>> iface_rec->name,
>> >> > +                             iface_id);
>> >> >              }
>> >> >
>> >> >              /* Check if this is a tunnel interface. */
>> >> > @@ -1165,9 +1285,9 @@ binding_run(struct binding_ctx_in *b_ctx_in,
>> struct
>> >> binding_ctx_out *b_ctx_out)
>> >> >       * corresponding local datapath accordingly. */
>> >> >      struct localnet_lport *lnet_lport;
>> >> >      LIST_FOR_EACH_POP (lnet_lport, list_node, &localnet_lports) {
>> >> > -        consider_localnet_port(lnet_lport->pb, &bridge_mappings,
>> >> > -                               b_ctx_out->egress_ifaces,
>> >> > -                               b_ctx_out->local_datapaths);
>> >> > +        update_ld_localnet_port(lnet_lport->pb, &bridge_mappings,
>> >> > +                                b_ctx_out->egress_ifaces,
>> >> > +                                b_ctx_out->local_datapaths);
>> >> >          free(lnet_lport);
>> >> >      }
>> >> >
>> >> > @@ -1185,95 +1305,625 @@ binding_run(struct binding_ctx_in
*b_ctx_in,
>> >> struct binding_ctx_out *b_ctx_out)
>> >> >      destroy_qos_map(&qos_map);
>> >> >  }
>> >> >
>> >> > -/* Returns true if port-binding changes potentially require flow
>> changes
>> >> on
>> >> > - * the current chassis. Returns false if we are sure there is no
>> impact.
>> >> */
>> >> > +/* Returns true if the database is all cleaned up, false if more
work
>> is
>> >> > + * required. */
>> >> >  bool
>> >> > -binding_evaluate_port_binding_changes(struct binding_ctx_in
*b_ctx_in,
>> >> > -                                      struct binding_ctx_out
>> *b_ctx_out)
>> >> > +binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn,
>> >> > +                const struct sbrec_port_binding_table
>> >> *port_binding_table,
>> >> > +                const struct sbrec_chassis *chassis_rec)
>> >> >  {
>> >> > -    if (!b_ctx_in->chassis_rec) {
>> >> > +    if (!ovnsb_idl_txn) {
>> >> > +        return false;
>> >> > +    }
>> >> > +    if (!chassis_rec) {
>> >> >          return true;
>> >> >      }
>> >> >
>> >> > -    bool changed = false;
>> >> > -
>> >> >      const struct sbrec_port_binding *binding_rec;
>> >> > -    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (binding_rec,
>> >> > -
>> >> b_ctx_in->port_binding_table) {
>> >> > -        /* XXX: currently OVSDB change tracking doesn't support
>> getting
>> >> old
>> >> > -         * data when the operation is update, so if a port-binding
>> moved
>> >> from
>> >> > -         * this chassis to another, there is no easy way to find
out
>> the
>> >> > -         * change. To workaround this problem, we just makes sure
if
>> >> > -         * any port *related to* this chassis has any change, then
>> >> trigger
>> >> > -         * recompute.
>> >> > -         *
>> >> > -         * - If a regular VIF is unbound from this chassis, the
local
>> >> ovsdb
>> >> > -         *   interface table will be updated, which will trigger
>> >> recompute.
>> >> > -         *
>> >> > -         * - If the port is not a regular VIF, always trigger
>> recompute.
>> >> */
>> >> > -        if (binding_rec->chassis == b_ctx_in->chassis_rec) {
>> >> > -            changed = true;
>> >> > +    bool any_changes = false;
>> >> > +    SBREC_PORT_BINDING_TABLE_FOR_EACH (binding_rec,
>> port_binding_table) {
>> >> > +        if (binding_rec->chassis == chassis_rec) {
>> >> > +            if (binding_rec->encap) {
>> >> > +                sbrec_port_binding_set_encap(binding_rec, NULL);
>> >> > +            }
>> >> > +            sbrec_port_binding_set_chassis(binding_rec, NULL);
>> >> > +            any_changes = true;
>> >> > +        }
>> >> > +    }
>> >> > +
>> >> > +    if (any_changes) {
>> >> > +        ovsdb_idl_txn_add_comment(
>> >> > +            ovnsb_idl_txn,
>> >> > +            "ovn-controller: removing all port bindings for '%s'",
>> >> > +            chassis_rec->name);
>> >> > +    }
>> >> > +
>> >> > +    return !any_changes;
>> >> > +}
>> >> > +
>> >> > +static void
>> >> > +add_local_datapath_peer_port(const struct sbrec_port_binding *pb,
>> >> > +                             struct binding_ctx_in *b_ctx_in,
>> >> > +                             struct binding_ctx_out *b_ctx_out,
>> >> > +                             struct local_datapath *ld)
>> >> > +{
>> >> > +    const char *peer_name = smap_get(&pb->options, "peer");
>> >> > +    if (strcmp(pb->type, "patch") || !peer_name) {
>> >> > +        return;
>> >> > +    }
>> >> > +
>> >> > +    const struct sbrec_port_binding *peer;
>> >> > +    peer =
lport_lookup_by_name(b_ctx_in->sbrec_port_binding_by_name,
>> >> > +                                peer_name);
>> >> > +
>> >> > +    if (!peer || !peer->datapath) {
>> >> > +        return;
>> >> > +    }
>> >> > +
>> >> > +    bool present = false;
>> >> > +    for (size_t i = 0; i < ld->n_peer_ports; i++) {
>> >> > +        if (ld->peer_ports[i].local == pb) {
>> >> > +            present = true;
>> >> >              break;
>> >> >          }
>> >> > +    }
>> >> >
>> >> > -        if (!strcmp(binding_rec->type, "remote")) {
>> >> > -            continue;
>> >> > +    if (!present) {
>> >> > +        ld->n_peer_ports++;
>> >> > +        if (ld->n_peer_ports > ld->n_allocated_peer_ports) {
>> >> > +            ld->peer_ports =
>> >> > +                x2nrealloc(ld->peer_ports,
>> >> > +                           &ld->n_allocated_peer_ports,
>> >> > +                           sizeof *ld->peer_ports);
>> >> > +        }
>> >> > +        ld->peer_ports[ld->n_peer_ports - 1].local = pb;
>> >> > +        ld->peer_ports[ld->n_peer_ports - 1].remote = peer;
>> >> > +    }
>> >> > +
>> >> > +    struct local_datapath *peer_ld =
>> >> > +        get_local_datapath(b_ctx_out->local_datapaths,
>> >> > +                           peer->datapath->tunnel_key);
>> >> > +    if (!peer_ld) {
>> >> > +
 add_local_datapath__(b_ctx_in->sbrec_datapath_binding_by_key,
>> >> > +
b_ctx_in->sbrec_port_binding_by_datapath,
>> >> > +                             b_ctx_in->sbrec_port_binding_by_name,
>> >> > +                             peer->datapath, false,
>> >> > +                             1, b_ctx_out->local_datapaths);
>> >> > +        return;
>> >> > +    }
>> >> > +
>> >> > +    for (size_t i = 0; i < peer_ld->n_peer_ports; i++) {
>> >> > +        if (peer_ld->peer_ports[i].local == peer) {
>> >> > +            return;
>> >> >          }
>> >> > +    }
>> >> >
>> >> > -        if (strcmp(binding_rec->type, "")) {
>> >> > -            changed = true;
>> >> > +    peer_ld->n_peer_ports++;
>> >> > +    if (peer_ld->n_peer_ports > peer_ld->n_allocated_peer_ports) {
>> >> > +        peer_ld->peer_ports =
>> >> > +            x2nrealloc(peer_ld->peer_ports,
>> >> > +                        &peer_ld->n_allocated_peer_ports,
>> >> > +                        sizeof *peer_ld->peer_ports);
>> >> > +    }
>> >> > +    peer_ld->peer_ports[peer_ld->n_peer_ports - 1].local = peer;
>> >> > +    peer_ld->peer_ports[peer_ld->n_peer_ports - 1].remote = pb;
>> >> > +}
>> >> > +
>> >> > +static void
>> >> > +remove_local_datapath_peer_port(const struct sbrec_port_binding
*pb,
>> >> > +                                struct local_datapath *ld,
>> >> > +                                struct hmap *local_datapaths)
>> >> > +{
>> >> > +    size_t i = 0;
>> >> > +    for (i = 0; i < ld->n_peer_ports; i++) {
>> >> > +        if (ld->peer_ports[i].local == pb) {
>> >> >              break;
>> >> >          }
>> >> > +    }
>> >> > +
>> >> > +    if (i == ld->n_peer_ports) {
>> >> > +        return;
>> >> > +    }
>> >> > +
>> >> > +    const struct sbrec_port_binding *peer =
ld->peer_ports[i].remote;
>> >> >
>> >> > -        struct local_binding *lbinding = NULL;
>> >> > -        if (!binding_rec->parent_port ||
>> !binding_rec->parent_port[0]) {
>> >> > -            lbinding =
local_binding_find(b_ctx_out->local_bindings,
>> >> > -
 binding_rec->logical_port);
>> >> > +    /* Possible improvement: We can shrint the allocated peer ports
>> >> > +     * if (ld->n_peer_ports < ld->n_allocated_peer_ports / 2).
>> >> > +     */
>> >> > +    ld->peer_ports[i].local = ld->peer_ports[ld->n_peer_ports -
>> 1].local;
>> >> > +    ld->peer_ports[i].remote = ld->peer_ports[ld->n_peer_ports -
>> >> 1].remote;
>> >> > +    ld->n_peer_ports--;
>> >> > +
>> >> > +    struct local_datapath *peer_ld =
>> >> > +        get_local_datapath(local_datapaths,
>> peer->datapath->tunnel_key);
>> >> > +    if (peer_ld) {
>> >> > +        remove_local_datapath_peer_port(peer, peer_ld,
>> local_datapaths);
>> >>
>> >> Sorry that I don't understand this part. peer's peer is the "pb" that
is
>> >> passed in the parameter of the current function call, so this
recursive
>> >> call would only try to remove the "pb", which is already removed
(before
>> >> calling this function). So I think it is useless.
>> >
>> >
>> > Let's say there is sw0-lr0 of sw0 connected to the peer lr0-sw0 of lr0.
>> >
>> > When this function is called for sw0-lr0, it removes the peer ports
>> sw0-lr0 from
>> > the 'sw0' ld and then it calls recursively for lr0.
>> >
>> > Yes you're right. It will again call for sw0-lr0 of sw0. But that would
>> be a no-op as
>> > the for loop in the beginning cannot find sw0-lr0  in 'sw0' ld.. Even
>> though it
>> > is useless are there any issues with it ? Since its recursion and it
has
>> proper return
>> > conditions, I think it should be fine.
>> >
>> > Another option is duplicating the code for removing the peer.
>> >
>>
>> OK, I thought it is useless, but it seems needed.
>>
>> >
>> >>
>> >> > +    }
>> >> > +}
>> >> > +
>> >> > +static void
>> >> > +remove_pb_from_local_datapath(const struct sbrec_port_binding *pb,
>> >> > +                              const struct sbrec_chassis
*chassis_rec,
>> >> > +                              struct binding_ctx_out *b_ctx_out,
>> >> > +                              struct local_datapath *ld)
>> >> > +{
>> >> > +    remove_local_lport_ids(pb, b_ctx_out->local_lport_ids);
>> >> > +    if (!strcmp(pb->type, "patch") ||
>> >> > +        !strcmp(pb->type, "l3gateway")) {
>> >> > +        remove_local_datapath_peer_port(pb, ld,
>> >> b_ctx_out->local_datapaths);
>> >> > +    } else if (!strcmp(pb->type, "localnet")) {
>> >> > +        if (ld->localnet_port &&
>> !strcmp(ld->localnet_port->logical_port,
>> >> > +                                         pb->logical_port)) {
>> >> > +            ld->localnet_port = NULL;
>> >> > +        }
>> >> > +    } else if (!strcmp(pb->type, "l3gateway")) {
>> >> > +        const char *chassis_id = smap_get(&pb->options,
>> >> > +                                          "l3gateway-chassis");
>> >> > +        if (chassis_id && !strcmp(chassis_id, chassis_rec->name)) {
>> >> > +            ld->has_local_l3gateway = false;
>> >> > +        }
>> >> > +    }
>> >>
>> >> I think we need to remove ld from the local_datapaths if the current
pb
>> is
>> >> the only reason for that ld to be exist on this chassis. Also, all the
>> >> related lds connected by patch ports should be removed as well, if
there
>> >> are no other pbs causing them to be added to this chassis. This is the
>> >> reverse of add_local_datapath__(). It doesn't seem to be easy without
>> going
>> >> through all local_lbindings. Maybe this can be done in
>> >> binding_handle_ovs_interface_changes() and
>> >> binding_handle_port_binding_changes() out of the loop.
>> >>
>> >
>> > I left this unaddressed intentionally as I think it will complicate the
>> code.
>> >
>> > Patch 3 of this series handles I-P for datapath and when a datapath is
>> deleted,
>> > it removes the datapath from local_datapaths if it is present.
>> > I think this should be Ok as we will eventually free it. If we really
>> want to address this,
>> > I'm fine with it but I'd prefer a follow up patch to do this.
>> >
>> OK, maybe a comment can be added to make it clear for future improvement.
>> This changes the behavior of current implementation. Now unrelated
>> datapaths are removed when certain ports are unbound, and the flows in
the
>> HV can be largely reduced, but with this change it may not reduce any
more
>> until a recompute is triggered. It is not a big problem I think.
>
>
> Sure. I'll add a comment about it. I was also thinking of adding the same
in TODO.rst.
> But it looks like TODO.rst needs some cleaning first.
>
>>
>> >
>> >>
>> >> > +}
>> >> > +
>> >> > +static bool
>> >> > +handle_iface_add(const struct ovsrec_interface *iface_rec,
>> >> > +                 const char *iface_id,
>> >> > +                 struct binding_ctx_in *b_ctx_in,
>> >> > +                 struct binding_ctx_out *b_ctx_out,
>> >> > +                 struct hmap *qos_map)
>> >> > +{
>> >> > +    sset_add(b_ctx_out->local_lports, iface_id);
>> >> > +    smap_replace(b_ctx_out->local_iface_ids, iface_rec->name,
>> iface_id);
>> >> > +
>> >> > +    struct local_binding *lbinding =
>> >> > +        local_binding_find(b_ctx_out->local_bindings, iface_id);
>> >> > +
>> >> > +    if (!lbinding) {
>> >> > +        lbinding = local_binding_create(iface_id, iface_rec, NULL,
>> >> BT_VIF);
>> >> > +        local_binding_add(b_ctx_out->local_bindings, lbinding);
>> >> > +    } else {
>> >> > +        lbinding->iface = iface_rec;
>> >> > +    }
>> >> > +
>> >> > +    if (!lbinding->pb || strcmp(lbinding->name,
>> >> lbinding->pb->logical_port)) {
>> >> > +        lbinding->pb = lport_lookup_by_name(
>> >> > +            b_ctx_in->sbrec_port_binding_by_name, lbinding->name);
>> >> > +        if (lbinding->pb && !strcmp(lbinding->pb->type,
"virtual")) {
>> >>
>> >> Since we never bind "virtual" ports, this should be a
misconfiguration,
>> and
>> >> it is better to log a warning message to be more clear.
>> >>
>> >> > +            lbinding->pb = NULL;
>> >> > +        }
>> >> > +    }
>> >> > +
>> >> > +    if (lbinding->pb) {
>> >> > +        if (!consider_vif_lport(lbinding->pb, b_ctx_in, b_ctx_out,
>> >> > +                                lbinding, qos_map)) {
>> >> > +            return false;
>> >> > +        }
>> >> > +    }
>> >> > +
>> >> > +    /* Update the child local_binding's iface (if any children) and
>> try
>> >> to
>> >> > +     *  claim the container lbindings. */
>> >> > +    struct shash_node *node;
>> >> > +    SHASH_FOR_EACH (node, &lbinding->children) {
>> >> > +        struct local_binding *child = node->data;
>> >> > +        child->iface = iface_rec;
>> >> > +        if (child->type == BT_CONTAINER) {
>> >> > +            if (!consider_container_lport(child->pb, b_ctx_in,
>> b_ctx_out,
>> >> > +                                          qos_map)) {
>> >> > +                return false;
>> >> > +            }
>> >> > +        }
>> >> > +    }
>> >> > +
>> >> > +    return true;
>> >> > +}
>> >> > +
>> >> > +static bool
>> >> > +handle_iface_delete(const char *lport_name, const char *iface_name,
>> >> > +                    struct binding_ctx_in *b_ctx_in,
>> >> > +                    struct binding_ctx_out *b_ctx_out, bool
*changed)
>> >> > +{
>> >> > +    struct local_binding *lbinding;
>> >> > +    lbinding = local_binding_find(b_ctx_out->local_bindings,
>> >> > +                                  lport_name);
>> >> > +    if (lbinding && lbinding->pb &&
>> >> > +        lbinding->pb->chassis == b_ctx_in->chassis_rec) {
>> >> > +
>> >> > +        if (!release_local_binding(b_ctx_in->chassis_rec, lbinding,
>> >> > +                                   !b_ctx_in->ovnsb_idl_txn)) {
>> >> > +            return false;
>> >> > +        }
>> >> > +
>> >> > +        struct local_datapath *ld =
>> >> > +            get_local_datapath(b_ctx_out->local_datapaths,
>> >> > +
 lbinding->pb->datapath->tunnel_key);
>> >> > +        if (ld) {
>> >> > +            remove_pb_from_local_datapath(lbinding->pb,
>> >> > +                                            b_ctx_in->chassis_rec,
>> >> > +                                            b_ctx_out, ld);
>> >> > +        }
>> >> > +        local_binding_delete(b_ctx_out->local_bindings, lbinding);
>> >> > +        *changed = true;
>> >> > +    }
>> >> > +
>> >> > +    sset_find_and_delete(b_ctx_out->local_lports, lport_name);
>> >> > +    smap_remove(b_ctx_out->local_iface_ids, iface_name);
>> >> > +
>> >> > +    return true;
>> >> > +}
>> >> > +
>> >> > +static bool
>> >> > +is_iface_vif(const struct ovsrec_interface *iface_rec)
>> >> > +{
>> >> > +    if (iface_rec->type && iface_rec->type[0] &&
>> >> > +        strcmp(iface_rec->type, "internal")) {
>> >> > +        return false;
>> >> > +    }
>> >> > +
>> >> > +    return true;
>> >> > +}
>> >> > +
>> >> > +/* Returns true if the ovs interface changes were handled
>> successfully,
>> >> > + * false otherwise.
>> >> > + */
>> >> > +bool
>> >> > +binding_handle_ovs_interface_changes(struct binding_ctx_in
*b_ctx_in,
>> >> > +                                     struct binding_ctx_out
>> *b_ctx_out,
>> >> > +                                     bool *changed)
>> >> > +{
>> >> > +    if (!b_ctx_in->chassis_rec) {
>> >> > +        return false;
>> >> > +    }
>> >> > +
>> >> > +    bool handled = true;
>> >> > +    *changed = false;
>> >> > +
>> >> > +    /* Run the tracked interfaces loop twice. One to handle deleted
>> >> > +     * changes. And another to handle add/update changes.
>> >> > +     * This will ensure correctness.
>> >> > +     */
>> >> > +    const struct ovsrec_interface *iface_rec;
>> >> > +    OVSREC_INTERFACE_TABLE_FOR_EACH_TRACKED (iface_rec,
>> >> > +
b_ctx_in->iface_table) {
>> >> > +        if (!is_iface_vif(iface_rec)) {
>> >> > +            /* Right now are not handling ovs_interface changes of
>> >> > +             * other types. This can be enhanced to handle of
>> >> > +             * types - patch and tunnel. */
>> >> > +            handled = false;
>> >> > +            break;
>> >> > +        }
>> >> > +
>> >> > +        const char *iface_id = smap_get(&iface_rec->external_ids,
>> >> "iface-id");
>> >> > +        const char *old_iface_id =
>> smap_get(b_ctx_out->local_iface_ids,
>> >> > +                                            iface_rec->name);
>> >> > +        const char *cleared_iface_id = NULL;
>> >> > +        if (!ovsrec_interface_is_deleted(iface_rec)) {
>> >> > +            if (iface_id) {
>> >> > +                /* Check if iface_id is changed. If so we need to
>> >> > +                 * release the old port binding and associate this
>> >> > +                 * inteface to new port binding. */
>> >> > +                if (old_iface_id && strcmp(iface_id,
old_iface_id)) {
>> >> > +                    cleared_iface_id = old_iface_id;
>> >> > +                }
>> >> > +            } else if (old_iface_id) {
>> >> > +                cleared_iface_id = old_iface_id;
>> >> > +            }
>> >> >          } else {
>> >> > -            lbinding =
local_binding_find(b_ctx_out->local_bindings,
>> >> > -
 binding_rec->parent_port);
>> >> > +            cleared_iface_id = iface_id;
>> >> > +            iface_id = NULL;
>> >> >          }
>> >> >
>> >> > -        if (lbinding) {
>> >> > -            changed = true;
>> >> > +        if (cleared_iface_id) {
>> >> > +            handled = handle_iface_delete(cleared_iface_id,
>> >> iface_rec->name,
>> >> > +                                          b_ctx_in, b_ctx_out,
>> changed);
>> >> > +        }
>> >> > +
>> >> > +        if (!handled) {
>> >> >              break;
>> >> >          }
>> >> >      }
>> >> >
>> >> > -    return changed;
>> >> > +    if (!handled) {
>> >> > +        return false;
>> >> > +    }
>> >> > +
>> >> > +    struct hmap qos_map = HMAP_INITIALIZER(&qos_map);
>> >> > +    struct hmap *qos_map_ptr =
>> >> > +        sset_is_empty(b_ctx_out->egress_ifaces) ? NULL : &qos_map;
>> >> > +
>> >> > +    OVSREC_INTERFACE_TABLE_FOR_EACH_TRACKED (iface_rec,
>> >> > +
b_ctx_in->iface_table) {
>> >> > +        /* Loop to handle create and update changes only. */
>> >>
>> >> Thanks for splitting the loops, but here are still some problems.
>> >>
>> >> 1. The first loop handled: a) interface deletion. b) interface update
>> with
>> >> old iface-id cleared. So in this second loop it should handle all the
>> rest
>> >> cases: a) interface add. b) interface update without iface-id change.
It
>> >> seems b) is treated as just interface add, which may be wrong: if
>> iface-id
>> >> didn't change but some other fields of the interface record is
changed,
>> it
>> >> is not handled properly.
>> >
>> >
>> > If other fields change, what should we handle ? Can ofport change ?
>> > I'll see if ofport can be changed and what happens when it changes.
>> >
>> > For other fields like name, mtu etc do we need to handle anything ?
>> > I think we only care about the external_ids:iface-id and the ofport.
>> > The function handle_iface_add() even if it is called for other changes,
>> > it will be a no-op.
>> >
>> > Also from the tracking we can't figure out what fields change right ?
>> >
>> >
>> Yes, I think it is hard to check with fields are changed and handle it
>> accordingly. That's why I suggest to make it simple: if there is an
update,
>> always handle it like a deletion and then creation. This way, we dont
need
>> to consider all the corner cases and we are sure it is correct.
>
>
> In this particular case of binding, I don't think there are any corner
cases..
>
> We consider an OVS interface for binding if the below 2 conditions meet
>
> 1. external_ids:iface-id is set
> 2. ofport is > 0.
>
> Otherwise we need to consider a release if it was already claimed.
>
> When  an ovs interface is updated like below
>  $ovs-vsctl set interface p1 external_ids:foo=bar
>
> We don't need to handle it like a deletion and then a creation. Because
we can
> easily see that the above conditions are met,
>
What you described is correct (I believe). However, it is the declarative
way of thinking, which is perfect for the always recompute implementation,
but it is hard to reason the correctness and to maintain in longer term for
incremental processing. Incremental processing focuses on the data change
instead of the desired status. In this case you may have already thought
through and sure about the correctness even some data change is ignored.
However, in the long run, it is hard to remember all the details about
which field change needs to be handled in which way, and why some other
changes are ignored. More importantly, when a developer who is not so
familiar with this changle handling logic want to add a new feature, which
requires another field, e.g. another key-value in external_ids, it is easy
to forget to update here, and not easy to catch in code review either, and
even we do aware it needs to be updated here, the if condition would get so
complex even with just 3 fields to be considered. This is why I think we
should make it "simple and stupid" to handle updates as if the old one was
deleted and then a new one is added. At least this has provided me a lot of
convenience in the earlier I-P implementations, and I was even surprised
that there were so many new features added but most of them didn't needed
to be aware of the I-P details, thanks to this principle. I hope this
clarifies the idea behind.

> It also creates unnecessary deletion and re-adding of OVS flows related
to that
> logical port when we handle flow computation for changed runtime data.
>
> If we handle this way, there won't be any runtime data changes.
>

I agree it may be less optimized, but considering that we are incrementally
processing the changes here, even if there are many interfaces deteled and
added unnecesarily, the cost of I-P for such changes is just nothing
comparing with the full recompute. I believe it would be good enough from
the end user point of view, if we can make it incrementally processed,
correctly.


> Thanks
> Numan
>
>>
>> >
>> >>
>> >>
>> >> 2. Some other condition checks should be done for this loop, such as:
if
>> >> (!is_iface_vif(iface_rec)) ...
>> >
>> >
>> > This is not required because the first loop checks for it and if there
is
>> any
>> > non vif interface change, then this function returns false, before the
>> > second loop.
>> >
>> OK.
>>
>> >>
>> >>
>> >> > +        if (ovsrec_interface_is_deleted(iface_rec)) {
>> >> > +            continue;
>> >> > +        }
>> >> > +
>> >> > +        const char *iface_id = smap_get(&iface_rec->external_ids,
>> >> "iface-id");
>> >> > +        int64_t ofport = iface_rec->n_ofport ? *iface_rec->ofport
: 0;
>> >> > +        if (iface_id && ofport > 0) {
>> >>
>> >> What if iface-id didn't change, but ofport changed from non-zero to 0?
>> How
>> >> should we handle the change?
>> >>
>> >
>> > Good point. I think we should consider this as  handle_iface_delete.
>> >
>> > I'll rename the function handle_iface_delete() to
handle_iface_release()
>> > and handle_iface_add() to handle_iface_claim() to be more clear.
>> >
>> >> A general approach can be: whenever there is an update
>> >> (!ovsrec_interface_is_new()), we firstly handle it as interface
delete,
>> and
>> >> then handle an interface add.
>> >
>> >
>> > But why delete and add if can determine that it is going to be either
one
>> > i.e release or claim and I think we can definitely determine.
>> > Also in patch 6/7 where we do tracking, it becomes a bit complicated
>> > if we handle it as delete and then add as we may add 2 such tracking
>> > data.
>> >
>> It is for simplicity and correctness. Please see the comment above.
>>
>> > Thanks
>> > Numan
>> >
>> >>
>> >> > +            *changed = true;
>> >> > +            handled = handle_iface_add(iface_rec, iface_id,
b_ctx_in,
>> >> > +                                       b_ctx_out, qos_map_ptr);
>> >> > +            if (!handled) {
>> >> > +                break;
>> >> > +            }
>> >> > +        }
>> >> > +    }
>> >> > +
>> >> > +    if (handled && qos_map_ptr &&
set_noop_qos(b_ctx_in->ovs_idl_txn,
>> >> > +
b_ctx_in->port_table,
>> >> > +                                               b_ctx_in->qos_table,
>> >> > +
>> >> b_ctx_out->egress_ifaces)) {
>> >> > +        const char *entry;
>> >> > +        SSET_FOR_EACH (entry, b_ctx_out->egress_ifaces) {
>> >> > +            setup_qos(entry, &qos_map);
>> >> > +        }
>> >> > +    }
>> >> > +
>> >> > +    destroy_qos_map(&qos_map);
>> >> > +    return handled;
>> >> >  }
>> >> >
>> >> > -/* Returns true if the database is all cleaned up, false if more
work
>> is
>> >> > - * required. */
>> >> > -bool
>> >> > -binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn,
>> >> > -                const struct sbrec_port_binding_table
>> >> *port_binding_table,
>> >> > -                const struct sbrec_chassis *chassis_rec)
>> >> > +static void
>> >> > +handle_deleted_lport(const struct sbrec_port_binding *pb,
>> >> > +                     struct binding_ctx_in *b_ctx_in,
>> >> > +                     struct binding_ctx_out *b_ctx_out)
>> >> >  {
>> >> > -    if (!ovnsb_idl_txn) {
>> >> > +    struct local_datapath *ld =
>> >> > +        get_local_datapath(b_ctx_out->local_datapaths,
>> >> > +                           pb->datapath->tunnel_key);
>> >> > +    if (ld) {
>> >> > +        remove_pb_from_local_datapath(pb, b_ctx_in->chassis_rec,
>> >> > +                                      b_ctx_out, ld);
>> >> > +    }
>> >> > +}
>> >> > +
>> >> > +static struct local_binding *
>> >> > +get_lbinding_for_lport(const struct sbrec_port_binding *pb,
>> >> > +                       enum en_lport_type lport_type,
>> >> > +                       struct binding_ctx_out *b_ctx_out)
>> >> > +{
>> >> > +    ovs_assert(lport_type == LP_VIF || lport_type == LP_VIRTUAL);
>> >> > +
>> >> > +    if (lport_type == LP_VIF && !is_lport_container(pb)) {
>> >> > +        return local_binding_find(b_ctx_out->local_bindings,
>> >> pb->logical_port);
>> >> > +    }
>> >> > +
>> >> > +    struct local_binding *parent_lbinding = NULL;
>> >> > +
>> >> > +    if (lport_type == LP_VIRTUAL) {
>> >> > +        parent_lbinding =
>> local_binding_find(b_ctx_out->local_bindings,
>> >> > +                                             pb->virtual_parent);
>> >> > +    } else {
>> >> > +        parent_lbinding =
>> local_binding_find(b_ctx_out->local_bindings,
>> >> > +                                             pb->parent_port);
>> >> > +    }
>> >> > +
>> >> > +    return parent_lbinding
>> >> > +           ? local_binding_find(&parent_lbinding->children,
>> >> pb->logical_port)
>> >> > +           : NULL;
>> >> > +}
>> >> > +
>> >> > +static bool
>> >> > +handle_deleted_vif_lport(const struct sbrec_port_binding *pb,
>> >> > +                         enum en_lport_type lport_type,
>> >> > +                         struct binding_ctx_in *b_ctx_in,
>> >> > +                         struct binding_ctx_out *b_ctx_out,
>> >> > +                         bool *changed)
>> >> > +{
>> >> > +    struct local_binding *lbinding =
>> >> > +        get_lbinding_for_lport(pb, lport_type, b_ctx_out);
>> >> > +
>> >> > +    if (lbinding) {
>> >> > +        lbinding->pb = NULL;
>> >> > +        /* The port_binding 'pb' is deleted. So there is no need to
>> >> > +         * clear the 'chassis' column of 'pb'. But we need to do
>> >> > +         * for the local_binding's children. */
>> >> > +        if (lbinding->type == BT_VIF &&
>> >> > +
 !release_local_binding_children(b_ctx_in->chassis_rec,
>> >> > +                                                lbinding,
>> >> > +
>> >>  !b_ctx_in->ovnsb_idl_txn)) {
>> >> > +            return false;
>> >> > +        }
>> >> > +
>> >> > +        *changed = true;
>> >> > +    }
>> >> > +
>> >> > +    handle_deleted_lport(pb, b_ctx_in, b_ctx_out);
>> >> > +    return true;
>> >> > +}
>> >> > +
>> >> > +static bool
>> >> > +handle_updated_vif_lport(const struct sbrec_port_binding *pb,
>> >> > +                         enum en_lport_type lport_type,
>> >> > +                         struct binding_ctx_in *b_ctx_in,
>> >> > +                         struct binding_ctx_out *b_ctx_out,
>> >> > +                         struct hmap *qos_map,
>> >> > +                         bool *changed)
>> >> > +{
>> >> > +    bool claimed = (pb->chassis == b_ctx_in->chassis_rec);
>> >> > +    bool handled = true;
>> >> > +
>> >> > +    if (lport_type == LP_VIRTUAL) {
>> >> > +        handled = consider_virtual_lport(pb, b_ctx_in, b_ctx_out,
>> >> qos_map);
>> >> > +    } else if (lport_type == LP_VIF && is_lport_container(pb)) {
>> >> > +        handled = consider_container_lport(pb, b_ctx_in, b_ctx_out,
>> >> qos_map);
>> >> > +    } else {
>> >> > +        handled = consider_vif_lport(pb, b_ctx_in, b_ctx_out, NULL,
>> >> qos_map);
>> >> > +    }
>> >> > +
>> >> > +    if (!handled) {
>> >> >          return false;
>> >> >      }
>> >> > -    if (!chassis_rec) {
>> >> > +
>> >> > +    bool now_claimed = (pb->chassis == b_ctx_in->chassis_rec);
>> >> > +    bool changed_ = (claimed != now_claimed);
>> >> > +
>> >> > +    if (changed_) {
>> >> > +        *changed = true;
>> >> > +    }
>> >> > +
>> >> > +    if (lport_type == LP_VIRTUAL ||
>> >> > +        (lport_type == LP_VIF && is_lport_container(pb)) ||
>> !changed_) {
>> >> >          return true;
>> >> >      }
>> >> >
>> >> > -    const struct sbrec_port_binding *binding_rec;
>> >> > -    bool any_changes = false;
>> >> > -    SBREC_PORT_BINDING_TABLE_FOR_EACH (binding_rec,
>> port_binding_table) {
>> >> > -        if (binding_rec->chassis == chassis_rec) {
>> >> > -            if (binding_rec->encap)
>> >> > -                sbrec_port_binding_set_encap(binding_rec, NULL);
>> >> > -            sbrec_port_binding_set_chassis(binding_rec, NULL);
>> >> > -            any_changes = true;
>> >> > +    struct local_binding *lbinding =
>> >> > +        local_binding_find(b_ctx_out->local_bindings,
>> pb->logical_port);
>> >> > +
>> >> > +    ovs_assert(lbinding);
>> >> > +
>> >> > +    struct shash_node *node;
>> >> > +    SHASH_FOR_EACH (node, &lbinding->children) {
>> >> > +        struct local_binding *child = node->data;
>> >> > +        if (child->type == BT_CONTAINER) {
>> >> > +            handled = consider_container_lport(child->pb, b_ctx_in,
>> >> b_ctx_out,
>> >> > +                                               qos_map);
>> >> > +            if (!handled) {
>> >> > +                return false;
>> >> > +            }
>> >> >          }
>> >> >      }
>> >> >
>> >> > -    if (any_changes) {
>> >> > -        ovsdb_idl_txn_add_comment(
>> >> > -            ovnsb_idl_txn,
>> >> > -            "ovn-controller: removing all port bindings for '%s'",
>> >> > -            chassis_rec->name);
>> >> > +    return true;
>> >> > +}
>> >> > +
>> >> > +/* Returns true if the port binding changes resulted in local
binding
>> >> > + * updates, false otherwise.
>> >> > + */
>> >> > +bool
>> >> > +binding_handle_port_binding_changes(struct binding_ctx_in
*b_ctx_in,
>> >> > +                                    struct binding_ctx_out
*b_ctx_out,
>> >> > +                                    bool *changed)
>> >> > +{
>> >> > +    bool handled = true;
>> >> > +    *changed = false;
>> >> > +
>> >> > +    const struct sbrec_port_binding *pb;
>> >> > +
>> >> > +    /* Run the tracked port binding loop twice. One to handle
deleted
>> >> > +     * changes. And another to handle add/update changes.
>> >> > +     * This will ensure correctness.
>> >> > +     */
>> >> > +    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
>> >> > +
>> >> b_ctx_in->port_binding_table) {
>> >> > +        if (!sbrec_port_binding_is_deleted(pb)) {
>> >> > +            continue;
>> >> > +        }
>> >> > +
>> >> > +        enum en_lport_type lport_type = get_lport_type(pb);
>> >> > +        if (lport_type == LP_VIF || lport_type == LP_VIRTUAL) {
>> >> > +            handled = handle_deleted_vif_lport(pb, lport_type,
>> b_ctx_in,
>> >> > +                                                b_ctx_out,
changed);
>> >> > +        } else {
>> >> > +            handle_deleted_lport(pb, b_ctx_in, b_ctx_out);
>> >> > +        }
>> >> > +
>> >> > +        if (!handled) {
>> >> > +            break;
>> >> > +        }
>> >> >      }
>> >> >
>> >> > -    return !any_changes;
>> >> > +    if (!handled) {
>> >> > +        return false;
>> >> > +    }
>> >> > +
>> >> > +    struct hmap qos_map = HMAP_INITIALIZER(&qos_map);
>> >> > +    struct hmap *qos_map_ptr =
>> >> > +        sset_is_empty(b_ctx_out->egress_ifaces) ? NULL : &qos_map;
>> >> > +
>> >> > +    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
>> >> > +
>> >> b_ctx_in->port_binding_table) {
>> >> > +        /* Loop to handle create and update changes only. */
>> >> > +        if (sbrec_port_binding_is_deleted(pb)) {
>> >> > +            continue;
>> >> > +        }
>> >> > +
>> >> > +        enum en_lport_type lport_type = get_lport_type(pb);
>> >> > +
>> >> > +        struct local_datapath *ld =
>> >> > +            get_local_datapath(b_ctx_out->local_datapaths,
>> >> > +                               pb->datapath->tunnel_key);
>> >> > +
>> >> > +        switch (lport_type) {
>> >> > +        case LP_VIF:
>> >> > +        case LP_VIRTUAL:
>> >> > +            handled = handle_updated_vif_lport(pb, lport_type,
>> b_ctx_in,
>> >> > +                                               b_ctx_out,
qos_map_ptr,
>> >> > +                                               changed);
>> >> > +            break;
>> >> > +
>> >> > +        case LP_PATCH:
>> >> > +        case LP_LOCALPORT:
>> >> > +        case LP_VTEP:
>> >> > +            *changed = true;
>> >> > +            update_local_lport_ids(b_ctx_out->local_lport_ids, pb);
>> >> > +            if (lport_type ==  LP_PATCH) {
>> >> > +                /* Add the peer datapath to the local datapaths if
>> it's
>> >> > +                    * not present yet.
>> >> > +                    */
>> >> > +                if (ld) {
>> >> > +                    add_local_datapath_peer_port(pb, b_ctx_in,
>> >> > +                                                    b_ctx_out, ld);
>> >> > +                }
>> >> > +            }
>> >> > +            break;
>> >> > +
>> >> > +        case LP_L2GATEWAY:
>> >> > +            *changed = true;
>> >> > +            handled = consider_l2gw_lport(pb, b_ctx_in, b_ctx_out);
>> >> > +            break;
>> >> > +
>> >> > +        case LP_L3GATEWAY:
>> >> > +            *changed = true;
>> >> > +            handled = consider_l3gw_lport(pb, b_ctx_in, b_ctx_out);
>> >> > +            break;
>> >> > +
>> >> > +        case LP_CHASSISREDIRECT:
>> >> > +            *changed = true;
>> >> > +            handled = consider_cr_lport(pb, b_ctx_in, b_ctx_out);
>> >> > +            break;
>> >> > +
>> >> > +        case LP_EXTERNAL:
>> >> > +            *changed = true;
>> >> > +            handled = consider_external_lport(pb, b_ctx_in,
>> b_ctx_out);
>> >> > +            break;
>> >> > +
>> >> > +        case LP_LOCALNET: {
>> >> > +            *changed = true;
>> >> > +            consider_localnet_lport(pb, b_ctx_in, b_ctx_out,
>> >> qos_map_ptr);
>> >> > +
>> >> > +            struct shash bridge_mappings =
>> >> > +                SHASH_INITIALIZER(&bridge_mappings);
>> >> > +            add_ovs_bridge_mappings(b_ctx_in->ovs_table,
>> >> > +                                    b_ctx_in->bridge_table,
>> >> > +                                    &bridge_mappings);
>> >> > +            update_ld_localnet_port(pb, &bridge_mappings,
>> >> > +                                    b_ctx_out->egress_ifaces,
>> >> > +                                    b_ctx_out->local_datapaths);
>> >> > +            shash_destroy(&bridge_mappings);
>> >> > +            break;
>> >> > +        }
>> >> > +
>> >> > +        case LP_REMOTE:
>> >> > +        case LP_UNKNOWN:
>> >> > +            break;
>> >> > +        }
>> >> > +
>> >> > +        if (!handled) {
>> >> > +            break;
>> >> > +        }
>> >> > +    }
>> >> > +
>> >> > +    if (handled && qos_map_ptr &&
set_noop_qos(b_ctx_in->ovs_idl_txn,
>> >> > +
b_ctx_in->port_table,
>> >> > +                                               b_ctx_in->qos_table,
>> >> > +
>> >> b_ctx_out->egress_ifaces)) {
>> >> > +        const char *entry;
>> >> > +        SSET_FOR_EACH (entry, b_ctx_out->egress_ifaces) {
>> >> > +            setup_qos(entry, &qos_map);
>> >> > +        }
>> >> > +    }
>> >> > +
>> >> > +    destroy_qos_map(&qos_map);
>> >> > +    return handled;
>> >> >  }
>> >> > diff --git a/controller/binding.h b/controller/binding.h
>> >> > index 9affc9a96..f7ace6faf 100644
>> >> > --- a/controller/binding.h
>> >> > +++ b/controller/binding.h
>> >> > @@ -57,6 +57,7 @@ struct binding_ctx_out {
>> >> >      struct sset *local_lports;
>> >> >      struct sset *local_lport_ids;
>> >> >      struct sset *egress_ifaces;
>> >> > +    struct smap *local_iface_ids;
>> >> >  };
>> >> >
>> >> >  void binding_register_ovs_idl(struct ovsdb_idl *);
>> >> > @@ -64,9 +65,13 @@ void binding_run(struct binding_ctx_in *, struct
>> >> binding_ctx_out *);
>> >> >  bool binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn,
>> >> >                       const struct sbrec_port_binding_table *,
>> >> >                       const struct sbrec_chassis *);
>> >> > -bool binding_evaluate_port_binding_changes(struct binding_ctx_in *,
>> >> > -                                           struct binding_ctx_out
*);
>> >> >
>> >> >  void local_bindings_init(struct shash *local_bindings);
>> >> >  void local_bindings_destroy(struct shash *local_bindings);
>> >> > +bool binding_handle_ovs_interface_changes(struct binding_ctx_in *,
>> >> > +                                          struct binding_ctx_out *,
>> >> > +                                          bool *changed);
>> >> > +bool binding_handle_port_binding_changes(struct binding_ctx_in *,
>> >> > +                                         struct binding_ctx_out *,
>> >> > +                                         bool *changed);
>> >> >  #endif /* controller/binding.h */
>> >> > diff --git a/controller/ovn-controller.c
b/controller/ovn-controller.c
>> >> > index 6b759966b..08b074415 100644
>> >> > --- a/controller/ovn-controller.c
>> >> > +++ b/controller/ovn-controller.c
>> >> > @@ -753,6 +753,7 @@ enum sb_engine_node {
>> >> >      OVS_NODE(open_vswitch, "open_vswitch") \
>> >> >      OVS_NODE(bridge, "bridge") \
>> >> >      OVS_NODE(port, "port") \
>> >> > +    OVS_NODE(interface, "interface") \
>> >> >      OVS_NODE(qos, "qos")
>> >> >
>> >> >  enum ovs_engine_node {
>> >> > @@ -974,6 +975,7 @@ struct ed_type_runtime_data {
>> >> >      struct sset active_tunnels;
>> >> >
>> >> >      struct sset egress_ifaces;
>> >> > +    struct smap local_iface_ids;
>> >> >  };
>> >> >
>> >> >  static void *
>> >> > @@ -987,6 +989,7 @@ en_runtime_data_init(struct engine_node *node
>> >> OVS_UNUSED,
>> >> >      sset_init(&data->local_lport_ids);
>> >> >      sset_init(&data->active_tunnels);
>> >> >      sset_init(&data->egress_ifaces);
>> >> > +    smap_init(&data->local_iface_ids);
>> >> >      local_bindings_init(&data->local_bindings);
>> >> >      return data;
>> >> >  }
>> >> > @@ -1000,6 +1003,7 @@ en_runtime_data_cleanup(void *data)
>> >> >      sset_destroy(&rt_data->local_lport_ids);
>> >> >      sset_destroy(&rt_data->active_tunnels);
>> >> >      sset_destroy(&rt_data->egress_ifaces);
>> >> > +    smap_destroy(&rt_data->local_iface_ids);
>> >> >      struct local_datapath *cur_node, *next_node;
>> >> >      HMAP_FOR_EACH_SAFE (cur_node, next_node, hmap_node,
>> >> >                          &rt_data->local_datapaths) {
>> >> > @@ -1041,6 +1045,10 @@ init_binding_ctx(struct engine_node *node,
>> >> >          (struct ovsrec_port_table *)EN_OVSDB_GET(
>> >> >              engine_get_input("OVS_port", node));
>> >> >
>> >> > +    struct ovsrec_interface_table *iface_table =
>> >> > +        (struct ovsrec_interface_table *)EN_OVSDB_GET(
>> >> > +            engine_get_input("OVS_interface", node));
>> >> > +
>> >> >      struct ovsrec_qos_table *qos_table =
>> >> >          (struct ovsrec_qos_table *)EN_OVSDB_GET(
>> >> >              engine_get_input("OVS_qos", node));
>> >> > @@ -1070,6 +1078,7 @@ init_binding_ctx(struct engine_node *node,
>> >> >      b_ctx_in->sbrec_port_binding_by_datapath =
>> >> sbrec_port_binding_by_datapath;
>> >> >      b_ctx_in->sbrec_port_binding_by_name =
sbrec_port_binding_by_name;
>> >> >      b_ctx_in->port_table = port_table;
>> >> > +    b_ctx_in->iface_table = iface_table;
>> >> >      b_ctx_in->qos_table = qos_table;
>> >> >      b_ctx_in->port_binding_table = pb_table;
>> >> >      b_ctx_in->br_int = br_int;
>> >> > @@ -1083,6 +1092,7 @@ init_binding_ctx(struct engine_node *node,
>> >> >      b_ctx_out->local_lport_ids = &rt_data->local_lport_ids;
>> >> >      b_ctx_out->egress_ifaces = &rt_data->egress_ifaces;
>> >> >      b_ctx_out->local_bindings = &rt_data->local_bindings;
>> >> > +    b_ctx_out->local_iface_ids = &rt_data->local_iface_ids;
>> >> >  }
>> >> >
>> >> >  static void
>> >> > @@ -1111,10 +1121,12 @@ en_runtime_data_run(struct engine_node
*node,
>> >> void *data)
>> >> >          sset_destroy(local_lport_ids);
>> >> >          sset_destroy(active_tunnels);
>> >> >          sset_destroy(&rt_data->egress_ifaces);
>> >> > +        smap_destroy(&rt_data->local_iface_ids);
>> >> >          sset_init(local_lports);
>> >> >          sset_init(local_lport_ids);
>> >> >          sset_init(active_tunnels);
>> >> >          sset_init(&rt_data->egress_ifaces);
>> >> > +        smap_init(&rt_data->local_iface_ids);
>> >> >          local_bindings_init(&rt_data->local_bindings);
>> >> >      }
>> >> >
>> >> > @@ -1140,6 +1152,34 @@ en_runtime_data_run(struct engine_node *node,
>> void
>> >> *data)
>> >> >      engine_set_node_state(node, EN_UPDATED);
>> >> >  }
>> >> >
>> >> > +static bool
>> >> > +runtime_data_ovs_interface_handler(struct engine_node *node, void
>> *data)
>> >> > +{
>> >> > +    struct ed_type_runtime_data *rt_data = data;
>> >> > +    struct binding_ctx_in b_ctx_in;
>> >> > +    struct binding_ctx_out b_ctx_out;
>> >> > +    init_binding_ctx(node, rt_data, &b_ctx_in, &b_ctx_out);
>> >> > +
>> >> > +    bool changed = false;
>> >> > +    if (!binding_handle_ovs_interface_changes(&b_ctx_in,
&b_ctx_out,
>> >> > +                                              &changed)) {
>> >> > +        return false;
>> >> > +    }
>> >> > +
>> >> > +    if (changed) {
>> >> > +        engine_set_node_state(node, EN_UPDATED);
>> >> > +    }
>> >> > +
>> >> > +    return true;
>> >> > +}
>> >> > +
>> >> > +static bool
>> >> > +runtime_data_noop_handler(struct engine_node *node OVS_UNUSED,
>> >> > +                          void *data OVS_UNUSED)
>> >> > +{
>> >> > +    return true;
>> >> > +}
>> >> > +
>> >> >  static bool
>> >> >  runtime_data_sb_port_binding_handler(struct engine_node *node, void
>> >> *data)
>> >> >  {
>> >> > @@ -1147,11 +1187,21 @@ runtime_data_sb_port_binding_handler(struct
>> >> engine_node *node, void *data)
>> >> >      struct binding_ctx_in b_ctx_in;
>> >> >      struct binding_ctx_out b_ctx_out;
>> >> >      init_binding_ctx(node, rt_data, &b_ctx_in, &b_ctx_out);
>> >> > +    if (!b_ctx_in.chassis_rec) {
>> >> > +        return false;
>> >> > +    }
>> >> > +
>> >> > +    bool changed = false;
>> >> > +    if (!binding_handle_port_binding_changes(&b_ctx_in, &b_ctx_out,
>> >> > +                                             &changed)) {
>> >> > +        return false;
>> >> > +    }
>> >> >
>> >> > -    bool changed = binding_evaluate_port_binding_changes(&b_ctx_in,
>> >> > -
&b_ctx_out);
>> >> > +    if (changed) {
>> >> > +        engine_set_node_state(node, EN_UPDATED);
>> >> > +    }
>> >> >
>> >> > -    return !changed;
>> >> > +    return true;
>> >> >  }
>> >> >
>> >> >  /* Connection tracking zones. */
>> >> > @@ -1894,7 +1944,10 @@ main(int argc, char *argv[])
>> >> >
>> >> >      engine_add_input(&en_runtime_data, &en_ovs_open_vswitch, NULL);
>> >> >      engine_add_input(&en_runtime_data, &en_ovs_bridge, NULL);
>> >> > -    engine_add_input(&en_runtime_data, &en_ovs_port, NULL);
>> >> > +    engine_add_input(&en_runtime_data, &en_ovs_port,
>> >> > +                     runtime_data_noop_handler);
>> >> > +    engine_add_input(&en_runtime_data, &en_ovs_interface,
>> >> > +                     runtime_data_ovs_interface_handler);
>> >> >      engine_add_input(&en_runtime_data, &en_ovs_qos, NULL);
>> >> >
>> >> >      engine_add_input(&en_runtime_data, &en_sb_chassis, NULL);
>> >> > diff --git a/tests/ovn-performance.at b/tests/ovn-performance.at
>> >> > index a8a15f8fe..5dfc6f7ca 100644
>> >> > --- a/tests/ovn-performance.at
>> >> > +++ b/tests/ovn-performance.at
>> >> > @@ -239,6 +239,19 @@ for i in 1 2; do
>> >> >      ovn_attach n1 br-phys 192.168.0.$i
>> >> >  done
>> >> >
>> >> > +# Wait for the tunnel ports to be created and up.
>> >> > +# Otherwise this may affect the lflow_run count.
>> >> > +
>> >> > +OVS_WAIT_UNTIL([
>> >> > +    test $(as hv1 ovs-vsctl list interface ovn-hv2-0 | \
>> >> > +grep tunnel_egress_iface_carrier=up | wc -l) -eq 1
>> >> > +])
>> >> > +
>> >> > +OVS_WAIT_UNTIL([
>> >> > +    test $(as hv2 ovs-vsctl list interface ovn-hv1-0 | \
>> >> > +grep tunnel_egress_iface_carrier=up | wc -l) -eq 1
>> >> > +])
>> >> > +
>> >> >  # Add router lr1
>> >> >  OVN_CONTROLLER_EXPECT_HIT(
>> >> >      [hv1 hv2], [lflow_run],
>> >> > --
>> >> > 2.26.2
>> >> >
>> >> > _______________________________________________
>> >> > dev mailing list
>> >> > dev@openvswitch.org
>> >> > https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>> >> _______________________________________________
>> >> dev mailing list
>> >> dev@openvswitch.org
>> >> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>> >>
>> _______________________________________________
>> dev mailing list
>> dev@openvswitch.org
>> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>>
Numan Siddique May 22, 2020, 10:07 p.m. UTC | #6
On Sat, May 23, 2020 at 3:10 AM Han Zhou <hzhou@ovn.org> wrote:

> On Fri, May 22, 2020 at 1:51 AM Numan Siddique <numans@ovn.org> wrote:
> >
> >
> >
> > On Fri, May 22, 2020 at 1:17 AM Han Zhou <hzhou@ovn.org> wrote:
> >>
> >> On Thu, May 21, 2020 at 3:49 AM Numan Siddique <numans@ovn.org> wrote:
> >> >
> >> >
> >> >
> >> > On Thu, May 21, 2020 at 6:57 AM Han Zhou <hzhou@ovn.org> wrote:
> >> >>
> >> >> Hi Numan, please see my comments below.
> >> >
> >> >
> >> > Thanks for the review. Please see below for a few comments.
> >> >
> >> >
> >> >>
> >> >>
> >> >> On Wed, May 20, 2020 at 12:41 PM <numans@ovn.org> wrote:
> >> >> >
> >> >> > From: Numan Siddique <numans@ovn.org>
> >> >> >
> >> >> > This patch handles SB port binding changes and OVS interface
> changes
> >> >> > incrementally in the runtime_data stage which otherwise would have
> >> >> > resulted in calls to binding_run().
> >> >> >
> >> >> > Prior to this patch, port binding changes were handled differently.
> >> >> > The changes were only evaluated to see if they need full recompute
> >> >> > or they can be ignored.
> >> >> >
> >> >> > With this patch, the runtime data is updated with the changes (both
> >> >> > SB port binding and OVS interface) and ports are claimed/released
> in
> >> >> > the handlers itself, avoiding the need to trigger recompute of
> runtime
> >> >> > data stage.
> >> >> >
> >> >> > Acked-by: Mark Michelson <mmichels@redhat.com>
> >> >> > Signed-off-by: Numan Siddique <numans@ovn.org>
> >> >> > ---
> >> >> >  controller/binding.c        | 906
> +++++++++++++++++++++++++++++++-----
> >> >> >  controller/binding.h        |   9 +-
> >> >> >  controller/ovn-controller.c |  61 ++-
> >> >> >  tests/ovn-performance.at    |  13 +
> >> >> >  4 files changed, 855 insertions(+), 134 deletions(-)
> >> >> >
> >> >> > diff --git a/controller/binding.c b/controller/binding.c
> >> >> > index 2997fcae8..d5e8e4773 100644
> >> >> > --- a/controller/binding.c
> >> >> > +++ b/controller/binding.c
> >> >> > @@ -360,17 +360,6 @@ destroy_qos_map(struct hmap *qos_map)
> >> >> >      hmap_destroy(qos_map);
> >> >> >  }
> >> >> >
> >> >> > -static void
> >> >> > -update_local_lport_ids(struct sset *local_lport_ids,
> >> >> > -                       const struct sbrec_port_binding
> *binding_rec)
> >> >> > -{
> >> >> > -        char buf[16];
> >> >> > -        snprintf(buf, sizeof(buf), "%"PRId64"_%"PRId64,
> >> >> > -                 binding_rec->datapath->tunnel_key,
> >> >> > -                 binding_rec->tunnel_key);
> >> >> > -        sset_add(local_lport_ids, buf);
> >> >> > -}
> >> >> > -
> >> >> >  /*
> >> >> >   * Get the encap from the chassis for this port. The interface
> >> >> >   * may have an external_ids:encap-ip=<encap-ip> set; if so we
> >> >> > @@ -449,10 +438,10 @@ is_network_plugged(const struct
> >> sbrec_port_binding
> >> >> *binding_rec,
> >> >> >  }
> >> >> >
> >> >> >  static void
> >> >> > -consider_localnet_port(const struct sbrec_port_binding
> *binding_rec,
> >> >> > -                       struct shash *bridge_mappings,
> >> >> > -                       struct sset *egress_ifaces,
> >> >> > -                       struct hmap *local_datapaths)
> >> >> > +update_ld_localnet_port(const struct sbrec_port_binding
> *binding_rec,
> >> >> > +                        struct shash *bridge_mappings,
> >> >> > +                        struct sset *egress_ifaces,
> >> >> > +                        struct hmap *local_datapaths)
> >> >> >  {
> >> >> >      /* Ignore localnet ports for unplugged networks. */
> >> >> >      if (!is_network_plugged(binding_rec, bridge_mappings)) {
> >> >> > @@ -482,6 +471,28 @@ consider_localnet_port(const struct
> >> >> sbrec_port_binding *binding_rec,
> >> >> >      ld->localnet_port = binding_rec;
> >> >> >  }
> >> >> >
> >> >> > +static void
> >> >> > +update_local_lport_ids(struct sset *local_lport_ids,
> >> >> > +                       const struct sbrec_port_binding
> *binding_rec)
> >> >> > +{
> >> >> > +        char buf[16];
> >> >> > +        snprintf(buf, sizeof(buf), "%"PRId64"_%"PRId64,
> >> >> > +                 binding_rec->datapath->tunnel_key,
> >> >> > +                 binding_rec->tunnel_key);
> >> >> > +        sset_add(local_lport_ids, buf);
> >> >> > +}
> >> >> > +
> >> >> > +static void
> >> >> > +remove_local_lport_ids(const struct sbrec_port_binding
> *binding_rec,
> >> >> > +                       struct sset *local_lport_ids)
> >> >> > +{
> >> >> > +        char buf[16];
> >> >> > +        snprintf(buf, sizeof(buf), "%"PRId64"_%"PRId64,
> >> >> > +                 binding_rec->datapath->tunnel_key,
> >> >> > +                 binding_rec->tunnel_key);
> >> >> > +        sset_find_and_delete(local_lport_ids, buf);
> >> >> > +}
> >> >> > +
> >> >> >  /* Local bindings. binding.c module binds the logical port
> >> (represented
> >> >> by
> >> >> >   * Port_Binding rows) and sets the 'chassis' column when it is
> sees
> >> the
> >> >> >   * OVS interface row (of type "" or "internal") with the
> >> >> > @@ -599,6 +610,14 @@ local_bindings_destroy(struct shash
> >> *local_bindings)
> >> >> >      shash_destroy(local_bindings);
> >> >> >  }
> >> >> >
> >> >> > +static
> >> >> > +void local_binding_delete(struct shash *local_bindings,
> >> >> > +                          struct local_binding *lbinding)
> >> >> > +{
> >> >> > +    shash_find_and_delete(local_bindings, lbinding->name);
> >> >> > +    local_binding_destroy(lbinding);
> >> >> > +}
> >> >> > +
> >> >> >  static void
> >> >> >  local_binding_add_child(struct local_binding *lbinding,
> >> >> >                          struct local_binding *child)
> >> >> > @@ -625,6 +644,7 @@ is_lport_container(const struct
> sbrec_port_binding
> >> >> *pb)
> >> >> >      return !pb->type[0] && pb->parent_port && pb->parent_port[0];
> >> >> >  }
> >> >> >
> >> >> > +/* Corresponds to each Port_Binding.type. */
> >> >> >  enum en_lport_type {
> >> >> >      LP_UNKNOWN,
> >> >> >      LP_VIF,
> >> >> > @@ -670,12 +690,20 @@ get_lport_type(const struct
> sbrec_port_binding
> >> *pb)
> >> >> >      return LP_UNKNOWN;
> >> >> >  }
> >> >> >
> >> >> > -static void
> >> >> > +/* Returns false if lport is not claimed due to 'sb_readonly'.
> >> >> > + * Returns true otherwise.
> >> >> > + */
> >> >> > +static bool
> >> >> >  claim_lport(const struct sbrec_port_binding *pb,
> >> >> >              const struct sbrec_chassis *chassis_rec,
> >> >> > -            const struct ovsrec_interface *iface_rec)
> >> >> > +            const struct ovsrec_interface *iface_rec,
> >> >> > +            bool sb_readonly)
> >> >> >  {
> >> >> >      if (pb->chassis != chassis_rec) {
> >> >> > +        if (sb_readonly) {
> >> >> > +            return false;
> >> >> > +        }
> >> >> > +
> >> >> >          if (pb->chassis) {
> >> >> >              VLOG_INFO("Changing chassis for lport %s from %s to
> %s.",
> >> >> >                      pb->logical_port, pb->chassis->name,
> >> >> > @@ -694,26 +722,48 @@ claim_lport(const struct sbrec_port_binding
> *pb,
> >> >> >      struct sbrec_encap *encap_rec =
> >> >> >          sbrec_get_port_encap(chassis_rec, iface_rec);
> >> >> >      if (encap_rec && pb->encap != encap_rec) {
> >> >> > +        if (sb_readonly) {
> >> >> > +            return false;
> >> >> > +        }
> >> >> >          sbrec_port_binding_set_encap(pb, encap_rec);
> >> >> >      }
> >> >> > +
> >> >> > +    return true;
> >> >> >  }
> >> >> >
> >> >> > -static void
> >> >> > -release_lport(const struct sbrec_port_binding *pb)
> >> >> > +/* Returns false if lport is not released due to 'sb_readonly'.
> >> >> > + * Returns true otherwise.
> >> >> > + */
> >> >> > +static bool
> >> >> > +release_lport(const struct sbrec_port_binding *pb, bool
> sb_readonly)
> >> >> >  {
> >> >> >      if (!pb) {
> >> >> > -        return;
> >> >> > +        return true;
> >> >> >      }
> >> >> >
> >> >> > -    VLOG_INFO("Releasing lport %s from this chassis.",
> >> pb->logical_port);
> >> >> >      if (pb->encap) {
> >> >> > +        if (sb_readonly) {
> >> >> > +            return false;
> >> >> > +        }
> >> >> >          sbrec_port_binding_set_encap(pb, NULL);
> >> >> >      }
> >> >> > -    sbrec_port_binding_set_chassis(pb, NULL);
> >> >> > +
> >> >> > +    if (pb->chassis) {
> >> >> > +        if (sb_readonly) {
> >> >> > +            return false;
> >> >> > +        }
> >> >> > +        sbrec_port_binding_set_chassis(pb, NULL);
> >> >> > +    }
> >> >> >
> >> >> >      if (pb->virtual_parent) {
> >> >> > +        if (sb_readonly) {
> >> >> > +            return false;
> >> >> > +        }
> >> >> >          sbrec_port_binding_set_virtual_parent(pb, NULL);
> >> >> >      }
> >> >> > +
> >> >> > +    VLOG_INFO("Releasing lport %s from this chassis.",
> >> pb->logical_port);
> >> >> > +    return true;
> >> >> >  }
> >> >> >
> >> >> >  static bool
> >> >> > @@ -738,7 +788,41 @@ can_bind_on_this_chassis(const struct
> >> sbrec_chassis
> >> >> *chassis_rec,
> >> >> >             || !strcmp(requested_chassis, chassis_rec->hostname);
> >> >> >  }
> >> >> >
> >> >> > -static void
> >> >> > +static bool
> >> >> > +release_local_binding_children(const struct sbrec_chassis
> >> *chassis_rec,
> >> >> > +                               struct local_binding *lbinding,
> >> >> > +                               bool sb_readonly)
> >> >> > +{
> >> >> > +    struct shash_node *node;
> >> >> > +    SHASH_FOR_EACH (node, &lbinding->children) {
> >> >> > +        struct local_binding *l = node->data;
> >> >> > +        if (is_lbinding_this_chassis(l, chassis_rec)) {
> >> >> > +            if (!release_lport(l->pb, sb_readonly)) {
> >> >> > +                return false;
> >> >> > +            }
> >> >> > +        }
> >> >> > +    }
> >> >> > +
> >> >> > +    return true;
> >> >> > +}
> >> >> > +
> >> >> > +static bool
> >> >> > +release_local_binding(const struct sbrec_chassis *chassis_rec,
> >> >> > +                      struct local_binding *lbinding, bool
> >> sb_readonly)
> >> >> > +{
> >> >> > +    if (!release_local_binding_children(chassis_rec, lbinding,
> >> >> > +                                        sb_readonly)) {
> >> >> > +        return false;
> >> >> > +    }
> >> >> > +
> >> >> > +    if (is_lbinding_this_chassis(lbinding, chassis_rec)) {
> >> >> > +        return release_lport(lbinding->pb, sb_readonly);
> >> >> > +    }
> >> >> > +
> >> >> > +    return true;
> >> >> > +}
> >> >> > +
> >> >> > +static bool
> >> >> >  consider_vif_lport_(const struct sbrec_port_binding *pb,
> >> >> >                      bool can_bind, const char *vif_chassis,
> >> >> >                      struct binding_ctx_in *b_ctx_in,
> >> >> > @@ -750,7 +834,10 @@ consider_vif_lport_(const struct
> >> sbrec_port_binding
> >> >> *pb,
> >> >> >      if (lbinding_set) {
> >> >> >          if (can_bind) {
> >> >> >              /* We can claim the lport. */
> >> >> > -            claim_lport(pb, b_ctx_in->chassis_rec,
> lbinding->iface);
> >> >> > +            if (!claim_lport(pb, b_ctx_in->chassis_rec,
> >> lbinding->iface,
> >> >> > +                             !b_ctx_in->ovnsb_idl_txn)){
> >> >> > +                return false;
> >> >> > +            }
> >> >> >
> >> >> >
> >>  add_local_datapath(b_ctx_in->sbrec_datapath_binding_by_key,
> >> >> >
>  b_ctx_in->sbrec_port_binding_by_datapath,
> >> >> > @@ -775,13 +862,14 @@ consider_vif_lport_(const struct
> >> sbrec_port_binding
> >> >> *pb,
> >> >> >      if (pb->chassis == b_ctx_in->chassis_rec) {
> >> >> >          /* Release the lport if there is no lbinding. */
> >> >> >          if (!lbinding_set || !can_bind) {
> >> >> > -            release_lport(pb);
> >> >> > +            return release_lport(pb, !b_ctx_in->ovnsb_idl_txn);
> >> >> >          }
> >> >> >      }
> >> >> >
> >> >> > +    return true;
> >> >> >  }
> >> >> >
> >> >> > -static void
> >> >> > +static bool
> >> >> >  consider_vif_lport(const struct sbrec_port_binding *pb,
> >> >> >                     struct binding_ctx_in *b_ctx_in,
> >> >> >                     struct binding_ctx_out *b_ctx_out,
> >> >> > @@ -801,11 +889,11 @@ consider_vif_lport(const struct
> >> sbrec_port_binding
> >> >> *pb,
> >> >> >          lbinding->pb = pb;
> >> >> >      }
> >> >> >
> >> >> > -    consider_vif_lport_(pb, can_bind, vif_chassis, b_ctx_in,
> >> >> > -                        b_ctx_out, lbinding, qos_map);
> >> >> > +    return consider_vif_lport_(pb, can_bind, vif_chassis,
> b_ctx_in,
> >> >> > +                               b_ctx_out, lbinding, qos_map);
> >> >> >  }
> >> >> >
> >> >> > -static void
> >> >> > +static bool
> >> >> >  consider_container_lport(const struct sbrec_port_binding *pb,
> >> >> >                           struct binding_ctx_in *b_ctx_in,
> >> >> >                           struct binding_ctx_out *b_ctx_out,
> >> >> > @@ -815,7 +903,39 @@ consider_container_lport(const struct
> >> >> sbrec_port_binding *pb,
> >> >> >      parent_lbinding =
> local_binding_find(b_ctx_out->local_bindings,
> >> >> >                                           pb->parent_port);
> >> >> >
> >> >> > -    if (parent_lbinding && !parent_lbinding->pb) {
> >> >> > +    if (!parent_lbinding) {
> >> >> > +        /* There is no local_binding for parent port. Create it
> >> >> > +         * without OVS interface row. This is the only exception
> >> >> > +         * for creating the 'struct local_binding' object without
> >> >> > +         * corresponding OVS interface row.
> >> >> > +         *
> >> >> > +         * This is required for the following reasons:
> >> >> > +         *   - If a logical port P1 is created and then
> >> >> > +         *     few container ports - C1, C2, .. are created first
> by
> >> CMS.
> >> >> > +         *   - And later when OVS interface row  is created for
> P1,
> >> then
> >> >> > +         *     we want the these container ports also be claimed
> by
> >> the
> >> >> > +         *     chassis.
> >> >> > +         * */
> >> >>
> >> >> It seems the assumption is, for all container PBs, their parent PBs
> >> should
> >> >> always have local_binding created, no matter if they are bound to
> current
> >> >> chassis.
> >> >
> >> >
> >> > That's right.
> >> >
> >> >>
> >> >> However, at least some of the code in this patch would violate this
> >> >> assumption, e.g. handle_iface_delete() will call
> local_binding_delete()
> >> and
> >> >> delete the parent binding and all the children container bindings.
> >> >
> >> >
> >> > I missed this part. I'll handle it in next version.
> >> >
> >> >>
> >> >> It is also better to document this clearly in the comment of struct
> >> >> local_binding for what should be expected in the local_bindings.
> >> >
> >> >
> >> > I've added comments for the local_bindings struct. But it looks like
> it's
> >> not
> >> > sufficient. I'll add more .
> >> >
> >> >> My
> >> >> understanding is, for each binding_run() or change_handler execution,
> the
> >> >> expected items left in it is:
> >> >> 1) For each interface that has iface-id configured.
> >> >
> >> > Yes.
> >> >
> >> >>
> >> >> 2) For each container PBs and their parent PBs, no matter if they are
> >> bound
> >> >> to this chassis.
> >> >
> >> >
> >> > Yes.
> >> >
> >> >>
> >> >> 3) For some of the non VIF PBs ...
> >> >
> >> >
> >> > Only for the "virtual" PB if it's parent is bound.
> >> >
> >> >>
> >> >> Is this correct?
> >> >>
> >> >> > +        parent_lbinding = local_binding_create(pb->parent_port,
> NULL,
> >> >> NULL,
> >> >> > +                                               BT_VIF);
> >> >> > +        local_binding_add(b_ctx_out->local_bindings,
> parent_lbinding);
> >> >> > +    }
> >> >> > +
> >> >> > +    struct local_binding *container_lbinding =
> >> >> > +        local_binding_find_child(parent_lbinding,
> pb->logical_port);
> >> >> > +
> >> >> > +    if (!container_lbinding) {
> >> >> > +        container_lbinding =
> local_binding_create(pb->logical_port,
> >> >> > +
> >>  parent_lbinding->iface,
> >> >> > +                                                  pb,
> BT_CONTAINER);
> >> >> > +        local_binding_add_child(parent_lbinding,
> container_lbinding);
> >> >> > +    } else {
> >> >> > +        ovs_assert(container_lbinding->type == BT_CONTAINER);
> >> >> > +        container_lbinding->pb = pb;
> >> >> > +        container_lbinding->iface = parent_lbinding->iface;
> >> >> > +    }
> >> >> > +
> >> >> > +    if (!parent_lbinding->pb) {
> >> >> >          parent_lbinding->pb = lport_lookup_by_name(
> >> >> >              b_ctx_in->sbrec_port_binding_by_name,
> pb->parent_port);
> >> >> >
> >> >> > @@ -824,37 +944,28 @@ consider_container_lport(const struct
> >> >> sbrec_port_binding *pb,
> >> >> >               * So call consider_vif_lport() to process it first.
> */
> >> >> >              consider_vif_lport(parent_lbinding->pb, b_ctx_in,
> >> b_ctx_out,
> >> >> >                                 parent_lbinding, qos_map);
> >> >> > -        }
> >> >> > -    }
> >> >> > +        } else {
> >> >> > +            /* The parent lport doesn't exist. Call
> release_lport() to
> >> >> > +             * release the container lport, if it was bound
> earlier.
> >> */
> >> >> > +            if (is_lbinding_this_chassis(container_lbinding,
> >> >> > +                                         b_ctx_in->chassis_rec)) {
> >> >> > +               return release_lport(pb, !b_ctx_in->ovnsb_idl_txn);
> >> >> > +            }
> >> >> >
> >> >> > -    if (!parent_lbinding || !parent_lbinding->pb) {
> >> >> > -        /* Call release_lport, to release the container lport, if
> >> >> > -         * it was bound earlier. */
> >> >> > -        if (pb->chassis == b_ctx_in->chassis_rec) {
> >> >> > -            release_lport(pb);
> >> >> > +            return true;
> >> >> >          }
> >> >> > -        return;
> >> >> >      }
> >> >> >
> >> >> > -    struct local_binding *container_lbinding =
> >> >> > -        local_binding_find_child(parent_lbinding,
> pb->logical_port);
> >> >> > -    ovs_assert(!container_lbinding);
> >> >> > -
> >> >> > -    container_lbinding = local_binding_create(pb->logical_port,
> >> >> > -
>  parent_lbinding->iface,
> >> >> > -                                              pb, BT_CONTAINER);
> >> >> > -    local_binding_add_child(parent_lbinding, container_lbinding);
> >> >> > -
> >> >> >      const char *vif_chassis =
> smap_get(&parent_lbinding->pb->options,
> >> >> >                                         "requested-chassis");
> >> >> >      bool can_bind =
> can_bind_on_this_chassis(b_ctx_in->chassis_rec,
> >> >> >                                               vif_chassis);
> >> >> >
> >> >> > -    consider_vif_lport_(pb, can_bind, vif_chassis, b_ctx_in,
> >> b_ctx_out,
> >> >> > -                        container_lbinding, qos_map);
> >> >> > +    return consider_vif_lport_(pb, can_bind, vif_chassis,
> b_ctx_in,
> >> >> b_ctx_out,
> >> >> > +                               container_lbinding, qos_map);
> >> >> >  }
> >> >> >
> >> >> > -static void
> >> >> > +static bool
> >> >> >  consider_virtual_lport(const struct sbrec_port_binding *pb,
> >> >> >                         struct binding_ctx_in *b_ctx_in,
> >> >> >                         struct binding_ctx_out *b_ctx_out,
> >> >> > @@ -881,11 +992,16 @@ consider_virtual_lport(const struct
> >> >> sbrec_port_binding *pb,
> >> >> >      if (is_lbinding_this_chassis(parent_lbinding,
> >> >> b_ctx_in->chassis_rec)) {
> >> >> >          virtual_lbinding =
> >> >> >              local_binding_find_child(parent_lbinding,
> >> pb->logical_port);
> >> >> > -        ovs_assert(!virtual_lbinding);
> >> >> > -        virtual_lbinding = local_binding_create(pb->logical_port,
> >> >> > -
> >>  parent_lbinding->iface,
> >> >> > -                                                pb, BT_VIRTUAL);
> >> >> > -        local_binding_add_child(parent_lbinding,
> virtual_lbinding);
> >> >> > +        if (!virtual_lbinding) {
> >> >> > +            virtual_lbinding =
> local_binding_create(pb->logical_port,
> >> >> > +
> >> >>  parent_lbinding->iface,
> >> >> > +                                                    pb,
> BT_VIRTUAL);
> >> >> > +            local_binding_add_child(parent_lbinding,
> >> virtual_lbinding);
> >> >> > +        } else {
> >> >> > +            ovs_assert(virtual_lbinding->type == BT_VIRTUAL);
> >> >> > +            virtual_lbinding->pb = pb;
> >> >> > +            virtual_lbinding->iface = parent_lbinding->iface;
> >> >> > +        }
> >> >> >      }
> >> >> >
> >> >> >      return consider_vif_lport_(pb, true, NULL, b_ctx_in,
> b_ctx_out,
> >> >> > @@ -895,14 +1011,13 @@ consider_virtual_lport(const struct
> >> >> sbrec_port_binding *pb,
> >> >> >  /* Considers either claiming the lport or releasing the lport
> >> >> >   * for non VIF lports.
> >> >> >   */
> >> >> > -static void
> >> >> > +static bool
> >> >> >  consider_nonvif_lport_(const struct sbrec_port_binding *pb,
> >> >> >                         bool our_chassis,
> >> >> >                         bool has_local_l3gateway,
> >> >> >                         struct binding_ctx_in *b_ctx_in,
> >> >> >                         struct binding_ctx_out *b_ctx_out)
> >> >> >  {
> >> >> > -    ovs_assert(b_ctx_in->ovnsb_idl_txn);
> >> >> >      if (our_chassis) {
> >> >> >          sset_add(b_ctx_out->local_lports, pb->logical_port);
> >> >> >
> add_local_datapath(b_ctx_in->sbrec_datapath_binding_by_key,
> >> >> > @@ -912,13 +1027,16 @@ consider_nonvif_lport_(const struct
> >> >> sbrec_port_binding *pb,
> >> >> >                             b_ctx_out->local_datapaths);
> >> >> >
> >> >> >          update_local_lport_ids(b_ctx_out->local_lport_ids, pb);
> >> >> > -        claim_lport(pb, b_ctx_in->chassis_rec, NULL);
> >> >> > +        return claim_lport(pb, b_ctx_in->chassis_rec, NULL,
> >> >> > +                           !b_ctx_in->ovnsb_idl_txn);
> >> >> >      } else if (pb->chassis == b_ctx_in->chassis_rec) {
> >> >> > -        release_lport(pb);
> >> >> > +        return release_lport(pb, !b_ctx_in->ovnsb_idl_txn);
> >> >> >      }
> >> >> > +
> >> >> > +    return true;
> >> >> >  }
> >> >> >
> >> >> > -static void
> >> >> > +static bool
> >> >> >  consider_l2gw_lport(const struct sbrec_port_binding *pb,
> >> >> >                      struct binding_ctx_in *b_ctx_in,
> >> >> >                      struct binding_ctx_out *b_ctx_out)
> >> >> > @@ -927,10 +1045,10 @@ consider_l2gw_lport(const struct
> >> >> sbrec_port_binding *pb,
> >> >> >      bool our_chassis = chassis_id && !strcmp(chassis_id,
> >> >> >
> >> >> b_ctx_in->chassis_rec->name);
> >> >> >
> >> >> > -    consider_nonvif_lport_(pb, our_chassis, false, b_ctx_in,
> >> b_ctx_out);
> >> >> > +    return consider_nonvif_lport_(pb, our_chassis, false,
> b_ctx_in,
> >> >> b_ctx_out);
> >> >> >  }
> >> >> >
> >> >> > -static void
> >> >> > +static bool
> >> >> >  consider_l3gw_lport(const struct sbrec_port_binding *pb,
> >> >> >                      struct binding_ctx_in *b_ctx_in,
> >> >> >                      struct binding_ctx_out *b_ctx_out)
> >> >> > @@ -939,7 +1057,7 @@ consider_l3gw_lport(const struct
> >> sbrec_port_binding
> >> >> *pb,
> >> >> >      bool our_chassis = chassis_id && !strcmp(chassis_id,
> >> >> >
> >> >> b_ctx_in->chassis_rec->name);
> >> >> >
> >> >> > -    consider_nonvif_lport_(pb, our_chassis, true, b_ctx_in,
> >> b_ctx_out);
> >> >> > +    return consider_nonvif_lport_(pb, our_chassis, true, b_ctx_in,
> >> >> b_ctx_out);
> >> >> >  }
> >> >> >
> >> >> >  static void
> >> >> > @@ -958,7 +1076,7 @@ consider_localnet_lport(const struct
> >> >> sbrec_port_binding *pb,
> >> >> >      update_local_lport_ids(b_ctx_out->local_lport_ids, pb);
> >> >> >  }
> >> >> >
> >> >> > -static void
> >> >> > +static bool
> >> >> >  consider_ha_lport(const struct sbrec_port_binding *pb,
> >> >> >                    struct binding_ctx_in *b_ctx_in,
> >> >> >                    struct binding_ctx_out *b_ctx_out)
> >> >> > @@ -986,18 +1104,18 @@ consider_ha_lport(const struct
> >> sbrec_port_binding
> >> >> *pb,
> >> >> >                             b_ctx_out->local_datapaths);
> >> >> >      }
> >> >> >
> >> >> > -    consider_nonvif_lport_(pb, our_chassis, false, b_ctx_in,
> >> b_ctx_out);
> >> >> > +    return consider_nonvif_lport_(pb, our_chassis, false,
> b_ctx_in,
> >> >> b_ctx_out);
> >> >> >  }
> >> >> >
> >> >> > -static void
> >> >> > +static bool
> >> >> >  consider_cr_lport(const struct sbrec_port_binding *pb,
> >> >> >                    struct binding_ctx_in *b_ctx_in,
> >> >> >                    struct binding_ctx_out *b_ctx_out)
> >> >> >  {
> >> >> > -    consider_ha_lport(pb, b_ctx_in, b_ctx_out);
> >> >> > +    return consider_ha_lport(pb, b_ctx_in, b_ctx_out);
> >> >> >  }
> >> >> >
> >> >> > -static void
> >> >> > +static bool
> >> >> >  consider_external_lport(const struct sbrec_port_binding *pb,
> >> >> >                          struct binding_ctx_in *b_ctx_in,
> >> >> >                          struct binding_ctx_out *b_ctx_out)
> >> >> > @@ -1050,6 +1168,8 @@ build_local_bindings(struct binding_ctx_in
> >> >> *b_ctx_in,
> >> >> >                  }
> >> >> >
> >> >> >                  sset_add(b_ctx_out->local_lports, iface_id);
> >> >> > +                smap_replace(b_ctx_out->local_iface_ids,
> >> iface_rec->name,
> >> >> > +                             iface_id);
> >> >> >              }
> >> >> >
> >> >> >              /* Check if this is a tunnel interface. */
> >> >> > @@ -1165,9 +1285,9 @@ binding_run(struct binding_ctx_in *b_ctx_in,
> >> struct
> >> >> binding_ctx_out *b_ctx_out)
> >> >> >       * corresponding local datapath accordingly. */
> >> >> >      struct localnet_lport *lnet_lport;
> >> >> >      LIST_FOR_EACH_POP (lnet_lport, list_node, &localnet_lports) {
> >> >> > -        consider_localnet_port(lnet_lport->pb, &bridge_mappings,
> >> >> > -                               b_ctx_out->egress_ifaces,
> >> >> > -                               b_ctx_out->local_datapaths);
> >> >> > +        update_ld_localnet_port(lnet_lport->pb, &bridge_mappings,
> >> >> > +                                b_ctx_out->egress_ifaces,
> >> >> > +                                b_ctx_out->local_datapaths);
> >> >> >          free(lnet_lport);
> >> >> >      }
> >> >> >
> >> >> > @@ -1185,95 +1305,625 @@ binding_run(struct binding_ctx_in
> *b_ctx_in,
> >> >> struct binding_ctx_out *b_ctx_out)
> >> >> >      destroy_qos_map(&qos_map);
> >> >> >  }
> >> >> >
> >> >> > -/* Returns true if port-binding changes potentially require flow
> >> changes
> >> >> on
> >> >> > - * the current chassis. Returns false if we are sure there is no
> >> impact.
> >> >> */
> >> >> > +/* Returns true if the database is all cleaned up, false if more
> work
> >> is
> >> >> > + * required. */
> >> >> >  bool
> >> >> > -binding_evaluate_port_binding_changes(struct binding_ctx_in
> *b_ctx_in,
> >> >> > -                                      struct binding_ctx_out
> >> *b_ctx_out)
> >> >> > +binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn,
> >> >> > +                const struct sbrec_port_binding_table
> >> >> *port_binding_table,
> >> >> > +                const struct sbrec_chassis *chassis_rec)
> >> >> >  {
> >> >> > -    if (!b_ctx_in->chassis_rec) {
> >> >> > +    if (!ovnsb_idl_txn) {
> >> >> > +        return false;
> >> >> > +    }
> >> >> > +    if (!chassis_rec) {
> >> >> >          return true;
> >> >> >      }
> >> >> >
> >> >> > -    bool changed = false;
> >> >> > -
> >> >> >      const struct sbrec_port_binding *binding_rec;
> >> >> > -    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (binding_rec,
> >> >> > -
> >> >> b_ctx_in->port_binding_table) {
> >> >> > -        /* XXX: currently OVSDB change tracking doesn't support
> >> getting
> >> >> old
> >> >> > -         * data when the operation is update, so if a port-binding
> >> moved
> >> >> from
> >> >> > -         * this chassis to another, there is no easy way to find
> out
> >> the
> >> >> > -         * change. To workaround this problem, we just makes sure
> if
> >> >> > -         * any port *related to* this chassis has any change, then
> >> >> trigger
> >> >> > -         * recompute.
> >> >> > -         *
> >> >> > -         * - If a regular VIF is unbound from this chassis, the
> local
> >> >> ovsdb
> >> >> > -         *   interface table will be updated, which will trigger
> >> >> recompute.
> >> >> > -         *
> >> >> > -         * - If the port is not a regular VIF, always trigger
> >> recompute.
> >> >> */
> >> >> > -        if (binding_rec->chassis == b_ctx_in->chassis_rec) {
> >> >> > -            changed = true;
> >> >> > +    bool any_changes = false;
> >> >> > +    SBREC_PORT_BINDING_TABLE_FOR_EACH (binding_rec,
> >> port_binding_table) {
> >> >> > +        if (binding_rec->chassis == chassis_rec) {
> >> >> > +            if (binding_rec->encap) {
> >> >> > +                sbrec_port_binding_set_encap(binding_rec, NULL);
> >> >> > +            }
> >> >> > +            sbrec_port_binding_set_chassis(binding_rec, NULL);
> >> >> > +            any_changes = true;
> >> >> > +        }
> >> >> > +    }
> >> >> > +
> >> >> > +    if (any_changes) {
> >> >> > +        ovsdb_idl_txn_add_comment(
> >> >> > +            ovnsb_idl_txn,
> >> >> > +            "ovn-controller: removing all port bindings for '%s'",
> >> >> > +            chassis_rec->name);
> >> >> > +    }
> >> >> > +
> >> >> > +    return !any_changes;
> >> >> > +}
> >> >> > +
> >> >> > +static void
> >> >> > +add_local_datapath_peer_port(const struct sbrec_port_binding *pb,
> >> >> > +                             struct binding_ctx_in *b_ctx_in,
> >> >> > +                             struct binding_ctx_out *b_ctx_out,
> >> >> > +                             struct local_datapath *ld)
> >> >> > +{
> >> >> > +    const char *peer_name = smap_get(&pb->options, "peer");
> >> >> > +    if (strcmp(pb->type, "patch") || !peer_name) {
> >> >> > +        return;
> >> >> > +    }
> >> >> > +
> >> >> > +    const struct sbrec_port_binding *peer;
> >> >> > +    peer =
> lport_lookup_by_name(b_ctx_in->sbrec_port_binding_by_name,
> >> >> > +                                peer_name);
> >> >> > +
> >> >> > +    if (!peer || !peer->datapath) {
> >> >> > +        return;
> >> >> > +    }
> >> >> > +
> >> >> > +    bool present = false;
> >> >> > +    for (size_t i = 0; i < ld->n_peer_ports; i++) {
> >> >> > +        if (ld->peer_ports[i].local == pb) {
> >> >> > +            present = true;
> >> >> >              break;
> >> >> >          }
> >> >> > +    }
> >> >> >
> >> >> > -        if (!strcmp(binding_rec->type, "remote")) {
> >> >> > -            continue;
> >> >> > +    if (!present) {
> >> >> > +        ld->n_peer_ports++;
> >> >> > +        if (ld->n_peer_ports > ld->n_allocated_peer_ports) {
> >> >> > +            ld->peer_ports =
> >> >> > +                x2nrealloc(ld->peer_ports,
> >> >> > +                           &ld->n_allocated_peer_ports,
> >> >> > +                           sizeof *ld->peer_ports);
> >> >> > +        }
> >> >> > +        ld->peer_ports[ld->n_peer_ports - 1].local = pb;
> >> >> > +        ld->peer_ports[ld->n_peer_ports - 1].remote = peer;
> >> >> > +    }
> >> >> > +
> >> >> > +    struct local_datapath *peer_ld =
> >> >> > +        get_local_datapath(b_ctx_out->local_datapaths,
> >> >> > +                           peer->datapath->tunnel_key);
> >> >> > +    if (!peer_ld) {
> >> >> > +
>  add_local_datapath__(b_ctx_in->sbrec_datapath_binding_by_key,
> >> >> > +
> b_ctx_in->sbrec_port_binding_by_datapath,
> >> >> > +                             b_ctx_in->sbrec_port_binding_by_name,
> >> >> > +                             peer->datapath, false,
> >> >> > +                             1, b_ctx_out->local_datapaths);
> >> >> > +        return;
> >> >> > +    }
> >> >> > +
> >> >> > +    for (size_t i = 0; i < peer_ld->n_peer_ports; i++) {
> >> >> > +        if (peer_ld->peer_ports[i].local == peer) {
> >> >> > +            return;
> >> >> >          }
> >> >> > +    }
> >> >> >
> >> >> > -        if (strcmp(binding_rec->type, "")) {
> >> >> > -            changed = true;
> >> >> > +    peer_ld->n_peer_ports++;
> >> >> > +    if (peer_ld->n_peer_ports > peer_ld->n_allocated_peer_ports) {
> >> >> > +        peer_ld->peer_ports =
> >> >> > +            x2nrealloc(peer_ld->peer_ports,
> >> >> > +                        &peer_ld->n_allocated_peer_ports,
> >> >> > +                        sizeof *peer_ld->peer_ports);
> >> >> > +    }
> >> >> > +    peer_ld->peer_ports[peer_ld->n_peer_ports - 1].local = peer;
> >> >> > +    peer_ld->peer_ports[peer_ld->n_peer_ports - 1].remote = pb;
> >> >> > +}
> >> >> > +
> >> >> > +static void
> >> >> > +remove_local_datapath_peer_port(const struct sbrec_port_binding
> *pb,
> >> >> > +                                struct local_datapath *ld,
> >> >> > +                                struct hmap *local_datapaths)
> >> >> > +{
> >> >> > +    size_t i = 0;
> >> >> > +    for (i = 0; i < ld->n_peer_ports; i++) {
> >> >> > +        if (ld->peer_ports[i].local == pb) {
> >> >> >              break;
> >> >> >          }
> >> >> > +    }
> >> >> > +
> >> >> > +    if (i == ld->n_peer_ports) {
> >> >> > +        return;
> >> >> > +    }
> >> >> > +
> >> >> > +    const struct sbrec_port_binding *peer =
> ld->peer_ports[i].remote;
> >> >> >
> >> >> > -        struct local_binding *lbinding = NULL;
> >> >> > -        if (!binding_rec->parent_port ||
> >> !binding_rec->parent_port[0]) {
> >> >> > -            lbinding =
> local_binding_find(b_ctx_out->local_bindings,
> >> >> > -
>  binding_rec->logical_port);
> >> >> > +    /* Possible improvement: We can shrint the allocated peer
> ports
> >> >> > +     * if (ld->n_peer_ports < ld->n_allocated_peer_ports / 2).
> >> >> > +     */
> >> >> > +    ld->peer_ports[i].local = ld->peer_ports[ld->n_peer_ports -
> >> 1].local;
> >> >> > +    ld->peer_ports[i].remote = ld->peer_ports[ld->n_peer_ports -
> >> >> 1].remote;
> >> >> > +    ld->n_peer_ports--;
> >> >> > +
> >> >> > +    struct local_datapath *peer_ld =
> >> >> > +        get_local_datapath(local_datapaths,
> >> peer->datapath->tunnel_key);
> >> >> > +    if (peer_ld) {
> >> >> > +        remove_local_datapath_peer_port(peer, peer_ld,
> >> local_datapaths);
> >> >>
> >> >> Sorry that I don't understand this part. peer's peer is the "pb" that
> is
> >> >> passed in the parameter of the current function call, so this
> recursive
> >> >> call would only try to remove the "pb", which is already removed
> (before
> >> >> calling this function). So I think it is useless.
> >> >
> >> >
> >> > Let's say there is sw0-lr0 of sw0 connected to the peer lr0-sw0 of
> lr0.
> >> >
> >> > When this function is called for sw0-lr0, it removes the peer ports
> >> sw0-lr0 from
> >> > the 'sw0' ld and then it calls recursively for lr0.
> >> >
> >> > Yes you're right. It will again call for sw0-lr0 of sw0. But that
> would
> >> be a no-op as
> >> > the for loop in the beginning cannot find sw0-lr0  in 'sw0' ld.. Even
> >> though it
> >> > is useless are there any issues with it ? Since its recursion and it
> has
> >> proper return
> >> > conditions, I think it should be fine.
> >> >
> >> > Another option is duplicating the code for removing the peer.
> >> >
> >>
> >> OK, I thought it is useless, but it seems needed.
> >>
> >> >
> >> >>
> >> >> > +    }
> >> >> > +}
> >> >> > +
> >> >> > +static void
> >> >> > +remove_pb_from_local_datapath(const struct sbrec_port_binding *pb,
> >> >> > +                              const struct sbrec_chassis
> *chassis_rec,
> >> >> > +                              struct binding_ctx_out *b_ctx_out,
> >> >> > +                              struct local_datapath *ld)
> >> >> > +{
> >> >> > +    remove_local_lport_ids(pb, b_ctx_out->local_lport_ids);
> >> >> > +    if (!strcmp(pb->type, "patch") ||
> >> >> > +        !strcmp(pb->type, "l3gateway")) {
> >> >> > +        remove_local_datapath_peer_port(pb, ld,
> >> >> b_ctx_out->local_datapaths);
> >> >> > +    } else if (!strcmp(pb->type, "localnet")) {
> >> >> > +        if (ld->localnet_port &&
> >> !strcmp(ld->localnet_port->logical_port,
> >> >> > +                                         pb->logical_port)) {
> >> >> > +            ld->localnet_port = NULL;
> >> >> > +        }
> >> >> > +    } else if (!strcmp(pb->type, "l3gateway")) {
> >> >> > +        const char *chassis_id = smap_get(&pb->options,
> >> >> > +                                          "l3gateway-chassis");
> >> >> > +        if (chassis_id && !strcmp(chassis_id, chassis_rec->name))
> {
> >> >> > +            ld->has_local_l3gateway = false;
> >> >> > +        }
> >> >> > +    }
> >> >>
> >> >> I think we need to remove ld from the local_datapaths if the current
> pb
> >> is
> >> >> the only reason for that ld to be exist on this chassis. Also, all
> the
> >> >> related lds connected by patch ports should be removed as well, if
> there
> >> >> are no other pbs causing them to be added to this chassis. This is
> the
> >> >> reverse of add_local_datapath__(). It doesn't seem to be easy without
> >> going
> >> >> through all local_lbindings. Maybe this can be done in
> >> >> binding_handle_ovs_interface_changes() and
> >> >> binding_handle_port_binding_changes() out of the loop.
> >> >>
> >> >
> >> > I left this unaddressed intentionally as I think it will complicate
> the
> >> code.
> >> >
> >> > Patch 3 of this series handles I-P for datapath and when a datapath is
> >> deleted,
> >> > it removes the datapath from local_datapaths if it is present.
> >> > I think this should be Ok as we will eventually free it. If we really
> >> want to address this,
> >> > I'm fine with it but I'd prefer a follow up patch to do this.
> >> >
> >> OK, maybe a comment can be added to make it clear for future
> improvement.
> >> This changes the behavior of current implementation. Now unrelated
> >> datapaths are removed when certain ports are unbound, and the flows in
> the
> >> HV can be largely reduced, but with this change it may not reduce any
> more
> >> until a recompute is triggered. It is not a big problem I think.
> >
> >
> > Sure. I'll add a comment about it. I was also thinking of adding the same
> in TODO.rst.
> > But it looks like TODO.rst needs some cleaning first.
> >
> >>
> >> >
> >> >>
> >> >> > +}
> >> >> > +
> >> >> > +static bool
> >> >> > +handle_iface_add(const struct ovsrec_interface *iface_rec,
> >> >> > +                 const char *iface_id,
> >> >> > +                 struct binding_ctx_in *b_ctx_in,
> >> >> > +                 struct binding_ctx_out *b_ctx_out,
> >> >> > +                 struct hmap *qos_map)
> >> >> > +{
> >> >> > +    sset_add(b_ctx_out->local_lports, iface_id);
> >> >> > +    smap_replace(b_ctx_out->local_iface_ids, iface_rec->name,
> >> iface_id);
> >> >> > +
> >> >> > +    struct local_binding *lbinding =
> >> >> > +        local_binding_find(b_ctx_out->local_bindings, iface_id);
> >> >> > +
> >> >> > +    if (!lbinding) {
> >> >> > +        lbinding = local_binding_create(iface_id, iface_rec, NULL,
> >> >> BT_VIF);
> >> >> > +        local_binding_add(b_ctx_out->local_bindings, lbinding);
> >> >> > +    } else {
> >> >> > +        lbinding->iface = iface_rec;
> >> >> > +    }
> >> >> > +
> >> >> > +    if (!lbinding->pb || strcmp(lbinding->name,
> >> >> lbinding->pb->logical_port)) {
> >> >> > +        lbinding->pb = lport_lookup_by_name(
> >> >> > +            b_ctx_in->sbrec_port_binding_by_name, lbinding->name);
> >> >> > +        if (lbinding->pb && !strcmp(lbinding->pb->type,
> "virtual")) {
> >> >>
> >> >> Since we never bind "virtual" ports, this should be a
> misconfiguration,
> >> and
> >> >> it is better to log a warning message to be more clear.
> >> >>
> >> >> > +            lbinding->pb = NULL;
> >> >> > +        }
> >> >> > +    }
> >> >> > +
> >> >> > +    if (lbinding->pb) {
> >> >> > +        if (!consider_vif_lport(lbinding->pb, b_ctx_in, b_ctx_out,
> >> >> > +                                lbinding, qos_map)) {
> >> >> > +            return false;
> >> >> > +        }
> >> >> > +    }
> >> >> > +
> >> >> > +    /* Update the child local_binding's iface (if any children)
> and
> >> try
> >> >> to
> >> >> > +     *  claim the container lbindings. */
> >> >> > +    struct shash_node *node;
> >> >> > +    SHASH_FOR_EACH (node, &lbinding->children) {
> >> >> > +        struct local_binding *child = node->data;
> >> >> > +        child->iface = iface_rec;
> >> >> > +        if (child->type == BT_CONTAINER) {
> >> >> > +            if (!consider_container_lport(child->pb, b_ctx_in,
> >> b_ctx_out,
> >> >> > +                                          qos_map)) {
> >> >> > +                return false;
> >> >> > +            }
> >> >> > +        }
> >> >> > +    }
> >> >> > +
> >> >> > +    return true;
> >> >> > +}
> >> >> > +
> >> >> > +static bool
> >> >> > +handle_iface_delete(const char *lport_name, const char
> *iface_name,
> >> >> > +                    struct binding_ctx_in *b_ctx_in,
> >> >> > +                    struct binding_ctx_out *b_ctx_out, bool
> *changed)
> >> >> > +{
> >> >> > +    struct local_binding *lbinding;
> >> >> > +    lbinding = local_binding_find(b_ctx_out->local_bindings,
> >> >> > +                                  lport_name);
> >> >> > +    if (lbinding && lbinding->pb &&
> >> >> > +        lbinding->pb->chassis == b_ctx_in->chassis_rec) {
> >> >> > +
> >> >> > +        if (!release_local_binding(b_ctx_in->chassis_rec,
> lbinding,
> >> >> > +                                   !b_ctx_in->ovnsb_idl_txn)) {
> >> >> > +            return false;
> >> >> > +        }
> >> >> > +
> >> >> > +        struct local_datapath *ld =
> >> >> > +            get_local_datapath(b_ctx_out->local_datapaths,
> >> >> > +
>  lbinding->pb->datapath->tunnel_key);
> >> >> > +        if (ld) {
> >> >> > +            remove_pb_from_local_datapath(lbinding->pb,
> >> >> > +                                            b_ctx_in->chassis_rec,
> >> >> > +                                            b_ctx_out, ld);
> >> >> > +        }
> >> >> > +        local_binding_delete(b_ctx_out->local_bindings, lbinding);
> >> >> > +        *changed = true;
> >> >> > +    }
> >> >> > +
> >> >> > +    sset_find_and_delete(b_ctx_out->local_lports, lport_name);
> >> >> > +    smap_remove(b_ctx_out->local_iface_ids, iface_name);
> >> >> > +
> >> >> > +    return true;
> >> >> > +}
> >> >> > +
> >> >> > +static bool
> >> >> > +is_iface_vif(const struct ovsrec_interface *iface_rec)
> >> >> > +{
> >> >> > +    if (iface_rec->type && iface_rec->type[0] &&
> >> >> > +        strcmp(iface_rec->type, "internal")) {
> >> >> > +        return false;
> >> >> > +    }
> >> >> > +
> >> >> > +    return true;
> >> >> > +}
> >> >> > +
> >> >> > +/* Returns true if the ovs interface changes were handled
> >> successfully,
> >> >> > + * false otherwise.
> >> >> > + */
> >> >> > +bool
> >> >> > +binding_handle_ovs_interface_changes(struct binding_ctx_in
> *b_ctx_in,
> >> >> > +                                     struct binding_ctx_out
> >> *b_ctx_out,
> >> >> > +                                     bool *changed)
> >> >> > +{
> >> >> > +    if (!b_ctx_in->chassis_rec) {
> >> >> > +        return false;
> >> >> > +    }
> >> >> > +
> >> >> > +    bool handled = true;
> >> >> > +    *changed = false;
> >> >> > +
> >> >> > +    /* Run the tracked interfaces loop twice. One to handle
> deleted
> >> >> > +     * changes. And another to handle add/update changes.
> >> >> > +     * This will ensure correctness.
> >> >> > +     */
> >> >> > +    const struct ovsrec_interface *iface_rec;
> >> >> > +    OVSREC_INTERFACE_TABLE_FOR_EACH_TRACKED (iface_rec,
> >> >> > +
> b_ctx_in->iface_table) {
> >> >> > +        if (!is_iface_vif(iface_rec)) {
> >> >> > +            /* Right now are not handling ovs_interface changes of
> >> >> > +             * other types. This can be enhanced to handle of
> >> >> > +             * types - patch and tunnel. */
> >> >> > +            handled = false;
> >> >> > +            break;
> >> >> > +        }
> >> >> > +
> >> >> > +        const char *iface_id = smap_get(&iface_rec->external_ids,
> >> >> "iface-id");
> >> >> > +        const char *old_iface_id =
> >> smap_get(b_ctx_out->local_iface_ids,
> >> >> > +                                            iface_rec->name);
> >> >> > +        const char *cleared_iface_id = NULL;
> >> >> > +        if (!ovsrec_interface_is_deleted(iface_rec)) {
> >> >> > +            if (iface_id) {
> >> >> > +                /* Check if iface_id is changed. If so we need to
> >> >> > +                 * release the old port binding and associate this
> >> >> > +                 * inteface to new port binding. */
> >> >> > +                if (old_iface_id && strcmp(iface_id,
> old_iface_id)) {
> >> >> > +                    cleared_iface_id = old_iface_id;
> >> >> > +                }
> >> >> > +            } else if (old_iface_id) {
> >> >> > +                cleared_iface_id = old_iface_id;
> >> >> > +            }
> >> >> >          } else {
> >> >> > -            lbinding =
> local_binding_find(b_ctx_out->local_bindings,
> >> >> > -
>  binding_rec->parent_port);
> >> >> > +            cleared_iface_id = iface_id;
> >> >> > +            iface_id = NULL;
> >> >> >          }
> >> >> >
> >> >> > -        if (lbinding) {
> >> >> > -            changed = true;
> >> >> > +        if (cleared_iface_id) {
> >> >> > +            handled = handle_iface_delete(cleared_iface_id,
> >> >> iface_rec->name,
> >> >> > +                                          b_ctx_in, b_ctx_out,
> >> changed);
> >> >> > +        }
> >> >> > +
> >> >> > +        if (!handled) {
> >> >> >              break;
> >> >> >          }
> >> >> >      }
> >> >> >
> >> >> > -    return changed;
> >> >> > +    if (!handled) {
> >> >> > +        return false;
> >> >> > +    }
> >> >> > +
> >> >> > +    struct hmap qos_map = HMAP_INITIALIZER(&qos_map);
> >> >> > +    struct hmap *qos_map_ptr =
> >> >> > +        sset_is_empty(b_ctx_out->egress_ifaces) ? NULL : &qos_map;
> >> >> > +
> >> >> > +    OVSREC_INTERFACE_TABLE_FOR_EACH_TRACKED (iface_rec,
> >> >> > +
> b_ctx_in->iface_table) {
> >> >> > +        /* Loop to handle create and update changes only. */
> >> >>
> >> >> Thanks for splitting the loops, but here are still some problems.
> >> >>
> >> >> 1. The first loop handled: a) interface deletion. b) interface update
> >> with
> >> >> old iface-id cleared. So in this second loop it should handle all the
> >> rest
> >> >> cases: a) interface add. b) interface update without iface-id change.
> It
> >> >> seems b) is treated as just interface add, which may be wrong: if
> >> iface-id
> >> >> didn't change but some other fields of the interface record is
> changed,
> >> it
> >> >> is not handled properly.
> >> >
> >> >
> >> > If other fields change, what should we handle ? Can ofport change ?
> >> > I'll see if ofport can be changed and what happens when it changes.
> >> >
> >> > For other fields like name, mtu etc do we need to handle anything ?
> >> > I think we only care about the external_ids:iface-id and the ofport.
> >> > The function handle_iface_add() even if it is called for other
> changes,
> >> > it will be a no-op.
> >> >
> >> > Also from the tracking we can't figure out what fields change right ?
> >> >
> >> >
> >> Yes, I think it is hard to check with fields are changed and handle it
> >> accordingly. That's why I suggest to make it simple: if there is an
> update,
> >> always handle it like a deletion and then creation. This way, we dont
> need
> >> to consider all the corner cases and we are sure it is correct.
> >
> >
> > In this particular case of binding, I don't think there are any corner
> cases..
> >
> > We consider an OVS interface for binding if the below 2 conditions meet
> >
> > 1. external_ids:iface-id is set
> > 2. ofport is > 0.
> >
> > Otherwise we need to consider a release if it was already claimed.
> >
> > When  an ovs interface is updated like below
> >  $ovs-vsctl set interface p1 external_ids:foo=bar
> >
> > We don't need to handle it like a deletion and then a creation. Because
> we can
> > easily see that the above conditions are met,
> >
> What you described is correct (I believe). However, it is the declarative
> way of thinking, which is perfect for the always recompute implementation,
> but it is hard to reason the correctness and to maintain in longer term for
> incremental processing. Incremental processing focuses on the data change
> instead of the desired status. In this case you may have already thought
> through and sure about the correctness even some data change is ignored.
> However, in the long run, it is hard to remember all the details about
> which field change needs to be handled in which way, and why some other
> changes are ignored. More importantly, when a developer who is not so
> familiar with this changle handling logic want to add a new feature, which
> requires another field, e.g. another key-value in external_ids, it is easy
> to forget to update here, and not easy to catch in code review either, and
> even we do aware it needs to be updated here, the if condition would get so
> complex even with just 3 fields to be considered.



I think adding comments should be enough for a new user. And I'll make sure
I add proper comments.
In the case of binding an interface, I really don't see why would anything
change in the future. Even
if it changes, I think the test cases should fail if that is the case.



> This is why I think we
> should make it "simple and stupid" to handle updates as if the old one was
> deleted and then a new one is added.



If suppose an OVS interface external_ids:iface-id is updated from 'foo' to
'bar'
and if 'foo' logical port was already bound, then we still need to check
what was
the old value from the 'b_ctx_out->local_iface_ids' and release that port
binding
before considering claiming the port binding for 'bar'.

In my opinion calling delete followed by create will not help. And we still
need to maintain the
logic of checking the necessary fields as the ovsdb tracking doesn't say
what changed.
And I don't think calling delete, followed by create is going to make the
code simpler. Or will work
without any changes later if there is a need to handle port binding
differently.



> At least this has provided me a lot of
> convenience in the earlier I-P implementations, and I was even surprised
> that there were so many new features added but most of them didn't needed
> to be aware of the I-P details, thanks to this principle. I hope this
> clarifies the idea behind.
>

Ok. But I'm not sure if we should apply this principle as a strict rule.


> > It also creates unnecessary deletion and re-adding of OVS flows related
> to that
> > logical port when we handle flow computation for changed runtime data.
> >
> > If we handle this way, there won't be any runtime data changes.
> >
>
> I agree it may be less optimized, but considering that we are incrementally
> processing the changes here, even if there are many interfaces deteled and
> added unnecesarily, the cost of I-P for such changes is just nothing
> comparing with the full recompute. I believe it would be good enough from
> the end user point of view, if we can make it incrementally processed,
> correctly.
>
>
But the idea of this whole patch series is also to improve the performance
as much
as we can, because of the scale issues we have  seen.

I would also like to hear the opinion of Mark, Dumitru and others .

Thanks
Numan


> > Thanks
> > Numan
> >
> >>
> >> >
> >> >>
> >> >>
> >> >> 2. Some other condition checks should be done for this loop, such as:
> if
> >> >> (!is_iface_vif(iface_rec)) ...
> >> >
> >> >
> >> > This is not required because the first loop checks for it and if there
> is
> >> any
> >> > non vif interface change, then this function returns false, before the
> >> > second loop.
> >> >
> >> OK.
> >>
> >> >>
> >> >>
> >> >> > +        if (ovsrec_interface_is_deleted(iface_rec)) {
> >> >> > +            continue;
> >> >> > +        }
> >> >> > +
> >> >> > +        const char *iface_id = smap_get(&iface_rec->external_ids,
> >> >> "iface-id");
> >> >> > +        int64_t ofport = iface_rec->n_ofport ? *iface_rec->ofport
> : 0;
> >> >> > +        if (iface_id && ofport > 0) {
> >> >>
> >> >> What if iface-id didn't change, but ofport changed from non-zero to
> 0?
> >> How
> >> >> should we handle the change?
> >> >>
> >> >
> >> > Good point. I think we should consider this as  handle_iface_delete.
> >> >
> >> > I'll rename the function handle_iface_delete() to
> handle_iface_release()
> >> > and handle_iface_add() to handle_iface_claim() to be more clear.
> >> >
> >> >> A general approach can be: whenever there is an update
> >> >> (!ovsrec_interface_is_new()), we firstly handle it as interface
> delete,
> >> and
> >> >> then handle an interface add.
> >> >
> >> >
> >> > But why delete and add if can determine that it is going to be either
> one
> >> > i.e release or claim and I think we can definitely determine.
> >> > Also in patch 6/7 where we do tracking, it becomes a bit complicated
> >> > if we handle it as delete and then add as we may add 2 such tracking
> >> > data.
> >> >
> >> It is for simplicity and correctness. Please see the comment above.
> >>
> >> > Thanks
> >> > Numan
> >> >
> >> >>
> >> >> > +            *changed = true;
> >> >> > +            handled = handle_iface_add(iface_rec, iface_id,
> b_ctx_in,
> >> >> > +                                       b_ctx_out, qos_map_ptr);
> >> >> > +            if (!handled) {
> >> >> > +                break;
> >> >> > +            }
> >> >> > +        }
> >> >> > +    }
> >> >> > +
> >> >> > +    if (handled && qos_map_ptr &&
> set_noop_qos(b_ctx_in->ovs_idl_txn,
> >> >> > +
> b_ctx_in->port_table,
> >> >> > +
>  b_ctx_in->qos_table,
> >> >> > +
> >> >> b_ctx_out->egress_ifaces)) {
> >> >> > +        const char *entry;
> >> >> > +        SSET_FOR_EACH (entry, b_ctx_out->egress_ifaces) {
> >> >> > +            setup_qos(entry, &qos_map);
> >> >> > +        }
> >> >> > +    }
> >> >> > +
> >> >> > +    destroy_qos_map(&qos_map);
> >> >> > +    return handled;
> >> >> >  }
> >> >> >
> >> >> > -/* Returns true if the database is all cleaned up, false if more
> work
> >> is
> >> >> > - * required. */
> >> >> > -bool
> >> >> > -binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn,
> >> >> > -                const struct sbrec_port_binding_table
> >> >> *port_binding_table,
> >> >> > -                const struct sbrec_chassis *chassis_rec)
> >> >> > +static void
> >> >> > +handle_deleted_lport(const struct sbrec_port_binding *pb,
> >> >> > +                     struct binding_ctx_in *b_ctx_in,
> >> >> > +                     struct binding_ctx_out *b_ctx_out)
> >> >> >  {
> >> >> > -    if (!ovnsb_idl_txn) {
> >> >> > +    struct local_datapath *ld =
> >> >> > +        get_local_datapath(b_ctx_out->local_datapaths,
> >> >> > +                           pb->datapath->tunnel_key);
> >> >> > +    if (ld) {
> >> >> > +        remove_pb_from_local_datapath(pb, b_ctx_in->chassis_rec,
> >> >> > +                                      b_ctx_out, ld);
> >> >> > +    }
> >> >> > +}
> >> >> > +
> >> >> > +static struct local_binding *
> >> >> > +get_lbinding_for_lport(const struct sbrec_port_binding *pb,
> >> >> > +                       enum en_lport_type lport_type,
> >> >> > +                       struct binding_ctx_out *b_ctx_out)
> >> >> > +{
> >> >> > +    ovs_assert(lport_type == LP_VIF || lport_type == LP_VIRTUAL);
> >> >> > +
> >> >> > +    if (lport_type == LP_VIF && !is_lport_container(pb)) {
> >> >> > +        return local_binding_find(b_ctx_out->local_bindings,
> >> >> pb->logical_port);
> >> >> > +    }
> >> >> > +
> >> >> > +    struct local_binding *parent_lbinding = NULL;
> >> >> > +
> >> >> > +    if (lport_type == LP_VIRTUAL) {
> >> >> > +        parent_lbinding =
> >> local_binding_find(b_ctx_out->local_bindings,
> >> >> > +                                             pb->virtual_parent);
> >> >> > +    } else {
> >> >> > +        parent_lbinding =
> >> local_binding_find(b_ctx_out->local_bindings,
> >> >> > +                                             pb->parent_port);
> >> >> > +    }
> >> >> > +
> >> >> > +    return parent_lbinding
> >> >> > +           ? local_binding_find(&parent_lbinding->children,
> >> >> pb->logical_port)
> >> >> > +           : NULL;
> >> >> > +}
> >> >> > +
> >> >> > +static bool
> >> >> > +handle_deleted_vif_lport(const struct sbrec_port_binding *pb,
> >> >> > +                         enum en_lport_type lport_type,
> >> >> > +                         struct binding_ctx_in *b_ctx_in,
> >> >> > +                         struct binding_ctx_out *b_ctx_out,
> >> >> > +                         bool *changed)
> >> >> > +{
> >> >> > +    struct local_binding *lbinding =
> >> >> > +        get_lbinding_for_lport(pb, lport_type, b_ctx_out);
> >> >> > +
> >> >> > +    if (lbinding) {
> >> >> > +        lbinding->pb = NULL;
> >> >> > +        /* The port_binding 'pb' is deleted. So there is no need
> to
> >> >> > +         * clear the 'chassis' column of 'pb'. But we need to do
> >> >> > +         * for the local_binding's children. */
> >> >> > +        if (lbinding->type == BT_VIF &&
> >> >> > +
>  !release_local_binding_children(b_ctx_in->chassis_rec,
> >> >> > +                                                lbinding,
> >> >> > +
> >> >>  !b_ctx_in->ovnsb_idl_txn)) {
> >> >> > +            return false;
> >> >> > +        }
> >> >> > +
> >> >> > +        *changed = true;
> >> >> > +    }
> >> >> > +
> >> >> > +    handle_deleted_lport(pb, b_ctx_in, b_ctx_out);
> >> >> > +    return true;
> >> >> > +}
> >> >> > +
> >> >> > +static bool
> >> >> > +handle_updated_vif_lport(const struct sbrec_port_binding *pb,
> >> >> > +                         enum en_lport_type lport_type,
> >> >> > +                         struct binding_ctx_in *b_ctx_in,
> >> >> > +                         struct binding_ctx_out *b_ctx_out,
> >> >> > +                         struct hmap *qos_map,
> >> >> > +                         bool *changed)
> >> >> > +{
> >> >> > +    bool claimed = (pb->chassis == b_ctx_in->chassis_rec);
> >> >> > +    bool handled = true;
> >> >> > +
> >> >> > +    if (lport_type == LP_VIRTUAL) {
> >> >> > +        handled = consider_virtual_lport(pb, b_ctx_in, b_ctx_out,
> >> >> qos_map);
> >> >> > +    } else if (lport_type == LP_VIF && is_lport_container(pb)) {
> >> >> > +        handled = consider_container_lport(pb, b_ctx_in,
> b_ctx_out,
> >> >> qos_map);
> >> >> > +    } else {
> >> >> > +        handled = consider_vif_lport(pb, b_ctx_in, b_ctx_out,
> NULL,
> >> >> qos_map);
> >> >> > +    }
> >> >> > +
> >> >> > +    if (!handled) {
> >> >> >          return false;
> >> >> >      }
> >> >> > -    if (!chassis_rec) {
> >> >> > +
> >> >> > +    bool now_claimed = (pb->chassis == b_ctx_in->chassis_rec);
> >> >> > +    bool changed_ = (claimed != now_claimed);
> >> >> > +
> >> >> > +    if (changed_) {
> >> >> > +        *changed = true;
> >> >> > +    }
> >> >> > +
> >> >> > +    if (lport_type == LP_VIRTUAL ||
> >> >> > +        (lport_type == LP_VIF && is_lport_container(pb)) ||
> >> !changed_) {
> >> >> >          return true;
> >> >> >      }
> >> >> >
> >> >> > -    const struct sbrec_port_binding *binding_rec;
> >> >> > -    bool any_changes = false;
> >> >> > -    SBREC_PORT_BINDING_TABLE_FOR_EACH (binding_rec,
> >> port_binding_table) {
> >> >> > -        if (binding_rec->chassis == chassis_rec) {
> >> >> > -            if (binding_rec->encap)
> >> >> > -                sbrec_port_binding_set_encap(binding_rec, NULL);
> >> >> > -            sbrec_port_binding_set_chassis(binding_rec, NULL);
> >> >> > -            any_changes = true;
> >> >> > +    struct local_binding *lbinding =
> >> >> > +        local_binding_find(b_ctx_out->local_bindings,
> >> pb->logical_port);
> >> >> > +
> >> >> > +    ovs_assert(lbinding);
> >> >> > +
> >> >> > +    struct shash_node *node;
> >> >> > +    SHASH_FOR_EACH (node, &lbinding->children) {
> >> >> > +        struct local_binding *child = node->data;
> >> >> > +        if (child->type == BT_CONTAINER) {
> >> >> > +            handled = consider_container_lport(child->pb,
> b_ctx_in,
> >> >> b_ctx_out,
> >> >> > +                                               qos_map);
> >> >> > +            if (!handled) {
> >> >> > +                return false;
> >> >> > +            }
> >> >> >          }
> >> >> >      }
> >> >> >
> >> >> > -    if (any_changes) {
> >> >> > -        ovsdb_idl_txn_add_comment(
> >> >> > -            ovnsb_idl_txn,
> >> >> > -            "ovn-controller: removing all port bindings for '%s'",
> >> >> > -            chassis_rec->name);
> >> >> > +    return true;
> >> >> > +}
> >> >> > +
> >> >> > +/* Returns true if the port binding changes resulted in local
> binding
> >> >> > + * updates, false otherwise.
> >> >> > + */
> >> >> > +bool
> >> >> > +binding_handle_port_binding_changes(struct binding_ctx_in
> *b_ctx_in,
> >> >> > +                                    struct binding_ctx_out
> *b_ctx_out,
> >> >> > +                                    bool *changed)
> >> >> > +{
> >> >> > +    bool handled = true;
> >> >> > +    *changed = false;
> >> >> > +
> >> >> > +    const struct sbrec_port_binding *pb;
> >> >> > +
> >> >> > +    /* Run the tracked port binding loop twice. One to handle
> deleted
> >> >> > +     * changes. And another to handle add/update changes.
> >> >> > +     * This will ensure correctness.
> >> >> > +     */
> >> >> > +    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
> >> >> > +
> >> >> b_ctx_in->port_binding_table) {
> >> >> > +        if (!sbrec_port_binding_is_deleted(pb)) {
> >> >> > +            continue;
> >> >> > +        }
> >> >> > +
> >> >> > +        enum en_lport_type lport_type = get_lport_type(pb);
> >> >> > +        if (lport_type == LP_VIF || lport_type == LP_VIRTUAL) {
> >> >> > +            handled = handle_deleted_vif_lport(pb, lport_type,
> >> b_ctx_in,
> >> >> > +                                                b_ctx_out,
> changed);
> >> >> > +        } else {
> >> >> > +            handle_deleted_lport(pb, b_ctx_in, b_ctx_out);
> >> >> > +        }
> >> >> > +
> >> >> > +        if (!handled) {
> >> >> > +            break;
> >> >> > +        }
> >> >> >      }
> >> >> >
> >> >> > -    return !any_changes;
> >> >> > +    if (!handled) {
> >> >> > +        return false;
> >> >> > +    }
> >> >> > +
> >> >> > +    struct hmap qos_map = HMAP_INITIALIZER(&qos_map);
> >> >> > +    struct hmap *qos_map_ptr =
> >> >> > +        sset_is_empty(b_ctx_out->egress_ifaces) ? NULL : &qos_map;
> >> >> > +
> >> >> > +    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
> >> >> > +
> >> >> b_ctx_in->port_binding_table) {
> >> >> > +        /* Loop to handle create and update changes only. */
> >> >> > +        if (sbrec_port_binding_is_deleted(pb)) {
> >> >> > +            continue;
> >> >> > +        }
> >> >> > +
> >> >> > +        enum en_lport_type lport_type = get_lport_type(pb);
> >> >> > +
> >> >> > +        struct local_datapath *ld =
> >> >> > +            get_local_datapath(b_ctx_out->local_datapaths,
> >> >> > +                               pb->datapath->tunnel_key);
> >> >> > +
> >> >> > +        switch (lport_type) {
> >> >> > +        case LP_VIF:
> >> >> > +        case LP_VIRTUAL:
> >> >> > +            handled = handle_updated_vif_lport(pb, lport_type,
> >> b_ctx_in,
> >> >> > +                                               b_ctx_out,
> qos_map_ptr,
> >> >> > +                                               changed);
> >> >> > +            break;
> >> >> > +
> >> >> > +        case LP_PATCH:
> >> >> > +        case LP_LOCALPORT:
> >> >> > +        case LP_VTEP:
> >> >> > +            *changed = true;
> >> >> > +            update_local_lport_ids(b_ctx_out->local_lport_ids,
> pb);
> >> >> > +            if (lport_type ==  LP_PATCH) {
> >> >> > +                /* Add the peer datapath to the local datapaths if
> >> it's
> >> >> > +                    * not present yet.
> >> >> > +                    */
> >> >> > +                if (ld) {
> >> >> > +                    add_local_datapath_peer_port(pb, b_ctx_in,
> >> >> > +                                                    b_ctx_out,
> ld);
> >> >> > +                }
> >> >> > +            }
> >> >> > +            break;
> >> >> > +
> >> >> > +        case LP_L2GATEWAY:
> >> >> > +            *changed = true;
> >> >> > +            handled = consider_l2gw_lport(pb, b_ctx_in,
> b_ctx_out);
> >> >> > +            break;
> >> >> > +
> >> >> > +        case LP_L3GATEWAY:
> >> >> > +            *changed = true;
> >> >> > +            handled = consider_l3gw_lport(pb, b_ctx_in,
> b_ctx_out);
> >> >> > +            break;
> >> >> > +
> >> >> > +        case LP_CHASSISREDIRECT:
> >> >> > +            *changed = true;
> >> >> > +            handled = consider_cr_lport(pb, b_ctx_in, b_ctx_out);
> >> >> > +            break;
> >> >> > +
> >> >> > +        case LP_EXTERNAL:
> >> >> > +            *changed = true;
> >> >> > +            handled = consider_external_lport(pb, b_ctx_in,
> >> b_ctx_out);
> >> >> > +            break;
> >> >> > +
> >> >> > +        case LP_LOCALNET: {
> >> >> > +            *changed = true;
> >> >> > +            consider_localnet_lport(pb, b_ctx_in, b_ctx_out,
> >> >> qos_map_ptr);
> >> >> > +
> >> >> > +            struct shash bridge_mappings =
> >> >> > +                SHASH_INITIALIZER(&bridge_mappings);
> >> >> > +            add_ovs_bridge_mappings(b_ctx_in->ovs_table,
> >> >> > +                                    b_ctx_in->bridge_table,
> >> >> > +                                    &bridge_mappings);
> >> >> > +            update_ld_localnet_port(pb, &bridge_mappings,
> >> >> > +                                    b_ctx_out->egress_ifaces,
> >> >> > +                                    b_ctx_out->local_datapaths);
> >> >> > +            shash_destroy(&bridge_mappings);
> >> >> > +            break;
> >> >> > +        }
> >> >> > +
> >> >> > +        case LP_REMOTE:
> >> >> > +        case LP_UNKNOWN:
> >> >> > +            break;
> >> >> > +        }
> >> >> > +
> >> >> > +        if (!handled) {
> >> >> > +            break;
> >> >> > +        }
> >> >> > +    }
> >> >> > +
> >> >> > +    if (handled && qos_map_ptr &&
> set_noop_qos(b_ctx_in->ovs_idl_txn,
> >> >> > +
> b_ctx_in->port_table,
> >> >> > +
>  b_ctx_in->qos_table,
> >> >> > +
> >> >> b_ctx_out->egress_ifaces)) {
> >> >> > +        const char *entry;
> >> >> > +        SSET_FOR_EACH (entry, b_ctx_out->egress_ifaces) {
> >> >> > +            setup_qos(entry, &qos_map);
> >> >> > +        }
> >> >> > +    }
> >> >> > +
> >> >> > +    destroy_qos_map(&qos_map);
> >> >> > +    return handled;
> >> >> >  }
> >> >> > diff --git a/controller/binding.h b/controller/binding.h
> >> >> > index 9affc9a96..f7ace6faf 100644
> >> >> > --- a/controller/binding.h
> >> >> > +++ b/controller/binding.h
> >> >> > @@ -57,6 +57,7 @@ struct binding_ctx_out {
> >> >> >      struct sset *local_lports;
> >> >> >      struct sset *local_lport_ids;
> >> >> >      struct sset *egress_ifaces;
> >> >> > +    struct smap *local_iface_ids;
> >> >> >  };
> >> >> >
> >> >> >  void binding_register_ovs_idl(struct ovsdb_idl *);
> >> >> > @@ -64,9 +65,13 @@ void binding_run(struct binding_ctx_in *, struct
> >> >> binding_ctx_out *);
> >> >> >  bool binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn,
> >> >> >                       const struct sbrec_port_binding_table *,
> >> >> >                       const struct sbrec_chassis *);
> >> >> > -bool binding_evaluate_port_binding_changes(struct binding_ctx_in
> *,
> >> >> > -                                           struct binding_ctx_out
> *);
> >> >> >
> >> >> >  void local_bindings_init(struct shash *local_bindings);
> >> >> >  void local_bindings_destroy(struct shash *local_bindings);
> >> >> > +bool binding_handle_ovs_interface_changes(struct binding_ctx_in *,
> >> >> > +                                          struct binding_ctx_out
> *,
> >> >> > +                                          bool *changed);
> >> >> > +bool binding_handle_port_binding_changes(struct binding_ctx_in *,
> >> >> > +                                         struct binding_ctx_out *,
> >> >> > +                                         bool *changed);
> >> >> >  #endif /* controller/binding.h */
> >> >> > diff --git a/controller/ovn-controller.c
> b/controller/ovn-controller.c
> >> >> > index 6b759966b..08b074415 100644
> >> >> > --- a/controller/ovn-controller.c
> >> >> > +++ b/controller/ovn-controller.c
> >> >> > @@ -753,6 +753,7 @@ enum sb_engine_node {
> >> >> >      OVS_NODE(open_vswitch, "open_vswitch") \
> >> >> >      OVS_NODE(bridge, "bridge") \
> >> >> >      OVS_NODE(port, "port") \
> >> >> > +    OVS_NODE(interface, "interface") \
> >> >> >      OVS_NODE(qos, "qos")
> >> >> >
> >> >> >  enum ovs_engine_node {
> >> >> > @@ -974,6 +975,7 @@ struct ed_type_runtime_data {
> >> >> >      struct sset active_tunnels;
> >> >> >
> >> >> >      struct sset egress_ifaces;
> >> >> > +    struct smap local_iface_ids;
> >> >> >  };
> >> >> >
> >> >> >  static void *
> >> >> > @@ -987,6 +989,7 @@ en_runtime_data_init(struct engine_node *node
> >> >> OVS_UNUSED,
> >> >> >      sset_init(&data->local_lport_ids);
> >> >> >      sset_init(&data->active_tunnels);
> >> >> >      sset_init(&data->egress_ifaces);
> >> >> > +    smap_init(&data->local_iface_ids);
> >> >> >      local_bindings_init(&data->local_bindings);
> >> >> >      return data;
> >> >> >  }
> >> >> > @@ -1000,6 +1003,7 @@ en_runtime_data_cleanup(void *data)
> >> >> >      sset_destroy(&rt_data->local_lport_ids);
> >> >> >      sset_destroy(&rt_data->active_tunnels);
> >> >> >      sset_destroy(&rt_data->egress_ifaces);
> >> >> > +    smap_destroy(&rt_data->local_iface_ids);
> >> >> >      struct local_datapath *cur_node, *next_node;
> >> >> >      HMAP_FOR_EACH_SAFE (cur_node, next_node, hmap_node,
> >> >> >                          &rt_data->local_datapaths) {
> >> >> > @@ -1041,6 +1045,10 @@ init_binding_ctx(struct engine_node *node,
> >> >> >          (struct ovsrec_port_table *)EN_OVSDB_GET(
> >> >> >              engine_get_input("OVS_port", node));
> >> >> >
> >> >> > +    struct ovsrec_interface_table *iface_table =
> >> >> > +        (struct ovsrec_interface_table *)EN_OVSDB_GET(
> >> >> > +            engine_get_input("OVS_interface", node));
> >> >> > +
> >> >> >      struct ovsrec_qos_table *qos_table =
> >> >> >          (struct ovsrec_qos_table *)EN_OVSDB_GET(
> >> >> >              engine_get_input("OVS_qos", node));
> >> >> > @@ -1070,6 +1078,7 @@ init_binding_ctx(struct engine_node *node,
> >> >> >      b_ctx_in->sbrec_port_binding_by_datapath =
> >> >> sbrec_port_binding_by_datapath;
> >> >> >      b_ctx_in->sbrec_port_binding_by_name =
> sbrec_port_binding_by_name;
> >> >> >      b_ctx_in->port_table = port_table;
> >> >> > +    b_ctx_in->iface_table = iface_table;
> >> >> >      b_ctx_in->qos_table = qos_table;
> >> >> >      b_ctx_in->port_binding_table = pb_table;
> >> >> >      b_ctx_in->br_int = br_int;
> >> >> > @@ -1083,6 +1092,7 @@ init_binding_ctx(struct engine_node *node,
> >> >> >      b_ctx_out->local_lport_ids = &rt_data->local_lport_ids;
> >> >> >      b_ctx_out->egress_ifaces = &rt_data->egress_ifaces;
> >> >> >      b_ctx_out->local_bindings = &rt_data->local_bindings;
> >> >> > +    b_ctx_out->local_iface_ids = &rt_data->local_iface_ids;
> >> >> >  }
> >> >> >
> >> >> >  static void
> >> >> > @@ -1111,10 +1121,12 @@ en_runtime_data_run(struct engine_node
> *node,
> >> >> void *data)
> >> >> >          sset_destroy(local_lport_ids);
> >> >> >          sset_destroy(active_tunnels);
> >> >> >          sset_destroy(&rt_data->egress_ifaces);
> >> >> > +        smap_destroy(&rt_data->local_iface_ids);
> >> >> >          sset_init(local_lports);
> >> >> >          sset_init(local_lport_ids);
> >> >> >          sset_init(active_tunnels);
> >> >> >          sset_init(&rt_data->egress_ifaces);
> >> >> > +        smap_init(&rt_data->local_iface_ids);
> >> >> >          local_bindings_init(&rt_data->local_bindings);
> >> >> >      }
> >> >> >
> >> >> > @@ -1140,6 +1152,34 @@ en_runtime_data_run(struct engine_node
> *node,
> >> void
> >> >> *data)
> >> >> >      engine_set_node_state(node, EN_UPDATED);
> >> >> >  }
> >> >> >
> >> >> > +static bool
> >> >> > +runtime_data_ovs_interface_handler(struct engine_node *node, void
> >> *data)
> >> >> > +{
> >> >> > +    struct ed_type_runtime_data *rt_data = data;
> >> >> > +    struct binding_ctx_in b_ctx_in;
> >> >> > +    struct binding_ctx_out b_ctx_out;
> >> >> > +    init_binding_ctx(node, rt_data, &b_ctx_in, &b_ctx_out);
> >> >> > +
> >> >> > +    bool changed = false;
> >> >> > +    if (!binding_handle_ovs_interface_changes(&b_ctx_in,
> &b_ctx_out,
> >> >> > +                                              &changed)) {
> >> >> > +        return false;
> >> >> > +    }
> >> >> > +
> >> >> > +    if (changed) {
> >> >> > +        engine_set_node_state(node, EN_UPDATED);
> >> >> > +    }
> >> >> > +
> >> >> > +    return true;
> >> >> > +}
> >> >> > +
> >> >> > +static bool
> >> >> > +runtime_data_noop_handler(struct engine_node *node OVS_UNUSED,
> >> >> > +                          void *data OVS_UNUSED)
> >> >> > +{
> >> >> > +    return true;
> >> >> > +}
> >> >> > +
> >> >> >  static bool
> >> >> >  runtime_data_sb_port_binding_handler(struct engine_node *node,
> void
> >> >> *data)
> >> >> >  {
> >> >> > @@ -1147,11 +1187,21 @@ runtime_data_sb_port_binding_handler(struct
> >> >> engine_node *node, void *data)
> >> >> >      struct binding_ctx_in b_ctx_in;
> >> >> >      struct binding_ctx_out b_ctx_out;
> >> >> >      init_binding_ctx(node, rt_data, &b_ctx_in, &b_ctx_out);
> >> >> > +    if (!b_ctx_in.chassis_rec) {
> >> >> > +        return false;
> >> >> > +    }
> >> >> > +
> >> >> > +    bool changed = false;
> >> >> > +    if (!binding_handle_port_binding_changes(&b_ctx_in,
> &b_ctx_out,
> >> >> > +                                             &changed)) {
> >> >> > +        return false;
> >> >> > +    }
> >> >> >
> >> >> > -    bool changed =
> binding_evaluate_port_binding_changes(&b_ctx_in,
> >> >> > -
> &b_ctx_out);
> >> >> > +    if (changed) {
> >> >> > +        engine_set_node_state(node, EN_UPDATED);
> >> >> > +    }
> >> >> >
> >> >> > -    return !changed;
> >> >> > +    return true;
> >> >> >  }
> >> >> >
> >> >> >  /* Connection tracking zones. */
> >> >> > @@ -1894,7 +1944,10 @@ main(int argc, char *argv[])
> >> >> >
> >> >> >      engine_add_input(&en_runtime_data, &en_ovs_open_vswitch,
> NULL);
> >> >> >      engine_add_input(&en_runtime_data, &en_ovs_bridge, NULL);
> >> >> > -    engine_add_input(&en_runtime_data, &en_ovs_port, NULL);
> >> >> > +    engine_add_input(&en_runtime_data, &en_ovs_port,
> >> >> > +                     runtime_data_noop_handler);
> >> >> > +    engine_add_input(&en_runtime_data, &en_ovs_interface,
> >> >> > +                     runtime_data_ovs_interface_handler);
> >> >> >      engine_add_input(&en_runtime_data, &en_ovs_qos, NULL);
> >> >> >
> >> >> >      engine_add_input(&en_runtime_data, &en_sb_chassis, NULL);
> >> >> > diff --git a/tests/ovn-performance.at b/tests/ovn-performance.at
> >> >> > index a8a15f8fe..5dfc6f7ca 100644
> >> >> > --- a/tests/ovn-performance.at
> >> >> > +++ b/tests/ovn-performance.at
> >> >> > @@ -239,6 +239,19 @@ for i in 1 2; do
> >> >> >      ovn_attach n1 br-phys 192.168.0.$i
> >> >> >  done
> >> >> >
> >> >> > +# Wait for the tunnel ports to be created and up.
> >> >> > +# Otherwise this may affect the lflow_run count.
> >> >> > +
> >> >> > +OVS_WAIT_UNTIL([
> >> >> > +    test $(as hv1 ovs-vsctl list interface ovn-hv2-0 | \
> >> >> > +grep tunnel_egress_iface_carrier=up | wc -l) -eq 1
> >> >> > +])
> >> >> > +
> >> >> > +OVS_WAIT_UNTIL([
> >> >> > +    test $(as hv2 ovs-vsctl list interface ovn-hv1-0 | \
> >> >> > +grep tunnel_egress_iface_carrier=up | wc -l) -eq 1
> >> >> > +])
> >> >> > +
> >> >> >  # Add router lr1
> >> >> >  OVN_CONTROLLER_EXPECT_HIT(
> >> >> >      [hv1 hv2], [lflow_run],
> >> >> > --
> >> >> > 2.26.2
> >> >> >
> >> >> > _______________________________________________
> >> >> > dev mailing list
> >> >> > dev@openvswitch.org
> >> >> > https://mail.openvswitch.org/mailman/listinfo/ovs-dev
> >> >> _______________________________________________
> >> >> dev mailing list
> >> >> dev@openvswitch.org
> >> >> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
> >> >>
> >> _______________________________________________
> >> dev mailing list
> >> dev@openvswitch.org
> >> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
> >>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
>
Han Zhou May 23, 2020, 1:11 a.m. UTC | #7
On Fri, May 22, 2020 at 3:07 PM Numan Siddique <numans@ovn.org> wrote:
>
>
>
> On Sat, May 23, 2020 at 3:10 AM Han Zhou <hzhou@ovn.org> wrote:
>>
>> On Fri, May 22, 2020 at 1:51 AM Numan Siddique <numans@ovn.org> wrote:
>> >
>> >
>> >
>> > On Fri, May 22, 2020 at 1:17 AM Han Zhou <hzhou@ovn.org> wrote:
>> >>
>> >> On Thu, May 21, 2020 at 3:49 AM Numan Siddique <numans@ovn.org> wrote:
>> >> >
>> >> >
>> >> >
>> >> > On Thu, May 21, 2020 at 6:57 AM Han Zhou <hzhou@ovn.org> wrote:
>> >> >>
>> >> >> Hi Numan, please see my comments below.
>> >> >
>> >> >
>> >> > Thanks for the review. Please see below for a few comments.
>> >> >
>> >> >
>> >> >>
>> >> >>
>> >> >> On Wed, May 20, 2020 at 12:41 PM <numans@ovn.org> wrote:
>> >> >> >
>> >> >> > From: Numan Siddique <numans@ovn.org>
>> >> >> >
>> >> >> > This patch handles SB port binding changes and OVS interface
changes
>> >> >> > incrementally in the runtime_data stage which otherwise would
have
>> >> >> > resulted in calls to binding_run().
>> >> >> >
>> >> >> > Prior to this patch, port binding changes were handled
differently.
>> >> >> > The changes were only evaluated to see if they need full
recompute
>> >> >> > or they can be ignored.
>> >> >> >
>> >> >> > With this patch, the runtime data is updated with the changes
(both
>> >> >> > SB port binding and OVS interface) and ports are
claimed/released in
>> >> >> > the handlers itself, avoiding the need to trigger recompute of
>> runtime
>> >> >> > data stage.
>> >> >> >
>> >> >> > Acked-by: Mark Michelson <mmichels@redhat.com>
>> >> >> > Signed-off-by: Numan Siddique <numans@ovn.org>
>> >> >> > ---
>> >> >> >  controller/binding.c        | 906
>> +++++++++++++++++++++++++++++++-----
>> >> >> >  controller/binding.h        |   9 +-
>> >> >> >  controller/ovn-controller.c |  61 ++-
>> >> >> >  tests/ovn-performance.at    |  13 +
>> >> >> >  4 files changed, 855 insertions(+), 134 deletions(-)
>> >> >> >
>> >> >> > diff --git a/controller/binding.c b/controller/binding.c
>> >> >> > index 2997fcae8..d5e8e4773 100644
>> >> >> > --- a/controller/binding.c
>> >> >> > +++ b/controller/binding.c
>> >> >> > @@ -360,17 +360,6 @@ destroy_qos_map(struct hmap *qos_map)
>> >> >> >      hmap_destroy(qos_map);
>> >> >> >  }
>> >> >> >
>> >> >> > -static void
>> >> >> > -update_local_lport_ids(struct sset *local_lport_ids,
>> >> >> > -                       const struct sbrec_port_binding
>> *binding_rec)
>> >> >> > -{
>> >> >> > -        char buf[16];
>> >> >> > -        snprintf(buf, sizeof(buf), "%"PRId64"_%"PRId64,
>> >> >> > -                 binding_rec->datapath->tunnel_key,
>> >> >> > -                 binding_rec->tunnel_key);
>> >> >> > -        sset_add(local_lport_ids, buf);
>> >> >> > -}
>> >> >> > -
>> >> >> >  /*
>> >> >> >   * Get the encap from the chassis for this port. The interface
>> >> >> >   * may have an external_ids:encap-ip=<encap-ip> set; if so we
>> >> >> > @@ -449,10 +438,10 @@ is_network_plugged(const struct
>> >> sbrec_port_binding
>> >> >> *binding_rec,
>> >> >> >  }
>> >> >> >
>> >> >> >  static void
>> >> >> > -consider_localnet_port(const struct sbrec_port_binding
>> *binding_rec,
>> >> >> > -                       struct shash *bridge_mappings,
>> >> >> > -                       struct sset *egress_ifaces,
>> >> >> > -                       struct hmap *local_datapaths)
>> >> >> > +update_ld_localnet_port(const struct sbrec_port_binding
>> *binding_rec,
>> >> >> > +                        struct shash *bridge_mappings,
>> >> >> > +                        struct sset *egress_ifaces,
>> >> >> > +                        struct hmap *local_datapaths)
>> >> >> >  {
>> >> >> >      /* Ignore localnet ports for unplugged networks. */
>> >> >> >      if (!is_network_plugged(binding_rec, bridge_mappings)) {
>> >> >> > @@ -482,6 +471,28 @@ consider_localnet_port(const struct
>> >> >> sbrec_port_binding *binding_rec,
>> >> >> >      ld->localnet_port = binding_rec;
>> >> >> >  }
>> >> >> >
>> >> >> > +static void
>> >> >> > +update_local_lport_ids(struct sset *local_lport_ids,
>> >> >> > +                       const struct sbrec_port_binding
>> *binding_rec)
>> >> >> > +{
>> >> >> > +        char buf[16];
>> >> >> > +        snprintf(buf, sizeof(buf), "%"PRId64"_%"PRId64,
>> >> >> > +                 binding_rec->datapath->tunnel_key,
>> >> >> > +                 binding_rec->tunnel_key);
>> >> >> > +        sset_add(local_lport_ids, buf);
>> >> >> > +}
>> >> >> > +
>> >> >> > +static void
>> >> >> > +remove_local_lport_ids(const struct sbrec_port_binding
>> *binding_rec,
>> >> >> > +                       struct sset *local_lport_ids)
>> >> >> > +{
>> >> >> > +        char buf[16];
>> >> >> > +        snprintf(buf, sizeof(buf), "%"PRId64"_%"PRId64,
>> >> >> > +                 binding_rec->datapath->tunnel_key,
>> >> >> > +                 binding_rec->tunnel_key);
>> >> >> > +        sset_find_and_delete(local_lport_ids, buf);
>> >> >> > +}
>> >> >> > +
>> >> >> >  /* Local bindings. binding.c module binds the logical port
>> >> (represented
>> >> >> by
>> >> >> >   * Port_Binding rows) and sets the 'chassis' column when it is
sees
>> >> the
>> >> >> >   * OVS interface row (of type "" or "internal") with the
>> >> >> > @@ -599,6 +610,14 @@ local_bindings_destroy(struct shash
>> >> *local_bindings)
>> >> >> >      shash_destroy(local_bindings);
>> >> >> >  }
>> >> >> >
>> >> >> > +static
>> >> >> > +void local_binding_delete(struct shash *local_bindings,
>> >> >> > +                          struct local_binding *lbinding)
>> >> >> > +{
>> >> >> > +    shash_find_and_delete(local_bindings, lbinding->name);
>> >> >> > +    local_binding_destroy(lbinding);
>> >> >> > +}
>> >> >> > +
>> >> >> >  static void
>> >> >> >  local_binding_add_child(struct local_binding *lbinding,
>> >> >> >                          struct local_binding *child)
>> >> >> > @@ -625,6 +644,7 @@ is_lport_container(const struct
>> sbrec_port_binding
>> >> >> *pb)
>> >> >> >      return !pb->type[0] && pb->parent_port &&
pb->parent_port[0];
>> >> >> >  }
>> >> >> >
>> >> >> > +/* Corresponds to each Port_Binding.type. */
>> >> >> >  enum en_lport_type {
>> >> >> >      LP_UNKNOWN,
>> >> >> >      LP_VIF,
>> >> >> > @@ -670,12 +690,20 @@ get_lport_type(const struct
sbrec_port_binding
>> >> *pb)
>> >> >> >      return LP_UNKNOWN;
>> >> >> >  }
>> >> >> >
>> >> >> > -static void
>> >> >> > +/* Returns false if lport is not claimed due to 'sb_readonly'.
>> >> >> > + * Returns true otherwise.
>> >> >> > + */
>> >> >> > +static bool
>> >> >> >  claim_lport(const struct sbrec_port_binding *pb,
>> >> >> >              const struct sbrec_chassis *chassis_rec,
>> >> >> > -            const struct ovsrec_interface *iface_rec)
>> >> >> > +            const struct ovsrec_interface *iface_rec,
>> >> >> > +            bool sb_readonly)
>> >> >> >  {
>> >> >> >      if (pb->chassis != chassis_rec) {
>> >> >> > +        if (sb_readonly) {
>> >> >> > +            return false;
>> >> >> > +        }
>> >> >> > +
>> >> >> >          if (pb->chassis) {
>> >> >> >              VLOG_INFO("Changing chassis for lport %s from %s to
>> %s.",
>> >> >> >                      pb->logical_port, pb->chassis->name,
>> >> >> > @@ -694,26 +722,48 @@ claim_lport(const struct sbrec_port_binding
>> *pb,
>> >> >> >      struct sbrec_encap *encap_rec =
>> >> >> >          sbrec_get_port_encap(chassis_rec, iface_rec);
>> >> >> >      if (encap_rec && pb->encap != encap_rec) {
>> >> >> > +        if (sb_readonly) {
>> >> >> > +            return false;
>> >> >> > +        }
>> >> >> >          sbrec_port_binding_set_encap(pb, encap_rec);
>> >> >> >      }
>> >> >> > +
>> >> >> > +    return true;
>> >> >> >  }
>> >> >> >
>> >> >> > -static void
>> >> >> > -release_lport(const struct sbrec_port_binding *pb)
>> >> >> > +/* Returns false if lport is not released due to 'sb_readonly'.
>> >> >> > + * Returns true otherwise.
>> >> >> > + */
>> >> >> > +static bool
>> >> >> > +release_lport(const struct sbrec_port_binding *pb, bool
>> sb_readonly)
>> >> >> >  {
>> >> >> >      if (!pb) {
>> >> >> > -        return;
>> >> >> > +        return true;
>> >> >> >      }
>> >> >> >
>> >> >> > -    VLOG_INFO("Releasing lport %s from this chassis.",
>> >> pb->logical_port);
>> >> >> >      if (pb->encap) {
>> >> >> > +        if (sb_readonly) {
>> >> >> > +            return false;
>> >> >> > +        }
>> >> >> >          sbrec_port_binding_set_encap(pb, NULL);
>> >> >> >      }
>> >> >> > -    sbrec_port_binding_set_chassis(pb, NULL);
>> >> >> > +
>> >> >> > +    if (pb->chassis) {
>> >> >> > +        if (sb_readonly) {
>> >> >> > +            return false;
>> >> >> > +        }
>> >> >> > +        sbrec_port_binding_set_chassis(pb, NULL);
>> >> >> > +    }
>> >> >> >
>> >> >> >      if (pb->virtual_parent) {
>> >> >> > +        if (sb_readonly) {
>> >> >> > +            return false;
>> >> >> > +        }
>> >> >> >          sbrec_port_binding_set_virtual_parent(pb, NULL);
>> >> >> >      }
>> >> >> > +
>> >> >> > +    VLOG_INFO("Releasing lport %s from this chassis.",
>> >> pb->logical_port);
>> >> >> > +    return true;
>> >> >> >  }
>> >> >> >
>> >> >> >  static bool
>> >> >> > @@ -738,7 +788,41 @@ can_bind_on_this_chassis(const struct
>> >> sbrec_chassis
>> >> >> *chassis_rec,
>> >> >> >             || !strcmp(requested_chassis, chassis_rec->hostname);
>> >> >> >  }
>> >> >> >
>> >> >> > -static void
>> >> >> > +static bool
>> >> >> > +release_local_binding_children(const struct sbrec_chassis
>> >> *chassis_rec,
>> >> >> > +                               struct local_binding *lbinding,
>> >> >> > +                               bool sb_readonly)
>> >> >> > +{
>> >> >> > +    struct shash_node *node;
>> >> >> > +    SHASH_FOR_EACH (node, &lbinding->children) {
>> >> >> > +        struct local_binding *l = node->data;
>> >> >> > +        if (is_lbinding_this_chassis(l, chassis_rec)) {
>> >> >> > +            if (!release_lport(l->pb, sb_readonly)) {
>> >> >> > +                return false;
>> >> >> > +            }
>> >> >> > +        }
>> >> >> > +    }
>> >> >> > +
>> >> >> > +    return true;
>> >> >> > +}
>> >> >> > +
>> >> >> > +static bool
>> >> >> > +release_local_binding(const struct sbrec_chassis *chassis_rec,
>> >> >> > +                      struct local_binding *lbinding, bool
>> >> sb_readonly)
>> >> >> > +{
>> >> >> > +    if (!release_local_binding_children(chassis_rec, lbinding,
>> >> >> > +                                        sb_readonly)) {
>> >> >> > +        return false;
>> >> >> > +    }
>> >> >> > +
>> >> >> > +    if (is_lbinding_this_chassis(lbinding, chassis_rec)) {
>> >> >> > +        return release_lport(lbinding->pb, sb_readonly);
>> >> >> > +    }
>> >> >> > +
>> >> >> > +    return true;
>> >> >> > +}
>> >> >> > +
>> >> >> > +static bool
>> >> >> >  consider_vif_lport_(const struct sbrec_port_binding *pb,
>> >> >> >                      bool can_bind, const char *vif_chassis,
>> >> >> >                      struct binding_ctx_in *b_ctx_in,
>> >> >> > @@ -750,7 +834,10 @@ consider_vif_lport_(const struct
>> >> sbrec_port_binding
>> >> >> *pb,
>> >> >> >      if (lbinding_set) {
>> >> >> >          if (can_bind) {
>> >> >> >              /* We can claim the lport. */
>> >> >> > -            claim_lport(pb, b_ctx_in->chassis_rec,
>> lbinding->iface);
>> >> >> > +            if (!claim_lport(pb, b_ctx_in->chassis_rec,
>> >> lbinding->iface,
>> >> >> > +                             !b_ctx_in->ovnsb_idl_txn)){
>> >> >> > +                return false;
>> >> >> > +            }
>> >> >> >
>> >> >> >
>> >>  add_local_datapath(b_ctx_in->sbrec_datapath_binding_by_key,
>> >> >> >
>>  b_ctx_in->sbrec_port_binding_by_datapath,
>> >> >> > @@ -775,13 +862,14 @@ consider_vif_lport_(const struct
>> >> sbrec_port_binding
>> >> >> *pb,
>> >> >> >      if (pb->chassis == b_ctx_in->chassis_rec) {
>> >> >> >          /* Release the lport if there is no lbinding. */
>> >> >> >          if (!lbinding_set || !can_bind) {
>> >> >> > -            release_lport(pb);
>> >> >> > +            return release_lport(pb, !b_ctx_in->ovnsb_idl_txn);
>> >> >> >          }
>> >> >> >      }
>> >> >> >
>> >> >> > +    return true;
>> >> >> >  }
>> >> >> >
>> >> >> > -static void
>> >> >> > +static bool
>> >> >> >  consider_vif_lport(const struct sbrec_port_binding *pb,
>> >> >> >                     struct binding_ctx_in *b_ctx_in,
>> >> >> >                     struct binding_ctx_out *b_ctx_out,
>> >> >> > @@ -801,11 +889,11 @@ consider_vif_lport(const struct
>> >> sbrec_port_binding
>> >> >> *pb,
>> >> >> >          lbinding->pb = pb;
>> >> >> >      }
>> >> >> >
>> >> >> > -    consider_vif_lport_(pb, can_bind, vif_chassis, b_ctx_in,
>> >> >> > -                        b_ctx_out, lbinding, qos_map);
>> >> >> > +    return consider_vif_lport_(pb, can_bind, vif_chassis,
b_ctx_in,
>> >> >> > +                               b_ctx_out, lbinding, qos_map);
>> >> >> >  }
>> >> >> >
>> >> >> > -static void
>> >> >> > +static bool
>> >> >> >  consider_container_lport(const struct sbrec_port_binding *pb,
>> >> >> >                           struct binding_ctx_in *b_ctx_in,
>> >> >> >                           struct binding_ctx_out *b_ctx_out,
>> >> >> > @@ -815,7 +903,39 @@ consider_container_lport(const struct
>> >> >> sbrec_port_binding *pb,
>> >> >> >      parent_lbinding =
local_binding_find(b_ctx_out->local_bindings,
>> >> >> >                                           pb->parent_port);
>> >> >> >
>> >> >> > -    if (parent_lbinding && !parent_lbinding->pb) {
>> >> >> > +    if (!parent_lbinding) {
>> >> >> > +        /* There is no local_binding for parent port. Create it
>> >> >> > +         * without OVS interface row. This is the only exception
>> >> >> > +         * for creating the 'struct local_binding' object
without
>> >> >> > +         * corresponding OVS interface row.
>> >> >> > +         *
>> >> >> > +         * This is required for the following reasons:
>> >> >> > +         *   - If a logical port P1 is created and then
>> >> >> > +         *     few container ports - C1, C2, .. are created
first
>> by
>> >> CMS.
>> >> >> > +         *   - And later when OVS interface row  is created for
P1,
>> >> then
>> >> >> > +         *     we want the these container ports also be
claimed by
>> >> the
>> >> >> > +         *     chassis.
>> >> >> > +         * */
>> >> >>
>> >> >> It seems the assumption is, for all container PBs, their parent PBs
>> >> should
>> >> >> always have local_binding created, no matter if they are bound to
>> current
>> >> >> chassis.
>> >> >
>> >> >
>> >> > That's right.
>> >> >
>> >> >>
>> >> >> However, at least some of the code in this patch would violate this
>> >> >> assumption, e.g. handle_iface_delete() will call
>> local_binding_delete()
>> >> and
>> >> >> delete the parent binding and all the children container bindings.
>> >> >
>> >> >
>> >> > I missed this part. I'll handle it in next version.
>> >> >
>> >> >>
>> >> >> It is also better to document this clearly in the comment of struct
>> >> >> local_binding for what should be expected in the local_bindings.
>> >> >
>> >> >
>> >> > I've added comments for the local_bindings struct. But it looks like
>> it's
>> >> not
>> >> > sufficient. I'll add more .
>> >> >
>> >> >> My
>> >> >> understanding is, for each binding_run() or change_handler
execution,
>> the
>> >> >> expected items left in it is:
>> >> >> 1) For each interface that has iface-id configured.
>> >> >
>> >> > Yes.
>> >> >
>> >> >>
>> >> >> 2) For each container PBs and their parent PBs, no matter if they
are
>> >> bound
>> >> >> to this chassis.
>> >> >
>> >> >
>> >> > Yes.
>> >> >
>> >> >>
>> >> >> 3) For some of the non VIF PBs ...
>> >> >
>> >> >
>> >> > Only for the "virtual" PB if it's parent is bound.
>> >> >
>> >> >>
>> >> >> Is this correct?
>> >> >>
>> >> >> > +        parent_lbinding = local_binding_create(pb->parent_port,
>> NULL,
>> >> >> NULL,
>> >> >> > +                                               BT_VIF);
>> >> >> > +        local_binding_add(b_ctx_out->local_bindings,
>> parent_lbinding);
>> >> >> > +    }
>> >> >> > +
>> >> >> > +    struct local_binding *container_lbinding =
>> >> >> > +        local_binding_find_child(parent_lbinding,
>> pb->logical_port);
>> >> >> > +
>> >> >> > +    if (!container_lbinding) {
>> >> >> > +        container_lbinding =
local_binding_create(pb->logical_port,
>> >> >> > +
>> >>  parent_lbinding->iface,
>> >> >> > +                                                  pb,
>> BT_CONTAINER);
>> >> >> > +        local_binding_add_child(parent_lbinding,
>> container_lbinding);
>> >> >> > +    } else {
>> >> >> > +        ovs_assert(container_lbinding->type == BT_CONTAINER);
>> >> >> > +        container_lbinding->pb = pb;
>> >> >> > +        container_lbinding->iface = parent_lbinding->iface;
>> >> >> > +    }
>> >> >> > +
>> >> >> > +    if (!parent_lbinding->pb) {
>> >> >> >          parent_lbinding->pb = lport_lookup_by_name(
>> >> >> >              b_ctx_in->sbrec_port_binding_by_name,
pb->parent_port);
>> >> >> >
>> >> >> > @@ -824,37 +944,28 @@ consider_container_lport(const struct
>> >> >> sbrec_port_binding *pb,
>> >> >> >               * So call consider_vif_lport() to process it
first. */
>> >> >> >              consider_vif_lport(parent_lbinding->pb, b_ctx_in,
>> >> b_ctx_out,
>> >> >> >                                 parent_lbinding, qos_map);
>> >> >> > -        }
>> >> >> > -    }
>> >> >> > +        } else {
>> >> >> > +            /* The parent lport doesn't exist. Call
>> release_lport() to
>> >> >> > +             * release the container lport, if it was bound
>> earlier.
>> >> */
>> >> >> > +            if (is_lbinding_this_chassis(container_lbinding,
>> >> >> > +
b_ctx_in->chassis_rec)) {
>> >> >> > +               return release_lport(pb,
!b_ctx_in->ovnsb_idl_txn);
>> >> >> > +            }
>> >> >> >
>> >> >> > -    if (!parent_lbinding || !parent_lbinding->pb) {
>> >> >> > -        /* Call release_lport, to release the container lport,
if
>> >> >> > -         * it was bound earlier. */
>> >> >> > -        if (pb->chassis == b_ctx_in->chassis_rec) {
>> >> >> > -            release_lport(pb);
>> >> >> > +            return true;
>> >> >> >          }
>> >> >> > -        return;
>> >> >> >      }
>> >> >> >
>> >> >> > -    struct local_binding *container_lbinding =
>> >> >> > -        local_binding_find_child(parent_lbinding,
>> pb->logical_port);
>> >> >> > -    ovs_assert(!container_lbinding);
>> >> >> > -
>> >> >> > -    container_lbinding = local_binding_create(pb->logical_port,
>> >> >> > -
>>  parent_lbinding->iface,
>> >> >> > -                                              pb, BT_CONTAINER);
>> >> >> > -    local_binding_add_child(parent_lbinding,
container_lbinding);
>> >> >> > -
>> >> >> >      const char *vif_chassis =
>> smap_get(&parent_lbinding->pb->options,
>> >> >> >                                         "requested-chassis");
>> >> >> >      bool can_bind =
can_bind_on_this_chassis(b_ctx_in->chassis_rec,
>> >> >> >                                               vif_chassis);
>> >> >> >
>> >> >> > -    consider_vif_lport_(pb, can_bind, vif_chassis, b_ctx_in,
>> >> b_ctx_out,
>> >> >> > -                        container_lbinding, qos_map);
>> >> >> > +    return consider_vif_lport_(pb, can_bind, vif_chassis,
b_ctx_in,
>> >> >> b_ctx_out,
>> >> >> > +                               container_lbinding, qos_map);
>> >> >> >  }
>> >> >> >
>> >> >> > -static void
>> >> >> > +static bool
>> >> >> >  consider_virtual_lport(const struct sbrec_port_binding *pb,
>> >> >> >                         struct binding_ctx_in *b_ctx_in,
>> >> >> >                         struct binding_ctx_out *b_ctx_out,
>> >> >> > @@ -881,11 +992,16 @@ consider_virtual_lport(const struct
>> >> >> sbrec_port_binding *pb,
>> >> >> >      if (is_lbinding_this_chassis(parent_lbinding,
>> >> >> b_ctx_in->chassis_rec)) {
>> >> >> >          virtual_lbinding =
>> >> >> >              local_binding_find_child(parent_lbinding,
>> >> pb->logical_port);
>> >> >> > -        ovs_assert(!virtual_lbinding);
>> >> >> > -        virtual_lbinding =
local_binding_create(pb->logical_port,
>> >> >> > -
>> >>  parent_lbinding->iface,
>> >> >> > -                                                pb, BT_VIRTUAL);
>> >> >> > -        local_binding_add_child(parent_lbinding,
virtual_lbinding);
>> >> >> > +        if (!virtual_lbinding) {
>> >> >> > +            virtual_lbinding =
>> local_binding_create(pb->logical_port,
>> >> >> > +
>> >> >>  parent_lbinding->iface,
>> >> >> > +                                                    pb,
>> BT_VIRTUAL);
>> >> >> > +            local_binding_add_child(parent_lbinding,
>> >> virtual_lbinding);
>> >> >> > +        } else {
>> >> >> > +            ovs_assert(virtual_lbinding->type == BT_VIRTUAL);
>> >> >> > +            virtual_lbinding->pb = pb;
>> >> >> > +            virtual_lbinding->iface = parent_lbinding->iface;
>> >> >> > +        }
>> >> >> >      }
>> >> >> >
>> >> >> >      return consider_vif_lport_(pb, true, NULL, b_ctx_in,
b_ctx_out,
>> >> >> > @@ -895,14 +1011,13 @@ consider_virtual_lport(const struct
>> >> >> sbrec_port_binding *pb,
>> >> >> >  /* Considers either claiming the lport or releasing the lport
>> >> >> >   * for non VIF lports.
>> >> >> >   */
>> >> >> > -static void
>> >> >> > +static bool
>> >> >> >  consider_nonvif_lport_(const struct sbrec_port_binding *pb,
>> >> >> >                         bool our_chassis,
>> >> >> >                         bool has_local_l3gateway,
>> >> >> >                         struct binding_ctx_in *b_ctx_in,
>> >> >> >                         struct binding_ctx_out *b_ctx_out)
>> >> >> >  {
>> >> >> > -    ovs_assert(b_ctx_in->ovnsb_idl_txn);
>> >> >> >      if (our_chassis) {
>> >> >> >          sset_add(b_ctx_out->local_lports, pb->logical_port);
>> >> >> >
 add_local_datapath(b_ctx_in->sbrec_datapath_binding_by_key,
>> >> >> > @@ -912,13 +1027,16 @@ consider_nonvif_lport_(const struct
>> >> >> sbrec_port_binding *pb,
>> >> >> >                             b_ctx_out->local_datapaths);
>> >> >> >
>> >> >> >          update_local_lport_ids(b_ctx_out->local_lport_ids, pb);
>> >> >> > -        claim_lport(pb, b_ctx_in->chassis_rec, NULL);
>> >> >> > +        return claim_lport(pb, b_ctx_in->chassis_rec, NULL,
>> >> >> > +                           !b_ctx_in->ovnsb_idl_txn);
>> >> >> >      } else if (pb->chassis == b_ctx_in->chassis_rec) {
>> >> >> > -        release_lport(pb);
>> >> >> > +        return release_lport(pb, !b_ctx_in->ovnsb_idl_txn);
>> >> >> >      }
>> >> >> > +
>> >> >> > +    return true;
>> >> >> >  }
>> >> >> >
>> >> >> > -static void
>> >> >> > +static bool
>> >> >> >  consider_l2gw_lport(const struct sbrec_port_binding *pb,
>> >> >> >                      struct binding_ctx_in *b_ctx_in,
>> >> >> >                      struct binding_ctx_out *b_ctx_out)
>> >> >> > @@ -927,10 +1045,10 @@ consider_l2gw_lport(const struct
>> >> >> sbrec_port_binding *pb,
>> >> >> >      bool our_chassis = chassis_id && !strcmp(chassis_id,
>> >> >> >
>> >> >> b_ctx_in->chassis_rec->name);
>> >> >> >
>> >> >> > -    consider_nonvif_lport_(pb, our_chassis, false, b_ctx_in,
>> >> b_ctx_out);
>> >> >> > +    return consider_nonvif_lport_(pb, our_chassis, false,
b_ctx_in,
>> >> >> b_ctx_out);
>> >> >> >  }
>> >> >> >
>> >> >> > -static void
>> >> >> > +static bool
>> >> >> >  consider_l3gw_lport(const struct sbrec_port_binding *pb,
>> >> >> >                      struct binding_ctx_in *b_ctx_in,
>> >> >> >                      struct binding_ctx_out *b_ctx_out)
>> >> >> > @@ -939,7 +1057,7 @@ consider_l3gw_lport(const struct
>> >> sbrec_port_binding
>> >> >> *pb,
>> >> >> >      bool our_chassis = chassis_id && !strcmp(chassis_id,
>> >> >> >
>> >> >> b_ctx_in->chassis_rec->name);
>> >> >> >
>> >> >> > -    consider_nonvif_lport_(pb, our_chassis, true, b_ctx_in,
>> >> b_ctx_out);
>> >> >> > +    return consider_nonvif_lport_(pb, our_chassis, true,
b_ctx_in,
>> >> >> b_ctx_out);
>> >> >> >  }
>> >> >> >
>> >> >> >  static void
>> >> >> > @@ -958,7 +1076,7 @@ consider_localnet_lport(const struct
>> >> >> sbrec_port_binding *pb,
>> >> >> >      update_local_lport_ids(b_ctx_out->local_lport_ids, pb);
>> >> >> >  }
>> >> >> >
>> >> >> > -static void
>> >> >> > +static bool
>> >> >> >  consider_ha_lport(const struct sbrec_port_binding *pb,
>> >> >> >                    struct binding_ctx_in *b_ctx_in,
>> >> >> >                    struct binding_ctx_out *b_ctx_out)
>> >> >> > @@ -986,18 +1104,18 @@ consider_ha_lport(const struct
>> >> sbrec_port_binding
>> >> >> *pb,
>> >> >> >                             b_ctx_out->local_datapaths);
>> >> >> >      }
>> >> >> >
>> >> >> > -    consider_nonvif_lport_(pb, our_chassis, false, b_ctx_in,
>> >> b_ctx_out);
>> >> >> > +    return consider_nonvif_lport_(pb, our_chassis, false,
b_ctx_in,
>> >> >> b_ctx_out);
>> >> >> >  }
>> >> >> >
>> >> >> > -static void
>> >> >> > +static bool
>> >> >> >  consider_cr_lport(const struct sbrec_port_binding *pb,
>> >> >> >                    struct binding_ctx_in *b_ctx_in,
>> >> >> >                    struct binding_ctx_out *b_ctx_out)
>> >> >> >  {
>> >> >> > -    consider_ha_lport(pb, b_ctx_in, b_ctx_out);
>> >> >> > +    return consider_ha_lport(pb, b_ctx_in, b_ctx_out);
>> >> >> >  }
>> >> >> >
>> >> >> > -static void
>> >> >> > +static bool
>> >> >> >  consider_external_lport(const struct sbrec_port_binding *pb,
>> >> >> >                          struct binding_ctx_in *b_ctx_in,
>> >> >> >                          struct binding_ctx_out *b_ctx_out)
>> >> >> > @@ -1050,6 +1168,8 @@ build_local_bindings(struct binding_ctx_in
>> >> >> *b_ctx_in,
>> >> >> >                  }
>> >> >> >
>> >> >> >                  sset_add(b_ctx_out->local_lports, iface_id);
>> >> >> > +                smap_replace(b_ctx_out->local_iface_ids,
>> >> iface_rec->name,
>> >> >> > +                             iface_id);
>> >> >> >              }
>> >> >> >
>> >> >> >              /* Check if this is a tunnel interface. */
>> >> >> > @@ -1165,9 +1285,9 @@ binding_run(struct binding_ctx_in
*b_ctx_in,
>> >> struct
>> >> >> binding_ctx_out *b_ctx_out)
>> >> >> >       * corresponding local datapath accordingly. */
>> >> >> >      struct localnet_lport *lnet_lport;
>> >> >> >      LIST_FOR_EACH_POP (lnet_lport, list_node, &localnet_lports)
{
>> >> >> > -        consider_localnet_port(lnet_lport->pb, &bridge_mappings,
>> >> >> > -                               b_ctx_out->egress_ifaces,
>> >> >> > -                               b_ctx_out->local_datapaths);
>> >> >> > +        update_ld_localnet_port(lnet_lport->pb,
&bridge_mappings,
>> >> >> > +                                b_ctx_out->egress_ifaces,
>> >> >> > +                                b_ctx_out->local_datapaths);
>> >> >> >          free(lnet_lport);
>> >> >> >      }
>> >> >> >
>> >> >> > @@ -1185,95 +1305,625 @@ binding_run(struct binding_ctx_in
>> *b_ctx_in,
>> >> >> struct binding_ctx_out *b_ctx_out)
>> >> >> >      destroy_qos_map(&qos_map);
>> >> >> >  }
>> >> >> >
>> >> >> > -/* Returns true if port-binding changes potentially require flow
>> >> changes
>> >> >> on
>> >> >> > - * the current chassis. Returns false if we are sure there is no
>> >> impact.
>> >> >> */
>> >> >> > +/* Returns true if the database is all cleaned up, false if more
>> work
>> >> is
>> >> >> > + * required. */
>> >> >> >  bool
>> >> >> > -binding_evaluate_port_binding_changes(struct binding_ctx_in
>> *b_ctx_in,
>> >> >> > -                                      struct binding_ctx_out
>> >> *b_ctx_out)
>> >> >> > +binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn,
>> >> >> > +                const struct sbrec_port_binding_table
>> >> >> *port_binding_table,
>> >> >> > +                const struct sbrec_chassis *chassis_rec)
>> >> >> >  {
>> >> >> > -    if (!b_ctx_in->chassis_rec) {
>> >> >> > +    if (!ovnsb_idl_txn) {
>> >> >> > +        return false;
>> >> >> > +    }
>> >> >> > +    if (!chassis_rec) {
>> >> >> >          return true;
>> >> >> >      }
>> >> >> >
>> >> >> > -    bool changed = false;
>> >> >> > -
>> >> >> >      const struct sbrec_port_binding *binding_rec;
>> >> >> > -    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (binding_rec,
>> >> >> > -
>> >> >> b_ctx_in->port_binding_table) {
>> >> >> > -        /* XXX: currently OVSDB change tracking doesn't support
>> >> getting
>> >> >> old
>> >> >> > -         * data when the operation is update, so if a
port-binding
>> >> moved
>> >> >> from
>> >> >> > -         * this chassis to another, there is no easy way to find
>> out
>> >> the
>> >> >> > -         * change. To workaround this problem, we just makes
sure
>> if
>> >> >> > -         * any port *related to* this chassis has any change,
then
>> >> >> trigger
>> >> >> > -         * recompute.
>> >> >> > -         *
>> >> >> > -         * - If a regular VIF is unbound from this chassis, the
>> local
>> >> >> ovsdb
>> >> >> > -         *   interface table will be updated, which will trigger
>> >> >> recompute.
>> >> >> > -         *
>> >> >> > -         * - If the port is not a regular VIF, always trigger
>> >> recompute.
>> >> >> */
>> >> >> > -        if (binding_rec->chassis == b_ctx_in->chassis_rec) {
>> >> >> > -            changed = true;
>> >> >> > +    bool any_changes = false;
>> >> >> > +    SBREC_PORT_BINDING_TABLE_FOR_EACH (binding_rec,
>> >> port_binding_table) {
>> >> >> > +        if (binding_rec->chassis == chassis_rec) {
>> >> >> > +            if (binding_rec->encap) {
>> >> >> > +                sbrec_port_binding_set_encap(binding_rec, NULL);
>> >> >> > +            }
>> >> >> > +            sbrec_port_binding_set_chassis(binding_rec, NULL);
>> >> >> > +            any_changes = true;
>> >> >> > +        }
>> >> >> > +    }
>> >> >> > +
>> >> >> > +    if (any_changes) {
>> >> >> > +        ovsdb_idl_txn_add_comment(
>> >> >> > +            ovnsb_idl_txn,
>> >> >> > +            "ovn-controller: removing all port bindings for
'%s'",
>> >> >> > +            chassis_rec->name);
>> >> >> > +    }
>> >> >> > +
>> >> >> > +    return !any_changes;
>> >> >> > +}
>> >> >> > +
>> >> >> > +static void
>> >> >> > +add_local_datapath_peer_port(const struct sbrec_port_binding
*pb,
>> >> >> > +                             struct binding_ctx_in *b_ctx_in,
>> >> >> > +                             struct binding_ctx_out *b_ctx_out,
>> >> >> > +                             struct local_datapath *ld)
>> >> >> > +{
>> >> >> > +    const char *peer_name = smap_get(&pb->options, "peer");
>> >> >> > +    if (strcmp(pb->type, "patch") || !peer_name) {
>> >> >> > +        return;
>> >> >> > +    }
>> >> >> > +
>> >> >> > +    const struct sbrec_port_binding *peer;
>> >> >> > +    peer =
>> lport_lookup_by_name(b_ctx_in->sbrec_port_binding_by_name,
>> >> >> > +                                peer_name);
>> >> >> > +
>> >> >> > +    if (!peer || !peer->datapath) {
>> >> >> > +        return;
>> >> >> > +    }
>> >> >> > +
>> >> >> > +    bool present = false;
>> >> >> > +    for (size_t i = 0; i < ld->n_peer_ports; i++) {
>> >> >> > +        if (ld->peer_ports[i].local == pb) {
>> >> >> > +            present = true;
>> >> >> >              break;
>> >> >> >          }
>> >> >> > +    }
>> >> >> >
>> >> >> > -        if (!strcmp(binding_rec->type, "remote")) {
>> >> >> > -            continue;
>> >> >> > +    if (!present) {
>> >> >> > +        ld->n_peer_ports++;
>> >> >> > +        if (ld->n_peer_ports > ld->n_allocated_peer_ports) {
>> >> >> > +            ld->peer_ports =
>> >> >> > +                x2nrealloc(ld->peer_ports,
>> >> >> > +                           &ld->n_allocated_peer_ports,
>> >> >> > +                           sizeof *ld->peer_ports);
>> >> >> > +        }
>> >> >> > +        ld->peer_ports[ld->n_peer_ports - 1].local = pb;
>> >> >> > +        ld->peer_ports[ld->n_peer_ports - 1].remote = peer;
>> >> >> > +    }
>> >> >> > +
>> >> >> > +    struct local_datapath *peer_ld =
>> >> >> > +        get_local_datapath(b_ctx_out->local_datapaths,
>> >> >> > +                           peer->datapath->tunnel_key);
>> >> >> > +    if (!peer_ld) {
>> >> >> > +
>>  add_local_datapath__(b_ctx_in->sbrec_datapath_binding_by_key,
>> >> >> > +
>> b_ctx_in->sbrec_port_binding_by_datapath,
>> >> >> > +
b_ctx_in->sbrec_port_binding_by_name,
>> >> >> > +                             peer->datapath, false,
>> >> >> > +                             1, b_ctx_out->local_datapaths);
>> >> >> > +        return;
>> >> >> > +    }
>> >> >> > +
>> >> >> > +    for (size_t i = 0; i < peer_ld->n_peer_ports; i++) {
>> >> >> > +        if (peer_ld->peer_ports[i].local == peer) {
>> >> >> > +            return;
>> >> >> >          }
>> >> >> > +    }
>> >> >> >
>> >> >> > -        if (strcmp(binding_rec->type, "")) {
>> >> >> > -            changed = true;
>> >> >> > +    peer_ld->n_peer_ports++;
>> >> >> > +    if (peer_ld->n_peer_ports >
peer_ld->n_allocated_peer_ports) {
>> >> >> > +        peer_ld->peer_ports =
>> >> >> > +            x2nrealloc(peer_ld->peer_ports,
>> >> >> > +                        &peer_ld->n_allocated_peer_ports,
>> >> >> > +                        sizeof *peer_ld->peer_ports);
>> >> >> > +    }
>> >> >> > +    peer_ld->peer_ports[peer_ld->n_peer_ports - 1].local = peer;
>> >> >> > +    peer_ld->peer_ports[peer_ld->n_peer_ports - 1].remote = pb;
>> >> >> > +}
>> >> >> > +
>> >> >> > +static void
>> >> >> > +remove_local_datapath_peer_port(const struct sbrec_port_binding
>> *pb,
>> >> >> > +                                struct local_datapath *ld,
>> >> >> > +                                struct hmap *local_datapaths)
>> >> >> > +{
>> >> >> > +    size_t i = 0;
>> >> >> > +    for (i = 0; i < ld->n_peer_ports; i++) {
>> >> >> > +        if (ld->peer_ports[i].local == pb) {
>> >> >> >              break;
>> >> >> >          }
>> >> >> > +    }
>> >> >> > +
>> >> >> > +    if (i == ld->n_peer_ports) {
>> >> >> > +        return;
>> >> >> > +    }
>> >> >> > +
>> >> >> > +    const struct sbrec_port_binding *peer =
>> ld->peer_ports[i].remote;
>> >> >> >
>> >> >> > -        struct local_binding *lbinding = NULL;
>> >> >> > -        if (!binding_rec->parent_port ||
>> >> !binding_rec->parent_port[0]) {
>> >> >> > -            lbinding =
>> local_binding_find(b_ctx_out->local_bindings,
>> >> >> > -
>>  binding_rec->logical_port);
>> >> >> > +    /* Possible improvement: We can shrint the allocated peer
ports
>> >> >> > +     * if (ld->n_peer_ports < ld->n_allocated_peer_ports / 2).
>> >> >> > +     */
>> >> >> > +    ld->peer_ports[i].local = ld->peer_ports[ld->n_peer_ports -
>> >> 1].local;
>> >> >> > +    ld->peer_ports[i].remote = ld->peer_ports[ld->n_peer_ports -
>> >> >> 1].remote;
>> >> >> > +    ld->n_peer_ports--;
>> >> >> > +
>> >> >> > +    struct local_datapath *peer_ld =
>> >> >> > +        get_local_datapath(local_datapaths,
>> >> peer->datapath->tunnel_key);
>> >> >> > +    if (peer_ld) {
>> >> >> > +        remove_local_datapath_peer_port(peer, peer_ld,
>> >> local_datapaths);
>> >> >>
>> >> >> Sorry that I don't understand this part. peer's peer is the "pb"
that
>> is
>> >> >> passed in the parameter of the current function call, so this
>> recursive
>> >> >> call would only try to remove the "pb", which is already removed
>> (before
>> >> >> calling this function). So I think it is useless.
>> >> >
>> >> >
>> >> > Let's say there is sw0-lr0 of sw0 connected to the peer lr0-sw0 of
lr0.
>> >> >
>> >> > When this function is called for sw0-lr0, it removes the peer ports
>> >> sw0-lr0 from
>> >> > the 'sw0' ld and then it calls recursively for lr0.
>> >> >
>> >> > Yes you're right. It will again call for sw0-lr0 of sw0. But that
would
>> >> be a no-op as
>> >> > the for loop in the beginning cannot find sw0-lr0  in 'sw0' ld..
Even
>> >> though it
>> >> > is useless are there any issues with it ? Since its recursion and it
>> has
>> >> proper return
>> >> > conditions, I think it should be fine.
>> >> >
>> >> > Another option is duplicating the code for removing the peer.
>> >> >
>> >>
>> >> OK, I thought it is useless, but it seems needed.
>> >>
>> >> >
>> >> >>
>> >> >> > +    }
>> >> >> > +}
>> >> >> > +
>> >> >> > +static void
>> >> >> > +remove_pb_from_local_datapath(const struct sbrec_port_binding
*pb,
>> >> >> > +                              const struct sbrec_chassis
>> *chassis_rec,
>> >> >> > +                              struct binding_ctx_out *b_ctx_out,
>> >> >> > +                              struct local_datapath *ld)
>> >> >> > +{
>> >> >> > +    remove_local_lport_ids(pb, b_ctx_out->local_lport_ids);
>> >> >> > +    if (!strcmp(pb->type, "patch") ||
>> >> >> > +        !strcmp(pb->type, "l3gateway")) {
>> >> >> > +        remove_local_datapath_peer_port(pb, ld,
>> >> >> b_ctx_out->local_datapaths);
>> >> >> > +    } else if (!strcmp(pb->type, "localnet")) {
>> >> >> > +        if (ld->localnet_port &&
>> >> !strcmp(ld->localnet_port->logical_port,
>> >> >> > +                                         pb->logical_port)) {
>> >> >> > +            ld->localnet_port = NULL;
>> >> >> > +        }
>> >> >> > +    } else if (!strcmp(pb->type, "l3gateway")) {
>> >> >> > +        const char *chassis_id = smap_get(&pb->options,
>> >> >> > +                                          "l3gateway-chassis");
>> >> >> > +        if (chassis_id && !strcmp(chassis_id,
chassis_rec->name)) {
>> >> >> > +            ld->has_local_l3gateway = false;
>> >> >> > +        }
>> >> >> > +    }
>> >> >>
>> >> >> I think we need to remove ld from the local_datapaths if the
current
>> pb
>> >> is
>> >> >> the only reason for that ld to be exist on this chassis. Also, all
the
>> >> >> related lds connected by patch ports should be removed as well, if
>> there
>> >> >> are no other pbs causing them to be added to this chassis. This is
the
>> >> >> reverse of add_local_datapath__(). It doesn't seem to be easy
without
>> >> going
>> >> >> through all local_lbindings. Maybe this can be done in
>> >> >> binding_handle_ovs_interface_changes() and
>> >> >> binding_handle_port_binding_changes() out of the loop.
>> >> >>
>> >> >
>> >> > I left this unaddressed intentionally as I think it will complicate
the
>> >> code.
>> >> >
>> >> > Patch 3 of this series handles I-P for datapath and when a datapath
is
>> >> deleted,
>> >> > it removes the datapath from local_datapaths if it is present.
>> >> > I think this should be Ok as we will eventually free it. If we
really
>> >> want to address this,
>> >> > I'm fine with it but I'd prefer a follow up patch to do this.
>> >> >
>> >> OK, maybe a comment can be added to make it clear for future
improvement.
>> >> This changes the behavior of current implementation. Now unrelated
>> >> datapaths are removed when certain ports are unbound, and the flows in
>> the
>> >> HV can be largely reduced, but with this change it may not reduce any
>> more
>> >> until a recompute is triggered. It is not a big problem I think.
>> >
>> >
>> > Sure. I'll add a comment about it. I was also thinking of adding the
same
>> in TODO.rst.
>> > But it looks like TODO.rst needs some cleaning first.
>> >
>> >>
>> >> >
>> >> >>
>> >> >> > +}
>> >> >> > +
>> >> >> > +static bool
>> >> >> > +handle_iface_add(const struct ovsrec_interface *iface_rec,
>> >> >> > +                 const char *iface_id,
>> >> >> > +                 struct binding_ctx_in *b_ctx_in,
>> >> >> > +                 struct binding_ctx_out *b_ctx_out,
>> >> >> > +                 struct hmap *qos_map)
>> >> >> > +{
>> >> >> > +    sset_add(b_ctx_out->local_lports, iface_id);
>> >> >> > +    smap_replace(b_ctx_out->local_iface_ids, iface_rec->name,
>> >> iface_id);
>> >> >> > +
>> >> >> > +    struct local_binding *lbinding =
>> >> >> > +        local_binding_find(b_ctx_out->local_bindings, iface_id);
>> >> >> > +
>> >> >> > +    if (!lbinding) {
>> >> >> > +        lbinding = local_binding_create(iface_id, iface_rec,
NULL,
>> >> >> BT_VIF);
>> >> >> > +        local_binding_add(b_ctx_out->local_bindings, lbinding);
>> >> >> > +    } else {
>> >> >> > +        lbinding->iface = iface_rec;
>> >> >> > +    }
>> >> >> > +
>> >> >> > +    if (!lbinding->pb || strcmp(lbinding->name,
>> >> >> lbinding->pb->logical_port)) {
>> >> >> > +        lbinding->pb = lport_lookup_by_name(
>> >> >> > +            b_ctx_in->sbrec_port_binding_by_name,
lbinding->name);
>> >> >> > +        if (lbinding->pb && !strcmp(lbinding->pb->type,
>> "virtual")) {
>> >> >>
>> >> >> Since we never bind "virtual" ports, this should be a
>> misconfiguration,
>> >> and
>> >> >> it is better to log a warning message to be more clear.
>> >> >>
>> >> >> > +            lbinding->pb = NULL;
>> >> >> > +        }
>> >> >> > +    }
>> >> >> > +
>> >> >> > +    if (lbinding->pb) {
>> >> >> > +        if (!consider_vif_lport(lbinding->pb, b_ctx_in,
b_ctx_out,
>> >> >> > +                                lbinding, qos_map)) {
>> >> >> > +            return false;
>> >> >> > +        }
>> >> >> > +    }
>> >> >> > +
>> >> >> > +    /* Update the child local_binding's iface (if any children)
and
>> >> try
>> >> >> to
>> >> >> > +     *  claim the container lbindings. */
>> >> >> > +    struct shash_node *node;
>> >> >> > +    SHASH_FOR_EACH (node, &lbinding->children) {
>> >> >> > +        struct local_binding *child = node->data;
>> >> >> > +        child->iface = iface_rec;
>> >> >> > +        if (child->type == BT_CONTAINER) {
>> >> >> > +            if (!consider_container_lport(child->pb, b_ctx_in,
>> >> b_ctx_out,
>> >> >> > +                                          qos_map)) {
>> >> >> > +                return false;
>> >> >> > +            }
>> >> >> > +        }
>> >> >> > +    }
>> >> >> > +
>> >> >> > +    return true;
>> >> >> > +}
>> >> >> > +
>> >> >> > +static bool
>> >> >> > +handle_iface_delete(const char *lport_name, const char
*iface_name,
>> >> >> > +                    struct binding_ctx_in *b_ctx_in,
>> >> >> > +                    struct binding_ctx_out *b_ctx_out, bool
>> *changed)
>> >> >> > +{
>> >> >> > +    struct local_binding *lbinding;
>> >> >> > +    lbinding = local_binding_find(b_ctx_out->local_bindings,
>> >> >> > +                                  lport_name);
>> >> >> > +    if (lbinding && lbinding->pb &&
>> >> >> > +        lbinding->pb->chassis == b_ctx_in->chassis_rec) {
>> >> >> > +
>> >> >> > +        if (!release_local_binding(b_ctx_in->chassis_rec,
lbinding,
>> >> >> > +                                   !b_ctx_in->ovnsb_idl_txn)) {
>> >> >> > +            return false;
>> >> >> > +        }
>> >> >> > +
>> >> >> > +        struct local_datapath *ld =
>> >> >> > +            get_local_datapath(b_ctx_out->local_datapaths,
>> >> >> > +
>>  lbinding->pb->datapath->tunnel_key);
>> >> >> > +        if (ld) {
>> >> >> > +            remove_pb_from_local_datapath(lbinding->pb,
>> >> >> > +
 b_ctx_in->chassis_rec,
>> >> >> > +                                            b_ctx_out, ld);
>> >> >> > +        }
>> >> >> > +        local_binding_delete(b_ctx_out->local_bindings,
lbinding);
>> >> >> > +        *changed = true;
>> >> >> > +    }
>> >> >> > +
>> >> >> > +    sset_find_and_delete(b_ctx_out->local_lports, lport_name);
>> >> >> > +    smap_remove(b_ctx_out->local_iface_ids, iface_name);
>> >> >> > +
>> >> >> > +    return true;
>> >> >> > +}
>> >> >> > +
>> >> >> > +static bool
>> >> >> > +is_iface_vif(const struct ovsrec_interface *iface_rec)
>> >> >> > +{
>> >> >> > +    if (iface_rec->type && iface_rec->type[0] &&
>> >> >> > +        strcmp(iface_rec->type, "internal")) {
>> >> >> > +        return false;
>> >> >> > +    }
>> >> >> > +
>> >> >> > +    return true;
>> >> >> > +}
>> >> >> > +
>> >> >> > +/* Returns true if the ovs interface changes were handled
>> >> successfully,
>> >> >> > + * false otherwise.
>> >> >> > + */
>> >> >> > +bool
>> >> >> > +binding_handle_ovs_interface_changes(struct binding_ctx_in
>> *b_ctx_in,
>> >> >> > +                                     struct binding_ctx_out
>> >> *b_ctx_out,
>> >> >> > +                                     bool *changed)
>> >> >> > +{
>> >> >> > +    if (!b_ctx_in->chassis_rec) {
>> >> >> > +        return false;
>> >> >> > +    }
>> >> >> > +
>> >> >> > +    bool handled = true;
>> >> >> > +    *changed = false;
>> >> >> > +
>> >> >> > +    /* Run the tracked interfaces loop twice. One to handle
deleted
>> >> >> > +     * changes. And another to handle add/update changes.
>> >> >> > +     * This will ensure correctness.
>> >> >> > +     */
>> >> >> > +    const struct ovsrec_interface *iface_rec;
>> >> >> > +    OVSREC_INTERFACE_TABLE_FOR_EACH_TRACKED (iface_rec,
>> >> >> > +
>> b_ctx_in->iface_table) {
>> >> >> > +        if (!is_iface_vif(iface_rec)) {
>> >> >> > +            /* Right now are not handling ovs_interface changes
of
>> >> >> > +             * other types. This can be enhanced to handle of
>> >> >> > +             * types - patch and tunnel. */
>> >> >> > +            handled = false;
>> >> >> > +            break;
>> >> >> > +        }
>> >> >> > +
>> >> >> > +        const char *iface_id =
smap_get(&iface_rec->external_ids,
>> >> >> "iface-id");
>> >> >> > +        const char *old_iface_id =
>> >> smap_get(b_ctx_out->local_iface_ids,
>> >> >> > +                                            iface_rec->name);
>> >> >> > +        const char *cleared_iface_id = NULL;
>> >> >> > +        if (!ovsrec_interface_is_deleted(iface_rec)) {
>> >> >> > +            if (iface_id) {
>> >> >> > +                /* Check if iface_id is changed. If so we need
to
>> >> >> > +                 * release the old port binding and associate
this
>> >> >> > +                 * inteface to new port binding. */
>> >> >> > +                if (old_iface_id && strcmp(iface_id,
>> old_iface_id)) {
>> >> >> > +                    cleared_iface_id = old_iface_id;
>> >> >> > +                }
>> >> >> > +            } else if (old_iface_id) {
>> >> >> > +                cleared_iface_id = old_iface_id;
>> >> >> > +            }
>> >> >> >          } else {
>> >> >> > -            lbinding =
>> local_binding_find(b_ctx_out->local_bindings,
>> >> >> > -
>>  binding_rec->parent_port);
>> >> >> > +            cleared_iface_id = iface_id;
>> >> >> > +            iface_id = NULL;
>> >> >> >          }
>> >> >> >
>> >> >> > -        if (lbinding) {
>> >> >> > -            changed = true;
>> >> >> > +        if (cleared_iface_id) {
>> >> >> > +            handled = handle_iface_delete(cleared_iface_id,
>> >> >> iface_rec->name,
>> >> >> > +                                          b_ctx_in, b_ctx_out,
>> >> changed);
>> >> >> > +        }
>> >> >> > +
>> >> >> > +        if (!handled) {
>> >> >> >              break;
>> >> >> >          }
>> >> >> >      }
>> >> >> >
>> >> >> > -    return changed;
>> >> >> > +    if (!handled) {
>> >> >> > +        return false;
>> >> >> > +    }
>> >> >> > +
>> >> >> > +    struct hmap qos_map = HMAP_INITIALIZER(&qos_map);
>> >> >> > +    struct hmap *qos_map_ptr =
>> >> >> > +        sset_is_empty(b_ctx_out->egress_ifaces) ? NULL :
&qos_map;
>> >> >> > +
>> >> >> > +    OVSREC_INTERFACE_TABLE_FOR_EACH_TRACKED (iface_rec,
>> >> >> > +
>> b_ctx_in->iface_table) {
>> >> >> > +        /* Loop to handle create and update changes only. */
>> >> >>
>> >> >> Thanks for splitting the loops, but here are still some problems.
>> >> >>
>> >> >> 1. The first loop handled: a) interface deletion. b) interface
update
>> >> with
>> >> >> old iface-id cleared. So in this second loop it should handle all
the
>> >> rest
>> >> >> cases: a) interface add. b) interface update without iface-id
change.
>> It
>> >> >> seems b) is treated as just interface add, which may be wrong: if
>> >> iface-id
>> >> >> didn't change but some other fields of the interface record is
>> changed,
>> >> it
>> >> >> is not handled properly.
>> >> >
>> >> >
>> >> > If other fields change, what should we handle ? Can ofport change ?
>> >> > I'll see if ofport can be changed and what happens when it changes.
>> >> >
>> >> > For other fields like name, mtu etc do we need to handle anything ?
>> >> > I think we only care about the external_ids:iface-id and the ofport.
>> >> > The function handle_iface_add() even if it is called for other
changes,
>> >> > it will be a no-op.
>> >> >
>> >> > Also from the tracking we can't figure out what fields change right
?
>> >> >
>> >> >
>> >> Yes, I think it is hard to check with fields are changed and handle it
>> >> accordingly. That's why I suggest to make it simple: if there is an
>> update,
>> >> always handle it like a deletion and then creation. This way, we dont
>> need
>> >> to consider all the corner cases and we are sure it is correct.
>> >
>> >
>> > In this particular case of binding, I don't think there are any corner
>> cases..
>> >
>> > We consider an OVS interface for binding if the below 2 conditions meet
>> >
>> > 1. external_ids:iface-id is set
>> > 2. ofport is > 0.
>> >
>> > Otherwise we need to consider a release if it was already claimed.
>> >
>> > When  an ovs interface is updated like below
>> >  $ovs-vsctl set interface p1 external_ids:foo=bar
>> >
>> > We don't need to handle it like a deletion and then a creation. Because
>> we can
>> > easily see that the above conditions are met,
>> >
>> What you described is correct (I believe). However, it is the declarative
>> way of thinking, which is perfect for the always recompute
implementation,
>> but it is hard to reason the correctness and to maintain in longer term
for
>> incremental processing. Incremental processing focuses on the data change
>> instead of the desired status. In this case you may have already thought
>> through and sure about the correctness even some data change is ignored.
>> However, in the long run, it is hard to remember all the details about
>> which field change needs to be handled in which way, and why some other
>> changes are ignored. More importantly, when a developer who is not so
>> familiar with this changle handling logic want to add a new feature,
which
>> requires another field, e.g. another key-value in external_ids, it is
easy
>> to forget to update here, and not easy to catch in code review either,
and
>> even we do aware it needs to be updated here, the if condition would get
so
>> complex even with just 3 fields to be considered.
>
>
>
> I think adding comments should be enough for a new user. And I'll make
sure
> I add proper comments.
> In the case of binding an interface, I really don't see why would
anything change in the future. Even
> if it changes, I think the test cases should fail if that is the case.
>
>
>>
>> This is why I think we
>> should make it "simple and stupid" to handle updates as if the old one
was
>> deleted and then a new one is added.
>
>
>
> If suppose an OVS interface external_ids:iface-id is updated from 'foo'
to 'bar'
> and if 'foo' logical port was already bound, then we still need to check
what was
> the old value from the 'b_ctx_out->local_iface_ids' and release that port
binding
> before considering claiming the port binding for 'bar'.
>
> In my opinion calling delete followed by create will not help. And we
still need to maintain the
> logic of checking the necessary fields as the ovsdb tracking doesn't say
what changed.
> And I don't think calling delete, followed by create is going to make the
code simpler. Or will work
> without any changes later if there is a need to handle port binding
differently.
>

Maybe deletion/addition is not accurate here. I didn't mean we don't need
to compare the iface-id. iface-id is a key here and we need to be sure if
it is changed. Since ovsdb tracking doesn't provide the old data, I think
your approach of maintaining the local_binding data structure served that
purpose pretty well. The problem we are discussing here is about how to
handle the changes when we compared and identified the iface-id
change/no-change. So I didn't mean we can simply handle as if a interface
is deleted/added (sorry about my wording). Maybe unbinding/binding is a
better word (or releasing/claiming), which means deletion/addition of a
local_binding entry (and all the data related to it). Now the first loop is
already doing the "releasing", for the second loop, since the rest of cases
include both "claiming" and "updating claiming". My proposal is just to
handle the later in a simple way with confidence.

The miss in the below condition is just an example why handling each of the
other fields could be complex and error prone:

>> >> >> > +        if (iface_id && ofport > 0) {
>> >> >>
>> >> >> What if iface-id didn't change, but ofport changed from non-zero
to 0?
>> >> How
>> >> >> should we handle the change?

For the case of "iface-id didn't change, but ofport changed from non-zero
to 0", according to the criteria you gave, the binding should be released.
My proposal is that we don't need to check the ofport here at all. In this
loop, just follow the principle of "releasing the old" and "claiming the
new", we will first release the binding, and then handle the claiming,
which would fail because the criteria for binding is not met.

Although you could fix it by adding some more condition checks here for
ofport to achieve the same result, I think it makes the change-handler more
complex unnecessarily. If there are more conditions to check in the future
it would be harder to maintain. The conditions check is better to be
encapsulated in the functions that handles the claim/release.

Similar handlings in lflow processing is, lflow uuid is the key. If a lflow
is changed, delete everything related to the lflow, and re-consider it.
Same for port-binding handling in physical flows. Here, the key is
<iface-id, ifname>. If one of the pair is gone, the related bindings should
be released; if a new pair is added; the related binding should be created;
if an existing pair is updated with whatever fields, release the binding
and reclaim it so than any property of the binding is up to date. To figure
out the addition/deletion/update of the <iface-id, ifname> pair, the
approach you did with local_binding structure is very helpful, which
overcame the ovsdb tracking defect.

>
>>
>> At least this has provided me a lot of
>> convenience in the earlier I-P implementations, and I was even surprised
>> that there were so many new features added but most of them didn't needed
>> to be aware of the I-P details, thanks to this principle. I hope this
>> clarifies the idea behind.
>
>
> Ok. But I'm not sure if we should apply this principle as a strict rule.
>
Of course, there will always be exceptions. When applying this rule create
more trouble than benefit we don't need to follow, but at least it is a way
help us to evaluate if there is anything missing in the I-P, to avoid
pitfalls.

>>
>> > It also creates unnecessary deletion and re-adding of OVS flows related
>> to that
>> > logical port when we handle flow computation for changed runtime data.
>> >
>> > If we handle this way, there won't be any runtime data changes.
>> >
>>
>> I agree it may be less optimized, but considering that we are
incrementally
>> processing the changes here, even if there are many interfaces deteled
and
>> added unnecesarily, the cost of I-P for such changes is just nothing
>> comparing with the full recompute. I believe it would be good enough from
>> the end user point of view, if we can make it incrementally processed,
>> correctly.
>>
>
> But the idea of this whole patch series is also to improve the
performance as much
> as we can, because of the scale issues we have  seen.
>
I think all the performance problem was because of recomputing, so I guess
the purpose of this series is to do I-P as much as we can. I assume once
the change is handled incrementally, there is no performance problem to be
worried about. For the interface changes, one interface may correspond to
one physical flow only. Even tens of interfaces are changed together on the
chassis we only need to handle tens of flow computations, which is nothing
compared to the millions of flow computations that caused the performance
problems.

My suggestions here are the principles that I think is helpful to achieve
the correctness and maintenability, but if you think the same has been
thought through and can be achieved in different ways, I respect your
decision ;)

> I would also like to hear the opinion of Mark, Dumitru and others .
>
> Thanks
> Numan
>
>>
>> > Thanks
>> > Numan
>> >
>> >>
>> >> >
>> >> >>
>> >> >>
>> >> >> 2. Some other condition checks should be done for this loop, such
as:
>> if
>> >> >> (!is_iface_vif(iface_rec)) ...
>> >> >
>> >> >
>> >> > This is not required because the first loop checks for it and if
there
>> is
>> >> any
>> >> > non vif interface change, then this function returns false, before
the
>> >> > second loop.
>> >> >
>> >> OK.
>> >>
>> >> >>
>> >> >>
>> >> >> > +        if (ovsrec_interface_is_deleted(iface_rec)) {
>> >> >> > +            continue;
>> >> >> > +        }
>> >> >> > +
>> >> >> > +        const char *iface_id =
smap_get(&iface_rec->external_ids,
>> >> >> "iface-id");
>> >> >> > +        int64_t ofport = iface_rec->n_ofport ?
*iface_rec->ofport
>> : 0;
>> >> >> > +        if (iface_id && ofport > 0) {
>> >> >>
>> >> >> What if iface-id didn't change, but ofport changed from non-zero
to 0?
>> >> How
>> >> >> should we handle the change?
>> >> >>
>> >> >
>> >> > Good point. I think we should consider this as  handle_iface_delete.
>> >> >
>> >> > I'll rename the function handle_iface_delete() to
>> handle_iface_release()
>> >> > and handle_iface_add() to handle_iface_claim() to be more clear.
>> >> >
>> >> >> A general approach can be: whenever there is an update
>> >> >> (!ovsrec_interface_is_new()), we firstly handle it as interface
>> delete,
>> >> and
>> >> >> then handle an interface add.
>> >> >
>> >> >
>> >> > But why delete and add if can determine that it is going to be
either
>> one
>> >> > i.e release or claim and I think we can definitely determine.
>> >> > Also in patch 6/7 where we do tracking, it becomes a bit complicated
>> >> > if we handle it as delete and then add as we may add 2 such tracking
>> >> > data.
>> >> >
>> >> It is for simplicity and correctness. Please see the comment above.
>> >>
>> >> > Thanks
>> >> > Numan
>> >> >
>> >> >>
>> >> >> > +            *changed = true;
>> >> >> > +            handled = handle_iface_add(iface_rec, iface_id,
>> b_ctx_in,
>> >> >> > +                                       b_ctx_out, qos_map_ptr);
>> >> >> > +            if (!handled) {
>> >> >> > +                break;
>> >> >> > +            }
>> >> >> > +        }
>> >> >> > +    }
>> >> >> > +
>> >> >> > +    if (handled && qos_map_ptr &&
>> set_noop_qos(b_ctx_in->ovs_idl_txn,
>> >> >> > +
>> b_ctx_in->port_table,
>> >> >> > +
b_ctx_in->qos_table,
>> >> >> > +
>> >> >> b_ctx_out->egress_ifaces)) {
>> >> >> > +        const char *entry;
>> >> >> > +        SSET_FOR_EACH (entry, b_ctx_out->egress_ifaces) {
>> >> >> > +            setup_qos(entry, &qos_map);
>> >> >> > +        }
>> >> >> > +    }
>> >> >> > +
>> >> >> > +    destroy_qos_map(&qos_map);
>> >> >> > +    return handled;
>> >> >> >  }
>> >> >> >
>> >> >> > -/* Returns true if the database is all cleaned up, false if more
>> work
>> >> is
>> >> >> > - * required. */
>> >> >> > -bool
>> >> >> > -binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn,
>> >> >> > -                const struct sbrec_port_binding_table
>> >> >> *port_binding_table,
>> >> >> > -                const struct sbrec_chassis *chassis_rec)
>> >> >> > +static void
>> >> >> > +handle_deleted_lport(const struct sbrec_port_binding *pb,
>> >> >> > +                     struct binding_ctx_in *b_ctx_in,
>> >> >> > +                     struct binding_ctx_out *b_ctx_out)
>> >> >> >  {
>> >> >> > -    if (!ovnsb_idl_txn) {
>> >> >> > +    struct local_datapath *ld =
>> >> >> > +        get_local_datapath(b_ctx_out->local_datapaths,
>> >> >> > +                           pb->datapath->tunnel_key);
>> >> >> > +    if (ld) {
>> >> >> > +        remove_pb_from_local_datapath(pb, b_ctx_in->chassis_rec,
>> >> >> > +                                      b_ctx_out, ld);
>> >> >> > +    }
>> >> >> > +}
>> >> >> > +
>> >> >> > +static struct local_binding *
>> >> >> > +get_lbinding_for_lport(const struct sbrec_port_binding *pb,
>> >> >> > +                       enum en_lport_type lport_type,
>> >> >> > +                       struct binding_ctx_out *b_ctx_out)
>> >> >> > +{
>> >> >> > +    ovs_assert(lport_type == LP_VIF || lport_type ==
LP_VIRTUAL);
>> >> >> > +
>> >> >> > +    if (lport_type == LP_VIF && !is_lport_container(pb)) {
>> >> >> > +        return local_binding_find(b_ctx_out->local_bindings,
>> >> >> pb->logical_port);
>> >> >> > +    }
>> >> >> > +
>> >> >> > +    struct local_binding *parent_lbinding = NULL;
>> >> >> > +
>> >> >> > +    if (lport_type == LP_VIRTUAL) {
>> >> >> > +        parent_lbinding =
>> >> local_binding_find(b_ctx_out->local_bindings,
>> >> >> > +
pb->virtual_parent);
>> >> >> > +    } else {
>> >> >> > +        parent_lbinding =
>> >> local_binding_find(b_ctx_out->local_bindings,
>> >> >> > +                                             pb->parent_port);
>> >> >> > +    }
>> >> >> > +
>> >> >> > +    return parent_lbinding
>> >> >> > +           ? local_binding_find(&parent_lbinding->children,
>> >> >> pb->logical_port)
>> >> >> > +           : NULL;
>> >> >> > +}
>> >> >> > +
>> >> >> > +static bool
>> >> >> > +handle_deleted_vif_lport(const struct sbrec_port_binding *pb,
>> >> >> > +                         enum en_lport_type lport_type,
>> >> >> > +                         struct binding_ctx_in *b_ctx_in,
>> >> >> > +                         struct binding_ctx_out *b_ctx_out,
>> >> >> > +                         bool *changed)
>> >> >> > +{
>> >> >> > +    struct local_binding *lbinding =
>> >> >> > +        get_lbinding_for_lport(pb, lport_type, b_ctx_out);
>> >> >> > +
>> >> >> > +    if (lbinding) {
>> >> >> > +        lbinding->pb = NULL;
>> >> >> > +        /* The port_binding 'pb' is deleted. So there is no
need to
>> >> >> > +         * clear the 'chassis' column of 'pb'. But we need to do
>> >> >> > +         * for the local_binding's children. */
>> >> >> > +        if (lbinding->type == BT_VIF &&
>> >> >> > +
>>  !release_local_binding_children(b_ctx_in->chassis_rec,
>> >> >> > +                                                lbinding,
>> >> >> > +
>> >> >>  !b_ctx_in->ovnsb_idl_txn)) {
>> >> >> > +            return false;
>> >> >> > +        }
>> >> >> > +
>> >> >> > +        *changed = true;
>> >> >> > +    }
>> >> >> > +
>> >> >> > +    handle_deleted_lport(pb, b_ctx_in, b_ctx_out);
>> >> >> > +    return true;
>> >> >> > +}
>> >> >> > +
>> >> >> > +static bool
>> >> >> > +handle_updated_vif_lport(const struct sbrec_port_binding *pb,
>> >> >> > +                         enum en_lport_type lport_type,
>> >> >> > +                         struct binding_ctx_in *b_ctx_in,
>> >> >> > +                         struct binding_ctx_out *b_ctx_out,
>> >> >> > +                         struct hmap *qos_map,
>> >> >> > +                         bool *changed)
>> >> >> > +{
>> >> >> > +    bool claimed = (pb->chassis == b_ctx_in->chassis_rec);
>> >> >> > +    bool handled = true;
>> >> >> > +
>> >> >> > +    if (lport_type == LP_VIRTUAL) {
>> >> >> > +        handled = consider_virtual_lport(pb, b_ctx_in,
b_ctx_out,
>> >> >> qos_map);
>> >> >> > +    } else if (lport_type == LP_VIF && is_lport_container(pb)) {
>> >> >> > +        handled = consider_container_lport(pb, b_ctx_in,
b_ctx_out,
>> >> >> qos_map);
>> >> >> > +    } else {
>> >> >> > +        handled = consider_vif_lport(pb, b_ctx_in, b_ctx_out,
NULL,
>> >> >> qos_map);
>> >> >> > +    }
>> >> >> > +
>> >> >> > +    if (!handled) {
>> >> >> >          return false;
>> >> >> >      }
>> >> >> > -    if (!chassis_rec) {
>> >> >> > +
>> >> >> > +    bool now_claimed = (pb->chassis == b_ctx_in->chassis_rec);
>> >> >> > +    bool changed_ = (claimed != now_claimed);
>> >> >> > +
>> >> >> > +    if (changed_) {
>> >> >> > +        *changed = true;
>> >> >> > +    }
>> >> >> > +
>> >> >> > +    if (lport_type == LP_VIRTUAL ||
>> >> >> > +        (lport_type == LP_VIF && is_lport_container(pb)) ||
>> >> !changed_) {
>> >> >> >          return true;
>> >> >> >      }
>> >> >> >
>> >> >> > -    const struct sbrec_port_binding *binding_rec;
>> >> >> > -    bool any_changes = false;
>> >> >> > -    SBREC_PORT_BINDING_TABLE_FOR_EACH (binding_rec,
>> >> port_binding_table) {
>> >> >> > -        if (binding_rec->chassis == chassis_rec) {
>> >> >> > -            if (binding_rec->encap)
>> >> >> > -                sbrec_port_binding_set_encap(binding_rec, NULL);
>> >> >> > -            sbrec_port_binding_set_chassis(binding_rec, NULL);
>> >> >> > -            any_changes = true;
>> >> >> > +    struct local_binding *lbinding =
>> >> >> > +        local_binding_find(b_ctx_out->local_bindings,
>> >> pb->logical_port);
>> >> >> > +
>> >> >> > +    ovs_assert(lbinding);
>> >> >> > +
>> >> >> > +    struct shash_node *node;
>> >> >> > +    SHASH_FOR_EACH (node, &lbinding->children) {
>> >> >> > +        struct local_binding *child = node->data;
>> >> >> > +        if (child->type == BT_CONTAINER) {
>> >> >> > +            handled = consider_container_lport(child->pb,
b_ctx_in,
>> >> >> b_ctx_out,
>> >> >> > +                                               qos_map);
>> >> >> > +            if (!handled) {
>> >> >> > +                return false;
>> >> >> > +            }
>> >> >> >          }
>> >> >> >      }
>> >> >> >
>> >> >> > -    if (any_changes) {
>> >> >> > -        ovsdb_idl_txn_add_comment(
>> >> >> > -            ovnsb_idl_txn,
>> >> >> > -            "ovn-controller: removing all port bindings for
'%s'",
>> >> >> > -            chassis_rec->name);
>> >> >> > +    return true;
>> >> >> > +}
>> >> >> > +
>> >> >> > +/* Returns true if the port binding changes resulted in local
>> binding
>> >> >> > + * updates, false otherwise.
>> >> >> > + */
>> >> >> > +bool
>> >> >> > +binding_handle_port_binding_changes(struct binding_ctx_in
>> *b_ctx_in,
>> >> >> > +                                    struct binding_ctx_out
>> *b_ctx_out,
>> >> >> > +                                    bool *changed)
>> >> >> > +{
>> >> >> > +    bool handled = true;
>> >> >> > +    *changed = false;
>> >> >> > +
>> >> >> > +    const struct sbrec_port_binding *pb;
>> >> >> > +
>> >> >> > +    /* Run the tracked port binding loop twice. One to handle
>> deleted
>> >> >> > +     * changes. And another to handle add/update changes.
>> >> >> > +     * This will ensure correctness.
>> >> >> > +     */
>> >> >> > +    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
>> >> >> > +
>> >> >> b_ctx_in->port_binding_table) {
>> >> >> > +        if (!sbrec_port_binding_is_deleted(pb)) {
>> >> >> > +            continue;
>> >> >> > +        }
>> >> >> > +
>> >> >> > +        enum en_lport_type lport_type = get_lport_type(pb);
>> >> >> > +        if (lport_type == LP_VIF || lport_type == LP_VIRTUAL) {
>> >> >> > +            handled = handle_deleted_vif_lport(pb, lport_type,
>> >> b_ctx_in,
>> >> >> > +                                                b_ctx_out,
>> changed);
>> >> >> > +        } else {
>> >> >> > +            handle_deleted_lport(pb, b_ctx_in, b_ctx_out);
>> >> >> > +        }
>> >> >> > +
>> >> >> > +        if (!handled) {
>> >> >> > +            break;
>> >> >> > +        }
>> >> >> >      }
>> >> >> >
>> >> >> > -    return !any_changes;
>> >> >> > +    if (!handled) {
>> >> >> > +        return false;
>> >> >> > +    }
>> >> >> > +
>> >> >> > +    struct hmap qos_map = HMAP_INITIALIZER(&qos_map);
>> >> >> > +    struct hmap *qos_map_ptr =
>> >> >> > +        sset_is_empty(b_ctx_out->egress_ifaces) ? NULL :
&qos_map;
>> >> >> > +
>> >> >> > +    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
>> >> >> > +
>> >> >> b_ctx_in->port_binding_table) {
>> >> >> > +        /* Loop to handle create and update changes only. */
>> >> >> > +        if (sbrec_port_binding_is_deleted(pb)) {
>> >> >> > +            continue;
>> >> >> > +        }
>> >> >> > +
>> >> >> > +        enum en_lport_type lport_type = get_lport_type(pb);
>> >> >> > +
>> >> >> > +        struct local_datapath *ld =
>> >> >> > +            get_local_datapath(b_ctx_out->local_datapaths,
>> >> >> > +                               pb->datapath->tunnel_key);
>> >> >> > +
>> >> >> > +        switch (lport_type) {
>> >> >> > +        case LP_VIF:
>> >> >> > +        case LP_VIRTUAL:
>> >> >> > +            handled = handle_updated_vif_lport(pb, lport_type,
>> >> b_ctx_in,
>> >> >> > +                                               b_ctx_out,
>> qos_map_ptr,
>> >> >> > +                                               changed);
>> >> >> > +            break;
>> >> >> > +
>> >> >> > +        case LP_PATCH:
>> >> >> > +        case LP_LOCALPORT:
>> >> >> > +        case LP_VTEP:
>> >> >> > +            *changed = true;
>> >> >> > +            update_local_lport_ids(b_ctx_out->local_lport_ids,
pb);
>> >> >> > +            if (lport_type ==  LP_PATCH) {
>> >> >> > +                /* Add the peer datapath to the local datapaths
if
>> >> it's
>> >> >> > +                    * not present yet.
>> >> >> > +                    */
>> >> >> > +                if (ld) {
>> >> >> > +                    add_local_datapath_peer_port(pb, b_ctx_in,
>> >> >> > +                                                    b_ctx_out,
ld);
>> >> >> > +                }
>> >> >> > +            }
>> >> >> > +            break;
>> >> >> > +
>> >> >> > +        case LP_L2GATEWAY:
>> >> >> > +            *changed = true;
>> >> >> > +            handled = consider_l2gw_lport(pb, b_ctx_in,
b_ctx_out);
>> >> >> > +            break;
>> >> >> > +
>> >> >> > +        case LP_L3GATEWAY:
>> >> >> > +            *changed = true;
>> >> >> > +            handled = consider_l3gw_lport(pb, b_ctx_in,
b_ctx_out);
>> >> >> > +            break;
>> >> >> > +
>> >> >> > +        case LP_CHASSISREDIRECT:
>> >> >> > +            *changed = true;
>> >> >> > +            handled = consider_cr_lport(pb, b_ctx_in,
b_ctx_out);
>> >> >> > +            break;
>> >> >> > +
>> >> >> > +        case LP_EXTERNAL:
>> >> >> > +            *changed = true;
>> >> >> > +            handled = consider_external_lport(pb, b_ctx_in,
>> >> b_ctx_out);
>> >> >> > +            break;
>> >> >> > +
>> >> >> > +        case LP_LOCALNET: {
>> >> >> > +            *changed = true;
>> >> >> > +            consider_localnet_lport(pb, b_ctx_in, b_ctx_out,
>> >> >> qos_map_ptr);
>> >> >> > +
>> >> >> > +            struct shash bridge_mappings =
>> >> >> > +                SHASH_INITIALIZER(&bridge_mappings);
>> >> >> > +            add_ovs_bridge_mappings(b_ctx_in->ovs_table,
>> >> >> > +                                    b_ctx_in->bridge_table,
>> >> >> > +                                    &bridge_mappings);
>> >> >> > +            update_ld_localnet_port(pb, &bridge_mappings,
>> >> >> > +                                    b_ctx_out->egress_ifaces,
>> >> >> > +                                    b_ctx_out->local_datapaths);
>> >> >> > +            shash_destroy(&bridge_mappings);
>> >> >> > +            break;
>> >> >> > +        }
>> >> >> > +
>> >> >> > +        case LP_REMOTE:
>> >> >> > +        case LP_UNKNOWN:
>> >> >> > +            break;
>> >> >> > +        }
>> >> >> > +
>> >> >> > +        if (!handled) {
>> >> >> > +            break;
>> >> >> > +        }
>> >> >> > +    }
>> >> >> > +
>> >> >> > +    if (handled && qos_map_ptr &&
>> set_noop_qos(b_ctx_in->ovs_idl_txn,
>> >> >> > +
>> b_ctx_in->port_table,
>> >> >> > +
b_ctx_in->qos_table,
>> >> >> > +
>> >> >> b_ctx_out->egress_ifaces)) {
>> >> >> > +        const char *entry;
>> >> >> > +        SSET_FOR_EACH (entry, b_ctx_out->egress_ifaces) {
>> >> >> > +            setup_qos(entry, &qos_map);
>> >> >> > +        }
>> >> >> > +    }
>> >> >> > +
>> >> >> > +    destroy_qos_map(&qos_map);
>> >> >> > +    return handled;
>> >> >> >  }
>> >> >> > diff --git a/controller/binding.h b/controller/binding.h
>> >> >> > index 9affc9a96..f7ace6faf 100644
>> >> >> > --- a/controller/binding.h
>> >> >> > +++ b/controller/binding.h
>> >> >> > @@ -57,6 +57,7 @@ struct binding_ctx_out {
>> >> >> >      struct sset *local_lports;
>> >> >> >      struct sset *local_lport_ids;
>> >> >> >      struct sset *egress_ifaces;
>> >> >> > +    struct smap *local_iface_ids;
>> >> >> >  };
>> >> >> >
>> >> >> >  void binding_register_ovs_idl(struct ovsdb_idl *);
>> >> >> > @@ -64,9 +65,13 @@ void binding_run(struct binding_ctx_in *,
struct
>> >> >> binding_ctx_out *);
>> >> >> >  bool binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn,
>> >> >> >                       const struct sbrec_port_binding_table *,
>> >> >> >                       const struct sbrec_chassis *);
>> >> >> > -bool binding_evaluate_port_binding_changes(struct
binding_ctx_in *,
>> >> >> > -                                           struct
binding_ctx_out
>> *);
>> >> >> >
>> >> >> >  void local_bindings_init(struct shash *local_bindings);
>> >> >> >  void local_bindings_destroy(struct shash *local_bindings);
>> >> >> > +bool binding_handle_ovs_interface_changes(struct binding_ctx_in
*,
>> >> >> > +                                          struct
binding_ctx_out *,
>> >> >> > +                                          bool *changed);
>> >> >> > +bool binding_handle_port_binding_changes(struct binding_ctx_in
*,
>> >> >> > +                                         struct binding_ctx_out
*,
>> >> >> > +                                         bool *changed);
>> >> >> >  #endif /* controller/binding.h */
>> >> >> > diff --git a/controller/ovn-controller.c
>> b/controller/ovn-controller.c
>> >> >> > index 6b759966b..08b074415 100644
>> >> >> > --- a/controller/ovn-controller.c
>> >> >> > +++ b/controller/ovn-controller.c
>> >> >> > @@ -753,6 +753,7 @@ enum sb_engine_node {
>> >> >> >      OVS_NODE(open_vswitch, "open_vswitch") \
>> >> >> >      OVS_NODE(bridge, "bridge") \
>> >> >> >      OVS_NODE(port, "port") \
>> >> >> > +    OVS_NODE(interface, "interface") \
>> >> >> >      OVS_NODE(qos, "qos")
>> >> >> >
>> >> >> >  enum ovs_engine_node {
>> >> >> > @@ -974,6 +975,7 @@ struct ed_type_runtime_data {
>> >> >> >      struct sset active_tunnels;
>> >> >> >
>> >> >> >      struct sset egress_ifaces;
>> >> >> > +    struct smap local_iface_ids;
>> >> >> >  };
>> >> >> >
>> >> >> >  static void *
>> >> >> > @@ -987,6 +989,7 @@ en_runtime_data_init(struct engine_node *node
>> >> >> OVS_UNUSED,
>> >> >> >      sset_init(&data->local_lport_ids);
>> >> >> >      sset_init(&data->active_tunnels);
>> >> >> >      sset_init(&data->egress_ifaces);
>> >> >> > +    smap_init(&data->local_iface_ids);
>> >> >> >      local_bindings_init(&data->local_bindings);
>> >> >> >      return data;
>> >> >> >  }
>> >> >> > @@ -1000,6 +1003,7 @@ en_runtime_data_cleanup(void *data)
>> >> >> >      sset_destroy(&rt_data->local_lport_ids);
>> >> >> >      sset_destroy(&rt_data->active_tunnels);
>> >> >> >      sset_destroy(&rt_data->egress_ifaces);
>> >> >> > +    smap_destroy(&rt_data->local_iface_ids);
>> >> >> >      struct local_datapath *cur_node, *next_node;
>> >> >> >      HMAP_FOR_EACH_SAFE (cur_node, next_node, hmap_node,
>> >> >> >                          &rt_data->local_datapaths) {
>> >> >> > @@ -1041,6 +1045,10 @@ init_binding_ctx(struct engine_node *node,
>> >> >> >          (struct ovsrec_port_table *)EN_OVSDB_GET(
>> >> >> >              engine_get_input("OVS_port", node));
>> >> >> >
>> >> >> > +    struct ovsrec_interface_table *iface_table =
>> >> >> > +        (struct ovsrec_interface_table *)EN_OVSDB_GET(
>> >> >> > +            engine_get_input("OVS_interface", node));
>> >> >> > +
>> >> >> >      struct ovsrec_qos_table *qos_table =
>> >> >> >          (struct ovsrec_qos_table *)EN_OVSDB_GET(
>> >> >> >              engine_get_input("OVS_qos", node));
>> >> >> > @@ -1070,6 +1078,7 @@ init_binding_ctx(struct engine_node *node,
>> >> >> >      b_ctx_in->sbrec_port_binding_by_datapath =
>> >> >> sbrec_port_binding_by_datapath;
>> >> >> >      b_ctx_in->sbrec_port_binding_by_name =
>> sbrec_port_binding_by_name;
>> >> >> >      b_ctx_in->port_table = port_table;
>> >> >> > +    b_ctx_in->iface_table = iface_table;
>> >> >> >      b_ctx_in->qos_table = qos_table;
>> >> >> >      b_ctx_in->port_binding_table = pb_table;
>> >> >> >      b_ctx_in->br_int = br_int;
>> >> >> > @@ -1083,6 +1092,7 @@ init_binding_ctx(struct engine_node *node,
>> >> >> >      b_ctx_out->local_lport_ids = &rt_data->local_lport_ids;
>> >> >> >      b_ctx_out->egress_ifaces = &rt_data->egress_ifaces;
>> >> >> >      b_ctx_out->local_bindings = &rt_data->local_bindings;
>> >> >> > +    b_ctx_out->local_iface_ids = &rt_data->local_iface_ids;
>> >> >> >  }
>> >> >> >
>> >> >> >  static void
>> >> >> > @@ -1111,10 +1121,12 @@ en_runtime_data_run(struct engine_node
>> *node,
>> >> >> void *data)
>> >> >> >          sset_destroy(local_lport_ids);
>> >> >> >          sset_destroy(active_tunnels);
>> >> >> >          sset_destroy(&rt_data->egress_ifaces);
>> >> >> > +        smap_destroy(&rt_data->local_iface_ids);
>> >> >> >          sset_init(local_lports);
>> >> >> >          sset_init(local_lport_ids);
>> >> >> >          sset_init(active_tunnels);
>> >> >> >          sset_init(&rt_data->egress_ifaces);
>> >> >> > +        smap_init(&rt_data->local_iface_ids);
>> >> >> >          local_bindings_init(&rt_data->local_bindings);
>> >> >> >      }
>> >> >> >
>> >> >> > @@ -1140,6 +1152,34 @@ en_runtime_data_run(struct engine_node
*node,
>> >> void
>> >> >> *data)
>> >> >> >      engine_set_node_state(node, EN_UPDATED);
>> >> >> >  }
>> >> >> >
>> >> >> > +static bool
>> >> >> > +runtime_data_ovs_interface_handler(struct engine_node *node,
void
>> >> *data)
>> >> >> > +{
>> >> >> > +    struct ed_type_runtime_data *rt_data = data;
>> >> >> > +    struct binding_ctx_in b_ctx_in;
>> >> >> > +    struct binding_ctx_out b_ctx_out;
>> >> >> > +    init_binding_ctx(node, rt_data, &b_ctx_in, &b_ctx_out);
>> >> >> > +
>> >> >> > +    bool changed = false;
>> >> >> > +    if (!binding_handle_ovs_interface_changes(&b_ctx_in,
>> &b_ctx_out,
>> >> >> > +                                              &changed)) {
>> >> >> > +        return false;
>> >> >> > +    }
>> >> >> > +
>> >> >> > +    if (changed) {
>> >> >> > +        engine_set_node_state(node, EN_UPDATED);
>> >> >> > +    }
>> >> >> > +
>> >> >> > +    return true;
>> >> >> > +}
>> >> >> > +
>> >> >> > +static bool
>> >> >> > +runtime_data_noop_handler(struct engine_node *node OVS_UNUSED,
>> >> >> > +                          void *data OVS_UNUSED)
>> >> >> > +{
>> >> >> > +    return true;
>> >> >> > +}
>> >> >> > +
>> >> >> >  static bool
>> >> >> >  runtime_data_sb_port_binding_handler(struct engine_node *node,
void
>> >> >> *data)
>> >> >> >  {
>> >> >> > @@ -1147,11 +1187,21 @@
runtime_data_sb_port_binding_handler(struct
>> >> >> engine_node *node, void *data)
>> >> >> >      struct binding_ctx_in b_ctx_in;
>> >> >> >      struct binding_ctx_out b_ctx_out;
>> >> >> >      init_binding_ctx(node, rt_data, &b_ctx_in, &b_ctx_out);
>> >> >> > +    if (!b_ctx_in.chassis_rec) {
>> >> >> > +        return false;
>> >> >> > +    }
>> >> >> > +
>> >> >> > +    bool changed = false;
>> >> >> > +    if (!binding_handle_port_binding_changes(&b_ctx_in,
&b_ctx_out,
>> >> >> > +                                             &changed)) {
>> >> >> > +        return false;
>> >> >> > +    }
>> >> >> >
>> >> >> > -    bool changed =
binding_evaluate_port_binding_changes(&b_ctx_in,
>> >> >> > -
>> &b_ctx_out);
>> >> >> > +    if (changed) {
>> >> >> > +        engine_set_node_state(node, EN_UPDATED);
>> >> >> > +    }
>> >> >> >
>> >> >> > -    return !changed;
>> >> >> > +    return true;
>> >> >> >  }
>> >> >> >
>> >> >> >  /* Connection tracking zones. */
>> >> >> > @@ -1894,7 +1944,10 @@ main(int argc, char *argv[])
>> >> >> >
>> >> >> >      engine_add_input(&en_runtime_data, &en_ovs_open_vswitch,
NULL);
>> >> >> >      engine_add_input(&en_runtime_data, &en_ovs_bridge, NULL);
>> >> >> > -    engine_add_input(&en_runtime_data, &en_ovs_port, NULL);
>> >> >> > +    engine_add_input(&en_runtime_data, &en_ovs_port,
>> >> >> > +                     runtime_data_noop_handler);
>> >> >> > +    engine_add_input(&en_runtime_data, &en_ovs_interface,
>> >> >> > +                     runtime_data_ovs_interface_handler);
>> >> >> >      engine_add_input(&en_runtime_data, &en_ovs_qos, NULL);
>> >> >> >
>> >> >> >      engine_add_input(&en_runtime_data, &en_sb_chassis, NULL);
>> >> >> > diff --git a/tests/ovn-performance.at b/tests/ovn-performance.at
>> >> >> > index a8a15f8fe..5dfc6f7ca 100644
>> >> >> > --- a/tests/ovn-performance.at
>> >> >> > +++ b/tests/ovn-performance.at
>> >> >> > @@ -239,6 +239,19 @@ for i in 1 2; do
>> >> >> >      ovn_attach n1 br-phys 192.168.0.$i
>> >> >> >  done
>> >> >> >
>> >> >> > +# Wait for the tunnel ports to be created and up.
>> >> >> > +# Otherwise this may affect the lflow_run count.
>> >> >> > +
>> >> >> > +OVS_WAIT_UNTIL([
>> >> >> > +    test $(as hv1 ovs-vsctl list interface ovn-hv2-0 | \
>> >> >> > +grep tunnel_egress_iface_carrier=up | wc -l) -eq 1
>> >> >> > +])
>> >> >> > +
>> >> >> > +OVS_WAIT_UNTIL([
>> >> >> > +    test $(as hv2 ovs-vsctl list interface ovn-hv1-0 | \
>> >> >> > +grep tunnel_egress_iface_carrier=up | wc -l) -eq 1
>> >> >> > +])
>> >> >> > +
>> >> >> >  # Add router lr1
>> >> >> >  OVN_CONTROLLER_EXPECT_HIT(
>> >> >> >      [hv1 hv2], [lflow_run],
>> >> >> > --
>> >> >> > 2.26.2
>> >> >> >
>> >> >> > _______________________________________________
>> >> >> > dev mailing list
>> >> >> > dev@openvswitch.org
>> >> >> > https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>> >> >> _______________________________________________
>> >> >> dev mailing list
>> >> >> dev@openvswitch.org
>> >> >> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>> >> >>
>> >> _______________________________________________
>> >> dev mailing list
>> >> dev@openvswitch.org
>> >> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>> >>
>> _______________________________________________
>> dev mailing list
>> dev@openvswitch.org
>> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>>
Numan Siddique May 26, 2020, 1:03 p.m. UTC | #8
On Sat, May 23, 2020 at 6:42 AM Han Zhou <hzhou@ovn.org> wrote:

> On Fri, May 22, 2020 at 3:07 PM Numan Siddique <numans@ovn.org> wrote:
> >
> >
> >
> > On Sat, May 23, 2020 at 3:10 AM Han Zhou <hzhou@ovn.org> wrote:
> >>
> >> On Fri, May 22, 2020 at 1:51 AM Numan Siddique <numans@ovn.org> wrote:
> >> >
> >> >
> >> >
> >> > On Fri, May 22, 2020 at 1:17 AM Han Zhou <hzhou@ovn.org> wrote:
> >> >>
> >> >> On Thu, May 21, 2020 at 3:49 AM Numan Siddique <numans@ovn.org>
> wrote:
> >> >> >
> >> >> >
> >> >> >
> >> >> > On Thu, May 21, 2020 at 6:57 AM Han Zhou <hzhou@ovn.org> wrote:
> >> >> >>
> >> >> >> Hi Numan, please see my comments below.
> >> >> >
> >> >> >
> >> >> > Thanks for the review. Please see below for a few comments.
> >> >> >
> >> >> >
> >> >> >>
> >> >> >>
> >> >> >> On Wed, May 20, 2020 at 12:41 PM <numans@ovn.org> wrote:
> >> >> >> >
> >> >> >> > From: Numan Siddique <numans@ovn.org>
> >> >> >> >
> >> >> >> > This patch handles SB port binding changes and OVS interface
> changes
> >> >> >> > incrementally in the runtime_data stage which otherwise would
> have
> >> >> >> > resulted in calls to binding_run().
> >> >> >> >
> >> >> >> > Prior to this patch, port binding changes were handled
> differently.
> >> >> >> > The changes were only evaluated to see if they need full
> recompute
> >> >> >> > or they can be ignored.
> >> >> >> >
> >> >> >> > With this patch, the runtime data is updated with the changes
> (both
> >> >> >> > SB port binding and OVS interface) and ports are
> claimed/released in
> >> >> >> > the handlers itself, avoiding the need to trigger recompute of
> >> runtime
> >> >> >> > data stage.
> >> >> >> >
> >> >> >> > Acked-by: Mark Michelson <mmichels@redhat.com>
> >> >> >> > Signed-off-by: Numan Siddique <numans@ovn.org>
> >> >> >> > ---
> >> >> >> >  controller/binding.c        | 906
> >> +++++++++++++++++++++++++++++++-----
> >> >> >> >  controller/binding.h        |   9 +-
> >> >> >> >  controller/ovn-controller.c |  61 ++-
> >> >> >> >  tests/ovn-performance.at    |  13 +
> >> >> >> >  4 files changed, 855 insertions(+), 134 deletions(-)
> >> >> >> >
> >> >> >> > diff --git a/controller/binding.c b/controller/binding.c
> >> >> >> > index 2997fcae8..d5e8e4773 100644
> >> >> >> > --- a/controller/binding.c
> >> >> >> > +++ b/controller/binding.c
> >> >> >> > @@ -360,17 +360,6 @@ destroy_qos_map(struct hmap *qos_map)
> >> >> >> >      hmap_destroy(qos_map);
> >> >> >> >  }
> >> >> >> >
> >> >> >> > -static void
> >> >> >> > -update_local_lport_ids(struct sset *local_lport_ids,
> >> >> >> > -                       const struct sbrec_port_binding
> >> *binding_rec)
> >> >> >> > -{
> >> >> >> > -        char buf[16];
> >> >> >> > -        snprintf(buf, sizeof(buf), "%"PRId64"_%"PRId64,
> >> >> >> > -                 binding_rec->datapath->tunnel_key,
> >> >> >> > -                 binding_rec->tunnel_key);
> >> >> >> > -        sset_add(local_lport_ids, buf);
> >> >> >> > -}
> >> >> >> > -
> >> >> >> >  /*
> >> >> >> >   * Get the encap from the chassis for this port. The interface
> >> >> >> >   * may have an external_ids:encap-ip=<encap-ip> set; if so we
> >> >> >> > @@ -449,10 +438,10 @@ is_network_plugged(const struct
> >> >> sbrec_port_binding
> >> >> >> *binding_rec,
> >> >> >> >  }
> >> >> >> >
> >> >> >> >  static void
> >> >> >> > -consider_localnet_port(const struct sbrec_port_binding
> >> *binding_rec,
> >> >> >> > -                       struct shash *bridge_mappings,
> >> >> >> > -                       struct sset *egress_ifaces,
> >> >> >> > -                       struct hmap *local_datapaths)
> >> >> >> > +update_ld_localnet_port(const struct sbrec_port_binding
> >> *binding_rec,
> >> >> >> > +                        struct shash *bridge_mappings,
> >> >> >> > +                        struct sset *egress_ifaces,
> >> >> >> > +                        struct hmap *local_datapaths)
> >> >> >> >  {
> >> >> >> >      /* Ignore localnet ports for unplugged networks. */
> >> >> >> >      if (!is_network_plugged(binding_rec, bridge_mappings)) {
> >> >> >> > @@ -482,6 +471,28 @@ consider_localnet_port(const struct
> >> >> >> sbrec_port_binding *binding_rec,
> >> >> >> >      ld->localnet_port = binding_rec;
> >> >> >> >  }
> >> >> >> >
> >> >> >> > +static void
> >> >> >> > +update_local_lport_ids(struct sset *local_lport_ids,
> >> >> >> > +                       const struct sbrec_port_binding
> >> *binding_rec)
> >> >> >> > +{
> >> >> >> > +        char buf[16];
> >> >> >> > +        snprintf(buf, sizeof(buf), "%"PRId64"_%"PRId64,
> >> >> >> > +                 binding_rec->datapath->tunnel_key,
> >> >> >> > +                 binding_rec->tunnel_key);
> >> >> >> > +        sset_add(local_lport_ids, buf);
> >> >> >> > +}
> >> >> >> > +
> >> >> >> > +static void
> >> >> >> > +remove_local_lport_ids(const struct sbrec_port_binding
> >> *binding_rec,
> >> >> >> > +                       struct sset *local_lport_ids)
> >> >> >> > +{
> >> >> >> > +        char buf[16];
> >> >> >> > +        snprintf(buf, sizeof(buf), "%"PRId64"_%"PRId64,
> >> >> >> > +                 binding_rec->datapath->tunnel_key,
> >> >> >> > +                 binding_rec->tunnel_key);
> >> >> >> > +        sset_find_and_delete(local_lport_ids, buf);
> >> >> >> > +}
> >> >> >> > +
> >> >> >> >  /* Local bindings. binding.c module binds the logical port
> >> >> (represented
> >> >> >> by
> >> >> >> >   * Port_Binding rows) and sets the 'chassis' column when it is
> sees
> >> >> the
> >> >> >> >   * OVS interface row (of type "" or "internal") with the
> >> >> >> > @@ -599,6 +610,14 @@ local_bindings_destroy(struct shash
> >> >> *local_bindings)
> >> >> >> >      shash_destroy(local_bindings);
> >> >> >> >  }
> >> >> >> >
> >> >> >> > +static
> >> >> >> > +void local_binding_delete(struct shash *local_bindings,
> >> >> >> > +                          struct local_binding *lbinding)
> >> >> >> > +{
> >> >> >> > +    shash_find_and_delete(local_bindings, lbinding->name);
> >> >> >> > +    local_binding_destroy(lbinding);
> >> >> >> > +}
> >> >> >> > +
> >> >> >> >  static void
> >> >> >> >  local_binding_add_child(struct local_binding *lbinding,
> >> >> >> >                          struct local_binding *child)
> >> >> >> > @@ -625,6 +644,7 @@ is_lport_container(const struct
> >> sbrec_port_binding
> >> >> >> *pb)
> >> >> >> >      return !pb->type[0] && pb->parent_port &&
> pb->parent_port[0];
> >> >> >> >  }
> >> >> >> >
> >> >> >> > +/* Corresponds to each Port_Binding.type. */
> >> >> >> >  enum en_lport_type {
> >> >> >> >      LP_UNKNOWN,
> >> >> >> >      LP_VIF,
> >> >> >> > @@ -670,12 +690,20 @@ get_lport_type(const struct
> sbrec_port_binding
> >> >> *pb)
> >> >> >> >      return LP_UNKNOWN;
> >> >> >> >  }
> >> >> >> >
> >> >> >> > -static void
> >> >> >> > +/* Returns false if lport is not claimed due to 'sb_readonly'.
> >> >> >> > + * Returns true otherwise.
> >> >> >> > + */
> >> >> >> > +static bool
> >> >> >> >  claim_lport(const struct sbrec_port_binding *pb,
> >> >> >> >              const struct sbrec_chassis *chassis_rec,
> >> >> >> > -            const struct ovsrec_interface *iface_rec)
> >> >> >> > +            const struct ovsrec_interface *iface_rec,
> >> >> >> > +            bool sb_readonly)
> >> >> >> >  {
> >> >> >> >      if (pb->chassis != chassis_rec) {
> >> >> >> > +        if (sb_readonly) {
> >> >> >> > +            return false;
> >> >> >> > +        }
> >> >> >> > +
> >> >> >> >          if (pb->chassis) {
> >> >> >> >              VLOG_INFO("Changing chassis for lport %s from %s to
> >> %s.",
> >> >> >> >                      pb->logical_port, pb->chassis->name,
> >> >> >> > @@ -694,26 +722,48 @@ claim_lport(const struct
> sbrec_port_binding
> >> *pb,
> >> >> >> >      struct sbrec_encap *encap_rec =
> >> >> >> >          sbrec_get_port_encap(chassis_rec, iface_rec);
> >> >> >> >      if (encap_rec && pb->encap != encap_rec) {
> >> >> >> > +        if (sb_readonly) {
> >> >> >> > +            return false;
> >> >> >> > +        }
> >> >> >> >          sbrec_port_binding_set_encap(pb, encap_rec);
> >> >> >> >      }
> >> >> >> > +
> >> >> >> > +    return true;
> >> >> >> >  }
> >> >> >> >
> >> >> >> > -static void
> >> >> >> > -release_lport(const struct sbrec_port_binding *pb)
> >> >> >> > +/* Returns false if lport is not released due to 'sb_readonly'.
> >> >> >> > + * Returns true otherwise.
> >> >> >> > + */
> >> >> >> > +static bool
> >> >> >> > +release_lport(const struct sbrec_port_binding *pb, bool
> >> sb_readonly)
> >> >> >> >  {
> >> >> >> >      if (!pb) {
> >> >> >> > -        return;
> >> >> >> > +        return true;
> >> >> >> >      }
> >> >> >> >
> >> >> >> > -    VLOG_INFO("Releasing lport %s from this chassis.",
> >> >> pb->logical_port);
> >> >> >> >      if (pb->encap) {
> >> >> >> > +        if (sb_readonly) {
> >> >> >> > +            return false;
> >> >> >> > +        }
> >> >> >> >          sbrec_port_binding_set_encap(pb, NULL);
> >> >> >> >      }
> >> >> >> > -    sbrec_port_binding_set_chassis(pb, NULL);
> >> >> >> > +
> >> >> >> > +    if (pb->chassis) {
> >> >> >> > +        if (sb_readonly) {
> >> >> >> > +            return false;
> >> >> >> > +        }
> >> >> >> > +        sbrec_port_binding_set_chassis(pb, NULL);
> >> >> >> > +    }
> >> >> >> >
> >> >> >> >      if (pb->virtual_parent) {
> >> >> >> > +        if (sb_readonly) {
> >> >> >> > +            return false;
> >> >> >> > +        }
> >> >> >> >          sbrec_port_binding_set_virtual_parent(pb, NULL);
> >> >> >> >      }
> >> >> >> > +
> >> >> >> > +    VLOG_INFO("Releasing lport %s from this chassis.",
> >> >> pb->logical_port);
> >> >> >> > +    return true;
> >> >> >> >  }
> >> >> >> >
> >> >> >> >  static bool
> >> >> >> > @@ -738,7 +788,41 @@ can_bind_on_this_chassis(const struct
> >> >> sbrec_chassis
> >> >> >> *chassis_rec,
> >> >> >> >             || !strcmp(requested_chassis,
> chassis_rec->hostname);
> >> >> >> >  }
> >> >> >> >
> >> >> >> > -static void
> >> >> >> > +static bool
> >> >> >> > +release_local_binding_children(const struct sbrec_chassis
> >> >> *chassis_rec,
> >> >> >> > +                               struct local_binding *lbinding,
> >> >> >> > +                               bool sb_readonly)
> >> >> >> > +{
> >> >> >> > +    struct shash_node *node;
> >> >> >> > +    SHASH_FOR_EACH (node, &lbinding->children) {
> >> >> >> > +        struct local_binding *l = node->data;
> >> >> >> > +        if (is_lbinding_this_chassis(l, chassis_rec)) {
> >> >> >> > +            if (!release_lport(l->pb, sb_readonly)) {
> >> >> >> > +                return false;
> >> >> >> > +            }
> >> >> >> > +        }
> >> >> >> > +    }
> >> >> >> > +
> >> >> >> > +    return true;
> >> >> >> > +}
> >> >> >> > +
> >> >> >> > +static bool
> >> >> >> > +release_local_binding(const struct sbrec_chassis *chassis_rec,
> >> >> >> > +                      struct local_binding *lbinding, bool
> >> >> sb_readonly)
> >> >> >> > +{
> >> >> >> > +    if (!release_local_binding_children(chassis_rec, lbinding,
> >> >> >> > +                                        sb_readonly)) {
> >> >> >> > +        return false;
> >> >> >> > +    }
> >> >> >> > +
> >> >> >> > +    if (is_lbinding_this_chassis(lbinding, chassis_rec)) {
> >> >> >> > +        return release_lport(lbinding->pb, sb_readonly);
> >> >> >> > +    }
> >> >> >> > +
> >> >> >> > +    return true;
> >> >> >> > +}
> >> >> >> > +
> >> >> >> > +static bool
> >> >> >> >  consider_vif_lport_(const struct sbrec_port_binding *pb,
> >> >> >> >                      bool can_bind, const char *vif_chassis,
> >> >> >> >                      struct binding_ctx_in *b_ctx_in,
> >> >> >> > @@ -750,7 +834,10 @@ consider_vif_lport_(const struct
> >> >> sbrec_port_binding
> >> >> >> *pb,
> >> >> >> >      if (lbinding_set) {
> >> >> >> >          if (can_bind) {
> >> >> >> >              /* We can claim the lport. */
> >> >> >> > -            claim_lport(pb, b_ctx_in->chassis_rec,
> >> lbinding->iface);
> >> >> >> > +            if (!claim_lport(pb, b_ctx_in->chassis_rec,
> >> >> lbinding->iface,
> >> >> >> > +                             !b_ctx_in->ovnsb_idl_txn)){
> >> >> >> > +                return false;
> >> >> >> > +            }
> >> >> >> >
> >> >> >> >
> >> >>  add_local_datapath(b_ctx_in->sbrec_datapath_binding_by_key,
> >> >> >> >
> >>  b_ctx_in->sbrec_port_binding_by_datapath,
> >> >> >> > @@ -775,13 +862,14 @@ consider_vif_lport_(const struct
> >> >> sbrec_port_binding
> >> >> >> *pb,
> >> >> >> >      if (pb->chassis == b_ctx_in->chassis_rec) {
> >> >> >> >          /* Release the lport if there is no lbinding. */
> >> >> >> >          if (!lbinding_set || !can_bind) {
> >> >> >> > -            release_lport(pb);
> >> >> >> > +            return release_lport(pb, !b_ctx_in->ovnsb_idl_txn);
> >> >> >> >          }
> >> >> >> >      }
> >> >> >> >
> >> >> >> > +    return true;
> >> >> >> >  }
> >> >> >> >
> >> >> >> > -static void
> >> >> >> > +static bool
> >> >> >> >  consider_vif_lport(const struct sbrec_port_binding *pb,
> >> >> >> >                     struct binding_ctx_in *b_ctx_in,
> >> >> >> >                     struct binding_ctx_out *b_ctx_out,
> >> >> >> > @@ -801,11 +889,11 @@ consider_vif_lport(const struct
> >> >> sbrec_port_binding
> >> >> >> *pb,
> >> >> >> >          lbinding->pb = pb;
> >> >> >> >      }
> >> >> >> >
> >> >> >> > -    consider_vif_lport_(pb, can_bind, vif_chassis, b_ctx_in,
> >> >> >> > -                        b_ctx_out, lbinding, qos_map);
> >> >> >> > +    return consider_vif_lport_(pb, can_bind, vif_chassis,
> b_ctx_in,
> >> >> >> > +                               b_ctx_out, lbinding, qos_map);
> >> >> >> >  }
> >> >> >> >
> >> >> >> > -static void
> >> >> >> > +static bool
> >> >> >> >  consider_container_lport(const struct sbrec_port_binding *pb,
> >> >> >> >                           struct binding_ctx_in *b_ctx_in,
> >> >> >> >                           struct binding_ctx_out *b_ctx_out,
> >> >> >> > @@ -815,7 +903,39 @@ consider_container_lport(const struct
> >> >> >> sbrec_port_binding *pb,
> >> >> >> >      parent_lbinding =
> local_binding_find(b_ctx_out->local_bindings,
> >> >> >> >                                           pb->parent_port);
> >> >> >> >
> >> >> >> > -    if (parent_lbinding && !parent_lbinding->pb) {
> >> >> >> > +    if (!parent_lbinding) {
> >> >> >> > +        /* There is no local_binding for parent port. Create it
> >> >> >> > +         * without OVS interface row. This is the only
> exception
> >> >> >> > +         * for creating the 'struct local_binding' object
> without
> >> >> >> > +         * corresponding OVS interface row.
> >> >> >> > +         *
> >> >> >> > +         * This is required for the following reasons:
> >> >> >> > +         *   - If a logical port P1 is created and then
> >> >> >> > +         *     few container ports - C1, C2, .. are created
> first
> >> by
> >> >> CMS.
> >> >> >> > +         *   - And later when OVS interface row  is created for
> P1,
> >> >> then
> >> >> >> > +         *     we want the these container ports also be
> claimed by
> >> >> the
> >> >> >> > +         *     chassis.
> >> >> >> > +         * */
> >> >> >>
> >> >> >> It seems the assumption is, for all container PBs, their parent
> PBs
> >> >> should
> >> >> >> always have local_binding created, no matter if they are bound to
> >> current
> >> >> >> chassis.
> >> >> >
> >> >> >
> >> >> > That's right.
> >> >> >
> >> >> >>
> >> >> >> However, at least some of the code in this patch would violate
> this
> >> >> >> assumption, e.g. handle_iface_delete() will call
> >> local_binding_delete()
> >> >> and
> >> >> >> delete the parent binding and all the children container bindings.
> >> >> >
> >> >> >
> >> >> > I missed this part. I'll handle it in next version.
> >> >> >
> >> >> >>
> >> >> >> It is also better to document this clearly in the comment of
> struct
> >> >> >> local_binding for what should be expected in the local_bindings.
> >> >> >
> >> >> >
> >> >> > I've added comments for the local_bindings struct. But it looks
> like
> >> it's
> >> >> not
> >> >> > sufficient. I'll add more .
> >> >> >
> >> >> >> My
> >> >> >> understanding is, for each binding_run() or change_handler
> execution,
> >> the
> >> >> >> expected items left in it is:
> >> >> >> 1) For each interface that has iface-id configured.
> >> >> >
> >> >> > Yes.
> >> >> >
> >> >> >>
> >> >> >> 2) For each container PBs and their parent PBs, no matter if they
> are
> >> >> bound
> >> >> >> to this chassis.
> >> >> >
> >> >> >
> >> >> > Yes.
> >> >> >
> >> >> >>
> >> >> >> 3) For some of the non VIF PBs ...
> >> >> >
> >> >> >
> >> >> > Only for the "virtual" PB if it's parent is bound.
> >> >> >
> >> >> >>
> >> >> >> Is this correct?
> >> >> >>
> >> >> >> > +        parent_lbinding = local_binding_create(pb->parent_port,
> >> NULL,
> >> >> >> NULL,
> >> >> >> > +                                               BT_VIF);
> >> >> >> > +        local_binding_add(b_ctx_out->local_bindings,
> >> parent_lbinding);
> >> >> >> > +    }
> >> >> >> > +
> >> >> >> > +    struct local_binding *container_lbinding =
> >> >> >> > +        local_binding_find_child(parent_lbinding,
> >> pb->logical_port);
> >> >> >> > +
> >> >> >> > +    if (!container_lbinding) {
> >> >> >> > +        container_lbinding =
> local_binding_create(pb->logical_port,
> >> >> >> > +
> >> >>  parent_lbinding->iface,
> >> >> >> > +                                                  pb,
> >> BT_CONTAINER);
> >> >> >> > +        local_binding_add_child(parent_lbinding,
> >> container_lbinding);
> >> >> >> > +    } else {
> >> >> >> > +        ovs_assert(container_lbinding->type == BT_CONTAINER);
> >> >> >> > +        container_lbinding->pb = pb;
> >> >> >> > +        container_lbinding->iface = parent_lbinding->iface;
> >> >> >> > +    }
> >> >> >> > +
> >> >> >> > +    if (!parent_lbinding->pb) {
> >> >> >> >          parent_lbinding->pb = lport_lookup_by_name(
> >> >> >> >              b_ctx_in->sbrec_port_binding_by_name,
> pb->parent_port);
> >> >> >> >
> >> >> >> > @@ -824,37 +944,28 @@ consider_container_lport(const struct
> >> >> >> sbrec_port_binding *pb,
> >> >> >> >               * So call consider_vif_lport() to process it
> first. */
> >> >> >> >              consider_vif_lport(parent_lbinding->pb, b_ctx_in,
> >> >> b_ctx_out,
> >> >> >> >                                 parent_lbinding, qos_map);
> >> >> >> > -        }
> >> >> >> > -    }
> >> >> >> > +        } else {
> >> >> >> > +            /* The parent lport doesn't exist. Call
> >> release_lport() to
> >> >> >> > +             * release the container lport, if it was bound
> >> earlier.
> >> >> */
> >> >> >> > +            if (is_lbinding_this_chassis(container_lbinding,
> >> >> >> > +
> b_ctx_in->chassis_rec)) {
> >> >> >> > +               return release_lport(pb,
> !b_ctx_in->ovnsb_idl_txn);
> >> >> >> > +            }
> >> >> >> >
> >> >> >> > -    if (!parent_lbinding || !parent_lbinding->pb) {
> >> >> >> > -        /* Call release_lport, to release the container lport,
> if
> >> >> >> > -         * it was bound earlier. */
> >> >> >> > -        if (pb->chassis == b_ctx_in->chassis_rec) {
> >> >> >> > -            release_lport(pb);
> >> >> >> > +            return true;
> >> >> >> >          }
> >> >> >> > -        return;
> >> >> >> >      }
> >> >> >> >
> >> >> >> > -    struct local_binding *container_lbinding =
> >> >> >> > -        local_binding_find_child(parent_lbinding,
> >> pb->logical_port);
> >> >> >> > -    ovs_assert(!container_lbinding);
> >> >> >> > -
> >> >> >> > -    container_lbinding = local_binding_create(pb->logical_port,
> >> >> >> > -
> >>  parent_lbinding->iface,
> >> >> >> > -                                              pb,
> BT_CONTAINER);
> >> >> >> > -    local_binding_add_child(parent_lbinding,
> container_lbinding);
> >> >> >> > -
> >> >> >> >      const char *vif_chassis =
> >> smap_get(&parent_lbinding->pb->options,
> >> >> >> >                                         "requested-chassis");
> >> >> >> >      bool can_bind =
> can_bind_on_this_chassis(b_ctx_in->chassis_rec,
> >> >> >> >                                               vif_chassis);
> >> >> >> >
> >> >> >> > -    consider_vif_lport_(pb, can_bind, vif_chassis, b_ctx_in,
> >> >> b_ctx_out,
> >> >> >> > -                        container_lbinding, qos_map);
> >> >> >> > +    return consider_vif_lport_(pb, can_bind, vif_chassis,
> b_ctx_in,
> >> >> >> b_ctx_out,
> >> >> >> > +                               container_lbinding, qos_map);
> >> >> >> >  }
> >> >> >> >
> >> >> >> > -static void
> >> >> >> > +static bool
> >> >> >> >  consider_virtual_lport(const struct sbrec_port_binding *pb,
> >> >> >> >                         struct binding_ctx_in *b_ctx_in,
> >> >> >> >                         struct binding_ctx_out *b_ctx_out,
> >> >> >> > @@ -881,11 +992,16 @@ consider_virtual_lport(const struct
> >> >> >> sbrec_port_binding *pb,
> >> >> >> >      if (is_lbinding_this_chassis(parent_lbinding,
> >> >> >> b_ctx_in->chassis_rec)) {
> >> >> >> >          virtual_lbinding =
> >> >> >> >              local_binding_find_child(parent_lbinding,
> >> >> pb->logical_port);
> >> >> >> > -        ovs_assert(!virtual_lbinding);
> >> >> >> > -        virtual_lbinding =
> local_binding_create(pb->logical_port,
> >> >> >> > -
> >> >>  parent_lbinding->iface,
> >> >> >> > -                                                pb,
> BT_VIRTUAL);
> >> >> >> > -        local_binding_add_child(parent_lbinding,
> virtual_lbinding);
> >> >> >> > +        if (!virtual_lbinding) {
> >> >> >> > +            virtual_lbinding =
> >> local_binding_create(pb->logical_port,
> >> >> >> > +
> >> >> >>  parent_lbinding->iface,
> >> >> >> > +                                                    pb,
> >> BT_VIRTUAL);
> >> >> >> > +            local_binding_add_child(parent_lbinding,
> >> >> virtual_lbinding);
> >> >> >> > +        } else {
> >> >> >> > +            ovs_assert(virtual_lbinding->type == BT_VIRTUAL);
> >> >> >> > +            virtual_lbinding->pb = pb;
> >> >> >> > +            virtual_lbinding->iface = parent_lbinding->iface;
> >> >> >> > +        }
> >> >> >> >      }
> >> >> >> >
> >> >> >> >      return consider_vif_lport_(pb, true, NULL, b_ctx_in,
> b_ctx_out,
> >> >> >> > @@ -895,14 +1011,13 @@ consider_virtual_lport(const struct
> >> >> >> sbrec_port_binding *pb,
> >> >> >> >  /* Considers either claiming the lport or releasing the lport
> >> >> >> >   * for non VIF lports.
> >> >> >> >   */
> >> >> >> > -static void
> >> >> >> > +static bool
> >> >> >> >  consider_nonvif_lport_(const struct sbrec_port_binding *pb,
> >> >> >> >                         bool our_chassis,
> >> >> >> >                         bool has_local_l3gateway,
> >> >> >> >                         struct binding_ctx_in *b_ctx_in,
> >> >> >> >                         struct binding_ctx_out *b_ctx_out)
> >> >> >> >  {
> >> >> >> > -    ovs_assert(b_ctx_in->ovnsb_idl_txn);
> >> >> >> >      if (our_chassis) {
> >> >> >> >          sset_add(b_ctx_out->local_lports, pb->logical_port);
> >> >> >> >
>  add_local_datapath(b_ctx_in->sbrec_datapath_binding_by_key,
> >> >> >> > @@ -912,13 +1027,16 @@ consider_nonvif_lport_(const struct
> >> >> >> sbrec_port_binding *pb,
> >> >> >> >                             b_ctx_out->local_datapaths);
> >> >> >> >
> >> >> >> >          update_local_lport_ids(b_ctx_out->local_lport_ids, pb);
> >> >> >> > -        claim_lport(pb, b_ctx_in->chassis_rec, NULL);
> >> >> >> > +        return claim_lport(pb, b_ctx_in->chassis_rec, NULL,
> >> >> >> > +                           !b_ctx_in->ovnsb_idl_txn);
> >> >> >> >      } else if (pb->chassis == b_ctx_in->chassis_rec) {
> >> >> >> > -        release_lport(pb);
> >> >> >> > +        return release_lport(pb, !b_ctx_in->ovnsb_idl_txn);
> >> >> >> >      }
> >> >> >> > +
> >> >> >> > +    return true;
> >> >> >> >  }
> >> >> >> >
> >> >> >> > -static void
> >> >> >> > +static bool
> >> >> >> >  consider_l2gw_lport(const struct sbrec_port_binding *pb,
> >> >> >> >                      struct binding_ctx_in *b_ctx_in,
> >> >> >> >                      struct binding_ctx_out *b_ctx_out)
> >> >> >> > @@ -927,10 +1045,10 @@ consider_l2gw_lport(const struct
> >> >> >> sbrec_port_binding *pb,
> >> >> >> >      bool our_chassis = chassis_id && !strcmp(chassis_id,
> >> >> >> >
> >> >> >> b_ctx_in->chassis_rec->name);
> >> >> >> >
> >> >> >> > -    consider_nonvif_lport_(pb, our_chassis, false, b_ctx_in,
> >> >> b_ctx_out);
> >> >> >> > +    return consider_nonvif_lport_(pb, our_chassis, false,
> b_ctx_in,
> >> >> >> b_ctx_out);
> >> >> >> >  }
> >> >> >> >
> >> >> >> > -static void
> >> >> >> > +static bool
> >> >> >> >  consider_l3gw_lport(const struct sbrec_port_binding *pb,
> >> >> >> >                      struct binding_ctx_in *b_ctx_in,
> >> >> >> >                      struct binding_ctx_out *b_ctx_out)
> >> >> >> > @@ -939,7 +1057,7 @@ consider_l3gw_lport(const struct
> >> >> sbrec_port_binding
> >> >> >> *pb,
> >> >> >> >      bool our_chassis = chassis_id && !strcmp(chassis_id,
> >> >> >> >
> >> >> >> b_ctx_in->chassis_rec->name);
> >> >> >> >
> >> >> >> > -    consider_nonvif_lport_(pb, our_chassis, true, b_ctx_in,
> >> >> b_ctx_out);
> >> >> >> > +    return consider_nonvif_lport_(pb, our_chassis, true,
> b_ctx_in,
> >> >> >> b_ctx_out);
> >> >> >> >  }
> >> >> >> >
> >> >> >> >  static void
> >> >> >> > @@ -958,7 +1076,7 @@ consider_localnet_lport(const struct
> >> >> >> sbrec_port_binding *pb,
> >> >> >> >      update_local_lport_ids(b_ctx_out->local_lport_ids, pb);
> >> >> >> >  }
> >> >> >> >
> >> >> >> > -static void
> >> >> >> > +static bool
> >> >> >> >  consider_ha_lport(const struct sbrec_port_binding *pb,
> >> >> >> >                    struct binding_ctx_in *b_ctx_in,
> >> >> >> >                    struct binding_ctx_out *b_ctx_out)
> >> >> >> > @@ -986,18 +1104,18 @@ consider_ha_lport(const struct
> >> >> sbrec_port_binding
> >> >> >> *pb,
> >> >> >> >                             b_ctx_out->local_datapaths);
> >> >> >> >      }
> >> >> >> >
> >> >> >> > -    consider_nonvif_lport_(pb, our_chassis, false, b_ctx_in,
> >> >> b_ctx_out);
> >> >> >> > +    return consider_nonvif_lport_(pb, our_chassis, false,
> b_ctx_in,
> >> >> >> b_ctx_out);
> >> >> >> >  }
> >> >> >> >
> >> >> >> > -static void
> >> >> >> > +static bool
> >> >> >> >  consider_cr_lport(const struct sbrec_port_binding *pb,
> >> >> >> >                    struct binding_ctx_in *b_ctx_in,
> >> >> >> >                    struct binding_ctx_out *b_ctx_out)
> >> >> >> >  {
> >> >> >> > -    consider_ha_lport(pb, b_ctx_in, b_ctx_out);
> >> >> >> > +    return consider_ha_lport(pb, b_ctx_in, b_ctx_out);
> >> >> >> >  }
> >> >> >> >
> >> >> >> > -static void
> >> >> >> > +static bool
> >> >> >> >  consider_external_lport(const struct sbrec_port_binding *pb,
> >> >> >> >                          struct binding_ctx_in *b_ctx_in,
> >> >> >> >                          struct binding_ctx_out *b_ctx_out)
> >> >> >> > @@ -1050,6 +1168,8 @@ build_local_bindings(struct binding_ctx_in
> >> >> >> *b_ctx_in,
> >> >> >> >                  }
> >> >> >> >
> >> >> >> >                  sset_add(b_ctx_out->local_lports, iface_id);
> >> >> >> > +                smap_replace(b_ctx_out->local_iface_ids,
> >> >> iface_rec->name,
> >> >> >> > +                             iface_id);
> >> >> >> >              }
> >> >> >> >
> >> >> >> >              /* Check if this is a tunnel interface. */
> >> >> >> > @@ -1165,9 +1285,9 @@ binding_run(struct binding_ctx_in
> *b_ctx_in,
> >> >> struct
> >> >> >> binding_ctx_out *b_ctx_out)
> >> >> >> >       * corresponding local datapath accordingly. */
> >> >> >> >      struct localnet_lport *lnet_lport;
> >> >> >> >      LIST_FOR_EACH_POP (lnet_lport, list_node, &localnet_lports)
> {
> >> >> >> > -        consider_localnet_port(lnet_lport->pb,
> &bridge_mappings,
> >> >> >> > -                               b_ctx_out->egress_ifaces,
> >> >> >> > -                               b_ctx_out->local_datapaths);
> >> >> >> > +        update_ld_localnet_port(lnet_lport->pb,
> &bridge_mappings,
> >> >> >> > +                                b_ctx_out->egress_ifaces,
> >> >> >> > +                                b_ctx_out->local_datapaths);
> >> >> >> >          free(lnet_lport);
> >> >> >> >      }
> >> >> >> >
> >> >> >> > @@ -1185,95 +1305,625 @@ binding_run(struct binding_ctx_in
> >> *b_ctx_in,
> >> >> >> struct binding_ctx_out *b_ctx_out)
> >> >> >> >      destroy_qos_map(&qos_map);
> >> >> >> >  }
> >> >> >> >
> >> >> >> > -/* Returns true if port-binding changes potentially require
> flow
> >> >> changes
> >> >> >> on
> >> >> >> > - * the current chassis. Returns false if we are sure there is
> no
> >> >> impact.
> >> >> >> */
> >> >> >> > +/* Returns true if the database is all cleaned up, false if
> more
> >> work
> >> >> is
> >> >> >> > + * required. */
> >> >> >> >  bool
> >> >> >> > -binding_evaluate_port_binding_changes(struct binding_ctx_in
> >> *b_ctx_in,
> >> >> >> > -                                      struct binding_ctx_out
> >> >> *b_ctx_out)
> >> >> >> > +binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn,
> >> >> >> > +                const struct sbrec_port_binding_table
> >> >> >> *port_binding_table,
> >> >> >> > +                const struct sbrec_chassis *chassis_rec)
> >> >> >> >  {
> >> >> >> > -    if (!b_ctx_in->chassis_rec) {
> >> >> >> > +    if (!ovnsb_idl_txn) {
> >> >> >> > +        return false;
> >> >> >> > +    }
> >> >> >> > +    if (!chassis_rec) {
> >> >> >> >          return true;
> >> >> >> >      }
> >> >> >> >
> >> >> >> > -    bool changed = false;
> >> >> >> > -
> >> >> >> >      const struct sbrec_port_binding *binding_rec;
> >> >> >> > -    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (binding_rec,
> >> >> >> > -
> >> >> >> b_ctx_in->port_binding_table) {
> >> >> >> > -        /* XXX: currently OVSDB change tracking doesn't support
> >> >> getting
> >> >> >> old
> >> >> >> > -         * data when the operation is update, so if a
> port-binding
> >> >> moved
> >> >> >> from
> >> >> >> > -         * this chassis to another, there is no easy way to
> find
> >> out
> >> >> the
> >> >> >> > -         * change. To workaround this problem, we just makes
> sure
> >> if
> >> >> >> > -         * any port *related to* this chassis has any change,
> then
> >> >> >> trigger
> >> >> >> > -         * recompute.
> >> >> >> > -         *
> >> >> >> > -         * - If a regular VIF is unbound from this chassis, the
> >> local
> >> >> >> ovsdb
> >> >> >> > -         *   interface table will be updated, which will
> trigger
> >> >> >> recompute.
> >> >> >> > -         *
> >> >> >> > -         * - If the port is not a regular VIF, always trigger
> >> >> recompute.
> >> >> >> */
> >> >> >> > -        if (binding_rec->chassis == b_ctx_in->chassis_rec) {
> >> >> >> > -            changed = true;
> >> >> >> > +    bool any_changes = false;
> >> >> >> > +    SBREC_PORT_BINDING_TABLE_FOR_EACH (binding_rec,
> >> >> port_binding_table) {
> >> >> >> > +        if (binding_rec->chassis == chassis_rec) {
> >> >> >> > +            if (binding_rec->encap) {
> >> >> >> > +                sbrec_port_binding_set_encap(binding_rec,
> NULL);
> >> >> >> > +            }
> >> >> >> > +            sbrec_port_binding_set_chassis(binding_rec, NULL);
> >> >> >> > +            any_changes = true;
> >> >> >> > +        }
> >> >> >> > +    }
> >> >> >> > +
> >> >> >> > +    if (any_changes) {
> >> >> >> > +        ovsdb_idl_txn_add_comment(
> >> >> >> > +            ovnsb_idl_txn,
> >> >> >> > +            "ovn-controller: removing all port bindings for
> '%s'",
> >> >> >> > +            chassis_rec->name);
> >> >> >> > +    }
> >> >> >> > +
> >> >> >> > +    return !any_changes;
> >> >> >> > +}
> >> >> >> > +
> >> >> >> > +static void
> >> >> >> > +add_local_datapath_peer_port(const struct sbrec_port_binding
> *pb,
> >> >> >> > +                             struct binding_ctx_in *b_ctx_in,
> >> >> >> > +                             struct binding_ctx_out *b_ctx_out,
> >> >> >> > +                             struct local_datapath *ld)
> >> >> >> > +{
> >> >> >> > +    const char *peer_name = smap_get(&pb->options, "peer");
> >> >> >> > +    if (strcmp(pb->type, "patch") || !peer_name) {
> >> >> >> > +        return;
> >> >> >> > +    }
> >> >> >> > +
> >> >> >> > +    const struct sbrec_port_binding *peer;
> >> >> >> > +    peer =
> >> lport_lookup_by_name(b_ctx_in->sbrec_port_binding_by_name,
> >> >> >> > +                                peer_name);
> >> >> >> > +
> >> >> >> > +    if (!peer || !peer->datapath) {
> >> >> >> > +        return;
> >> >> >> > +    }
> >> >> >> > +
> >> >> >> > +    bool present = false;
> >> >> >> > +    for (size_t i = 0; i < ld->n_peer_ports; i++) {
> >> >> >> > +        if (ld->peer_ports[i].local == pb) {
> >> >> >> > +            present = true;
> >> >> >> >              break;
> >> >> >> >          }
> >> >> >> > +    }
> >> >> >> >
> >> >> >> > -        if (!strcmp(binding_rec->type, "remote")) {
> >> >> >> > -            continue;
> >> >> >> > +    if (!present) {
> >> >> >> > +        ld->n_peer_ports++;
> >> >> >> > +        if (ld->n_peer_ports > ld->n_allocated_peer_ports) {
> >> >> >> > +            ld->peer_ports =
> >> >> >> > +                x2nrealloc(ld->peer_ports,
> >> >> >> > +                           &ld->n_allocated_peer_ports,
> >> >> >> > +                           sizeof *ld->peer_ports);
> >> >> >> > +        }
> >> >> >> > +        ld->peer_ports[ld->n_peer_ports - 1].local = pb;
> >> >> >> > +        ld->peer_ports[ld->n_peer_ports - 1].remote = peer;
> >> >> >> > +    }
> >> >> >> > +
> >> >> >> > +    struct local_datapath *peer_ld =
> >> >> >> > +        get_local_datapath(b_ctx_out->local_datapaths,
> >> >> >> > +                           peer->datapath->tunnel_key);
> >> >> >> > +    if (!peer_ld) {
> >> >> >> > +
> >>  add_local_datapath__(b_ctx_in->sbrec_datapath_binding_by_key,
> >> >> >> > +
> >> b_ctx_in->sbrec_port_binding_by_datapath,
> >> >> >> > +
> b_ctx_in->sbrec_port_binding_by_name,
> >> >> >> > +                             peer->datapath, false,
> >> >> >> > +                             1, b_ctx_out->local_datapaths);
> >> >> >> > +        return;
> >> >> >> > +    }
> >> >> >> > +
> >> >> >> > +    for (size_t i = 0; i < peer_ld->n_peer_ports; i++) {
> >> >> >> > +        if (peer_ld->peer_ports[i].local == peer) {
> >> >> >> > +            return;
> >> >> >> >          }
> >> >> >> > +    }
> >> >> >> >
> >> >> >> > -        if (strcmp(binding_rec->type, "")) {
> >> >> >> > -            changed = true;
> >> >> >> > +    peer_ld->n_peer_ports++;
> >> >> >> > +    if (peer_ld->n_peer_ports >
> peer_ld->n_allocated_peer_ports) {
> >> >> >> > +        peer_ld->peer_ports =
> >> >> >> > +            x2nrealloc(peer_ld->peer_ports,
> >> >> >> > +                        &peer_ld->n_allocated_peer_ports,
> >> >> >> > +                        sizeof *peer_ld->peer_ports);
> >> >> >> > +    }
> >> >> >> > +    peer_ld->peer_ports[peer_ld->n_peer_ports - 1].local =
> peer;
> >> >> >> > +    peer_ld->peer_ports[peer_ld->n_peer_ports - 1].remote = pb;
> >> >> >> > +}
> >> >> >> > +
> >> >> >> > +static void
> >> >> >> > +remove_local_datapath_peer_port(const struct sbrec_port_binding
> >> *pb,
> >> >> >> > +                                struct local_datapath *ld,
> >> >> >> > +                                struct hmap *local_datapaths)
> >> >> >> > +{
> >> >> >> > +    size_t i = 0;
> >> >> >> > +    for (i = 0; i < ld->n_peer_ports; i++) {
> >> >> >> > +        if (ld->peer_ports[i].local == pb) {
> >> >> >> >              break;
> >> >> >> >          }
> >> >> >> > +    }
> >> >> >> > +
> >> >> >> > +    if (i == ld->n_peer_ports) {
> >> >> >> > +        return;
> >> >> >> > +    }
> >> >> >> > +
> >> >> >> > +    const struct sbrec_port_binding *peer =
> >> ld->peer_ports[i].remote;
> >> >> >> >
> >> >> >> > -        struct local_binding *lbinding = NULL;
> >> >> >> > -        if (!binding_rec->parent_port ||
> >> >> !binding_rec->parent_port[0]) {
> >> >> >> > -            lbinding =
> >> local_binding_find(b_ctx_out->local_bindings,
> >> >> >> > -
> >>  binding_rec->logical_port);
> >> >> >> > +    /* Possible improvement: We can shrint the allocated peer
> ports
> >> >> >> > +     * if (ld->n_peer_ports < ld->n_allocated_peer_ports / 2).
> >> >> >> > +     */
> >> >> >> > +    ld->peer_ports[i].local = ld->peer_ports[ld->n_peer_ports -
> >> >> 1].local;
> >> >> >> > +    ld->peer_ports[i].remote = ld->peer_ports[ld->n_peer_ports
> -
> >> >> >> 1].remote;
> >> >> >> > +    ld->n_peer_ports--;
> >> >> >> > +
> >> >> >> > +    struct local_datapath *peer_ld =
> >> >> >> > +        get_local_datapath(local_datapaths,
> >> >> peer->datapath->tunnel_key);
> >> >> >> > +    if (peer_ld) {
> >> >> >> > +        remove_local_datapath_peer_port(peer, peer_ld,
> >> >> local_datapaths);
> >> >> >>
> >> >> >> Sorry that I don't understand this part. peer's peer is the "pb"
> that
> >> is
> >> >> >> passed in the parameter of the current function call, so this
> >> recursive
> >> >> >> call would only try to remove the "pb", which is already removed
> >> (before
> >> >> >> calling this function). So I think it is useless.
> >> >> >
> >> >> >
> >> >> > Let's say there is sw0-lr0 of sw0 connected to the peer lr0-sw0 of
> lr0.
> >> >> >
> >> >> > When this function is called for sw0-lr0, it removes the peer ports
> >> >> sw0-lr0 from
> >> >> > the 'sw0' ld and then it calls recursively for lr0.
> >> >> >
> >> >> > Yes you're right. It will again call for sw0-lr0 of sw0. But that
> would
> >> >> be a no-op as
> >> >> > the for loop in the beginning cannot find sw0-lr0  in 'sw0' ld..
> Even
> >> >> though it
> >> >> > is useless are there any issues with it ? Since its recursion and
> it
> >> has
> >> >> proper return
> >> >> > conditions, I think it should be fine.
> >> >> >
> >> >> > Another option is duplicating the code for removing the peer.
> >> >> >
> >> >>
> >> >> OK, I thought it is useless, but it seems needed.
> >> >>
> >> >> >
> >> >> >>
> >> >> >> > +    }
> >> >> >> > +}
> >> >> >> > +
> >> >> >> > +static void
> >> >> >> > +remove_pb_from_local_datapath(const struct sbrec_port_binding
> *pb,
> >> >> >> > +                              const struct sbrec_chassis
> >> *chassis_rec,
> >> >> >> > +                              struct binding_ctx_out
> *b_ctx_out,
> >> >> >> > +                              struct local_datapath *ld)
> >> >> >> > +{
> >> >> >> > +    remove_local_lport_ids(pb, b_ctx_out->local_lport_ids);
> >> >> >> > +    if (!strcmp(pb->type, "patch") ||
> >> >> >> > +        !strcmp(pb->type, "l3gateway")) {
> >> >> >> > +        remove_local_datapath_peer_port(pb, ld,
> >> >> >> b_ctx_out->local_datapaths);
> >> >> >> > +    } else if (!strcmp(pb->type, "localnet")) {
> >> >> >> > +        if (ld->localnet_port &&
> >> >> !strcmp(ld->localnet_port->logical_port,
> >> >> >> > +                                         pb->logical_port)) {
> >> >> >> > +            ld->localnet_port = NULL;
> >> >> >> > +        }
> >> >> >> > +    } else if (!strcmp(pb->type, "l3gateway")) {
> >> >> >> > +        const char *chassis_id = smap_get(&pb->options,
> >> >> >> > +                                          "l3gateway-chassis");
> >> >> >> > +        if (chassis_id && !strcmp(chassis_id,
> chassis_rec->name)) {
> >> >> >> > +            ld->has_local_l3gateway = false;
> >> >> >> > +        }
> >> >> >> > +    }
> >> >> >>
> >> >> >> I think we need to remove ld from the local_datapaths if the
> current
> >> pb
> >> >> is
> >> >> >> the only reason for that ld to be exist on this chassis. Also, all
> the
> >> >> >> related lds connected by patch ports should be removed as well, if
> >> there
> >> >> >> are no other pbs causing them to be added to this chassis. This is
> the
> >> >> >> reverse of add_local_datapath__(). It doesn't seem to be easy
> without
> >> >> going
> >> >> >> through all local_lbindings. Maybe this can be done in
> >> >> >> binding_handle_ovs_interface_changes() and
> >> >> >> binding_handle_port_binding_changes() out of the loop.
> >> >> >>
> >> >> >
> >> >> > I left this unaddressed intentionally as I think it will complicate
> the
> >> >> code.
> >> >> >
> >> >> > Patch 3 of this series handles I-P for datapath and when a datapath
> is
> >> >> deleted,
> >> >> > it removes the datapath from local_datapaths if it is present.
> >> >> > I think this should be Ok as we will eventually free it. If we
> really
> >> >> want to address this,
> >> >> > I'm fine with it but I'd prefer a follow up patch to do this.
> >> >> >
> >> >> OK, maybe a comment can be added to make it clear for future
> improvement.
> >> >> This changes the behavior of current implementation. Now unrelated
> >> >> datapaths are removed when certain ports are unbound, and the flows
> in
> >> the
> >> >> HV can be largely reduced, but with this change it may not reduce any
> >> more
> >> >> until a recompute is triggered. It is not a big problem I think.
> >> >
> >> >
> >> > Sure. I'll add a comment about it. I was also thinking of adding the
> same
> >> in TODO.rst.
> >> > But it looks like TODO.rst needs some cleaning first.
> >> >
> >> >>
> >> >> >
> >> >> >>
> >> >> >> > +}
> >> >> >> > +
> >> >> >> > +static bool
> >> >> >> > +handle_iface_add(const struct ovsrec_interface *iface_rec,
> >> >> >> > +                 const char *iface_id,
> >> >> >> > +                 struct binding_ctx_in *b_ctx_in,
> >> >> >> > +                 struct binding_ctx_out *b_ctx_out,
> >> >> >> > +                 struct hmap *qos_map)
> >> >> >> > +{
> >> >> >> > +    sset_add(b_ctx_out->local_lports, iface_id);
> >> >> >> > +    smap_replace(b_ctx_out->local_iface_ids, iface_rec->name,
> >> >> iface_id);
> >> >> >> > +
> >> >> >> > +    struct local_binding *lbinding =
> >> >> >> > +        local_binding_find(b_ctx_out->local_bindings,
> iface_id);
> >> >> >> > +
> >> >> >> > +    if (!lbinding) {
> >> >> >> > +        lbinding = local_binding_create(iface_id, iface_rec,
> NULL,
> >> >> >> BT_VIF);
> >> >> >> > +        local_binding_add(b_ctx_out->local_bindings, lbinding);
> >> >> >> > +    } else {
> >> >> >> > +        lbinding->iface = iface_rec;
> >> >> >> > +    }
> >> >> >> > +
> >> >> >> > +    if (!lbinding->pb || strcmp(lbinding->name,
> >> >> >> lbinding->pb->logical_port)) {
> >> >> >> > +        lbinding->pb = lport_lookup_by_name(
> >> >> >> > +            b_ctx_in->sbrec_port_binding_by_name,
> lbinding->name);
> >> >> >> > +        if (lbinding->pb && !strcmp(lbinding->pb->type,
> >> "virtual")) {
> >> >> >>
> >> >> >> Since we never bind "virtual" ports, this should be a
> >> misconfiguration,
> >> >> and
> >> >> >> it is better to log a warning message to be more clear.
> >> >> >>
> >> >> >> > +            lbinding->pb = NULL;
> >> >> >> > +        }
> >> >> >> > +    }
> >> >> >> > +
> >> >> >> > +    if (lbinding->pb) {
> >> >> >> > +        if (!consider_vif_lport(lbinding->pb, b_ctx_in,
> b_ctx_out,
> >> >> >> > +                                lbinding, qos_map)) {
> >> >> >> > +            return false;
> >> >> >> > +        }
> >> >> >> > +    }
> >> >> >> > +
> >> >> >> > +    /* Update the child local_binding's iface (if any children)
> and
> >> >> try
> >> >> >> to
> >> >> >> > +     *  claim the container lbindings. */
> >> >> >> > +    struct shash_node *node;
> >> >> >> > +    SHASH_FOR_EACH (node, &lbinding->children) {
> >> >> >> > +        struct local_binding *child = node->data;
> >> >> >> > +        child->iface = iface_rec;
> >> >> >> > +        if (child->type == BT_CONTAINER) {
> >> >> >> > +            if (!consider_container_lport(child->pb, b_ctx_in,
> >> >> b_ctx_out,
> >> >> >> > +                                          qos_map)) {
> >> >> >> > +                return false;
> >> >> >> > +            }
> >> >> >> > +        }
> >> >> >> > +    }
> >> >> >> > +
> >> >> >> > +    return true;
> >> >> >> > +}
> >> >> >> > +
> >> >> >> > +static bool
> >> >> >> > +handle_iface_delete(const char *lport_name, const char
> *iface_name,
> >> >> >> > +                    struct binding_ctx_in *b_ctx_in,
> >> >> >> > +                    struct binding_ctx_out *b_ctx_out, bool
> >> *changed)
> >> >> >> > +{
> >> >> >> > +    struct local_binding *lbinding;
> >> >> >> > +    lbinding = local_binding_find(b_ctx_out->local_bindings,
> >> >> >> > +                                  lport_name);
> >> >> >> > +    if (lbinding && lbinding->pb &&
> >> >> >> > +        lbinding->pb->chassis == b_ctx_in->chassis_rec) {
> >> >> >> > +
> >> >> >> > +        if (!release_local_binding(b_ctx_in->chassis_rec,
> lbinding,
> >> >> >> > +                                   !b_ctx_in->ovnsb_idl_txn)) {
> >> >> >> > +            return false;
> >> >> >> > +        }
> >> >> >> > +
> >> >> >> > +        struct local_datapath *ld =
> >> >> >> > +            get_local_datapath(b_ctx_out->local_datapaths,
> >> >> >> > +
> >>  lbinding->pb->datapath->tunnel_key);
> >> >> >> > +        if (ld) {
> >> >> >> > +            remove_pb_from_local_datapath(lbinding->pb,
> >> >> >> > +
>  b_ctx_in->chassis_rec,
> >> >> >> > +                                            b_ctx_out, ld);
> >> >> >> > +        }
> >> >> >> > +        local_binding_delete(b_ctx_out->local_bindings,
> lbinding);
> >> >> >> > +        *changed = true;
> >> >> >> > +    }
> >> >> >> > +
> >> >> >> > +    sset_find_and_delete(b_ctx_out->local_lports, lport_name);
> >> >> >> > +    smap_remove(b_ctx_out->local_iface_ids, iface_name);
> >> >> >> > +
> >> >> >> > +    return true;
> >> >> >> > +}
> >> >> >> > +
> >> >> >> > +static bool
> >> >> >> > +is_iface_vif(const struct ovsrec_interface *iface_rec)
> >> >> >> > +{
> >> >> >> > +    if (iface_rec->type && iface_rec->type[0] &&
> >> >> >> > +        strcmp(iface_rec->type, "internal")) {
> >> >> >> > +        return false;
> >> >> >> > +    }
> >> >> >> > +
> >> >> >> > +    return true;
> >> >> >> > +}
> >> >> >> > +
> >> >> >> > +/* Returns true if the ovs interface changes were handled
> >> >> successfully,
> >> >> >> > + * false otherwise.
> >> >> >> > + */
> >> >> >> > +bool
> >> >> >> > +binding_handle_ovs_interface_changes(struct binding_ctx_in
> >> *b_ctx_in,
> >> >> >> > +                                     struct binding_ctx_out
> >> >> *b_ctx_out,
> >> >> >> > +                                     bool *changed)
> >> >> >> > +{
> >> >> >> > +    if (!b_ctx_in->chassis_rec) {
> >> >> >> > +        return false;
> >> >> >> > +    }
> >> >> >> > +
> >> >> >> > +    bool handled = true;
> >> >> >> > +    *changed = false;
> >> >> >> > +
> >> >> >> > +    /* Run the tracked interfaces loop twice. One to handle
> deleted
> >> >> >> > +     * changes. And another to handle add/update changes.
> >> >> >> > +     * This will ensure correctness.
> >> >> >> > +     */
> >> >> >> > +    const struct ovsrec_interface *iface_rec;
> >> >> >> > +    OVSREC_INTERFACE_TABLE_FOR_EACH_TRACKED (iface_rec,
> >> >> >> > +
> >> b_ctx_in->iface_table) {
> >> >> >> > +        if (!is_iface_vif(iface_rec)) {
> >> >> >> > +            /* Right now are not handling ovs_interface changes
> of
> >> >> >> > +             * other types. This can be enhanced to handle of
> >> >> >> > +             * types - patch and tunnel. */
> >> >> >> > +            handled = false;
> >> >> >> > +            break;
> >> >> >> > +        }
> >> >> >> > +
> >> >> >> > +        const char *iface_id =
> smap_get(&iface_rec->external_ids,
> >> >> >> "iface-id");
> >> >> >> > +        const char *old_iface_id =
> >> >> smap_get(b_ctx_out->local_iface_ids,
> >> >> >> > +                                            iface_rec->name);
> >> >> >> > +        const char *cleared_iface_id = NULL;
> >> >> >> > +        if (!ovsrec_interface_is_deleted(iface_rec)) {
> >> >> >> > +            if (iface_id) {
> >> >> >> > +                /* Check if iface_id is changed. If so we need
> to
> >> >> >> > +                 * release the old port binding and associate
> this
> >> >> >> > +                 * inteface to new port binding. */
> >> >> >> > +                if (old_iface_id && strcmp(iface_id,
> >> old_iface_id)) {
> >> >> >> > +                    cleared_iface_id = old_iface_id;
> >> >> >> > +                }
> >> >> >> > +            } else if (old_iface_id) {
> >> >> >> > +                cleared_iface_id = old_iface_id;
> >> >> >> > +            }
> >> >> >> >          } else {
> >> >> >> > -            lbinding =
> >> local_binding_find(b_ctx_out->local_bindings,
> >> >> >> > -
> >>  binding_rec->parent_port);
> >> >> >> > +            cleared_iface_id = iface_id;
> >> >> >> > +            iface_id = NULL;
> >> >> >> >          }
> >> >> >> >
> >> >> >> > -        if (lbinding) {
> >> >> >> > -            changed = true;
> >> >> >> > +        if (cleared_iface_id) {
> >> >> >> > +            handled = handle_iface_delete(cleared_iface_id,
> >> >> >> iface_rec->name,
> >> >> >> > +                                          b_ctx_in, b_ctx_out,
> >> >> changed);
> >> >> >> > +        }
> >> >> >> > +
> >> >> >> > +        if (!handled) {
> >> >> >> >              break;
> >> >> >> >          }
> >> >> >> >      }
> >> >> >> >
> >> >> >> > -    return changed;
> >> >> >> > +    if (!handled) {
> >> >> >> > +        return false;
> >> >> >> > +    }
> >> >> >> > +
> >> >> >> > +    struct hmap qos_map = HMAP_INITIALIZER(&qos_map);
> >> >> >> > +    struct hmap *qos_map_ptr =
> >> >> >> > +        sset_is_empty(b_ctx_out->egress_ifaces) ? NULL :
> &qos_map;
> >> >> >> > +
> >> >> >> > +    OVSREC_INTERFACE_TABLE_FOR_EACH_TRACKED (iface_rec,
> >> >> >> > +
> >> b_ctx_in->iface_table) {
> >> >> >> > +        /* Loop to handle create and update changes only. */
> >> >> >>
> >> >> >> Thanks for splitting the loops, but here are still some problems.
> >> >> >>
> >> >> >> 1. The first loop handled: a) interface deletion. b) interface
> update
> >> >> with
> >> >> >> old iface-id cleared. So in this second loop it should handle all
> the
> >> >> rest
> >> >> >> cases: a) interface add. b) interface update without iface-id
> change.
> >> It
> >> >> >> seems b) is treated as just interface add, which may be wrong: if
> >> >> iface-id
> >> >> >> didn't change but some other fields of the interface record is
> >> changed,
> >> >> it
> >> >> >> is not handled properly.
> >> >> >
> >> >> >
> >> >> > If other fields change, what should we handle ? Can ofport change ?
> >> >> > I'll see if ofport can be changed and what happens when it changes.
> >> >> >
> >> >> > For other fields like name, mtu etc do we need to handle anything ?
> >> >> > I think we only care about the external_ids:iface-id and the
> ofport.
> >> >> > The function handle_iface_add() even if it is called for other
> changes,
> >> >> > it will be a no-op.
> >> >> >
> >> >> > Also from the tracking we can't figure out what fields change right
> ?
> >> >> >
> >> >> >
> >> >> Yes, I think it is hard to check with fields are changed and handle
> it
> >> >> accordingly. That's why I suggest to make it simple: if there is an
> >> update,
> >> >> always handle it like a deletion and then creation. This way, we dont
> >> need
> >> >> to consider all the corner cases and we are sure it is correct.
> >> >
> >> >
> >> > In this particular case of binding, I don't think there are any corner
> >> cases..
> >> >
> >> > We consider an OVS interface for binding if the below 2 conditions
> meet
> >> >
> >> > 1. external_ids:iface-id is set
> >> > 2. ofport is > 0.
> >> >
> >> > Otherwise we need to consider a release if it was already claimed.
> >> >
> >> > When  an ovs interface is updated like below
> >> >  $ovs-vsctl set interface p1 external_ids:foo=bar
> >> >
> >> > We don't need to handle it like a deletion and then a creation.
> Because
> >> we can
> >> > easily see that the above conditions are met,
> >> >
> >> What you described is correct (I believe). However, it is the
> declarative
> >> way of thinking, which is perfect for the always recompute
> implementation,
> >> but it is hard to reason the correctness and to maintain in longer term
> for
> >> incremental processing. Incremental processing focuses on the data
> change
> >> instead of the desired status. In this case you may have already thought
> >> through and sure about the correctness even some data change is ignored.
> >> However, in the long run, it is hard to remember all the details about
> >> which field change needs to be handled in which way, and why some other
> >> changes are ignored. More importantly, when a developer who is not so
> >> familiar with this changle handling logic want to add a new feature,
> which
> >> requires another field, e.g. another key-value in external_ids, it is
> easy
> >> to forget to update here, and not easy to catch in code review either,
> and
> >> even we do aware it needs to be updated here, the if condition would get
> so
> >> complex even with just 3 fields to be considered.
> >
> >
> >
> > I think adding comments should be enough for a new user. And I'll make
> sure
> > I add proper comments.
> > In the case of binding an interface, I really don't see why would
> anything change in the future. Even
> > if it changes, I think the test cases should fail if that is the case.
> >
> >
> >>
> >> This is why I think we
> >> should make it "simple and stupid" to handle updates as if the old one
> was
> >> deleted and then a new one is added.
> >
> >
> >
> > If suppose an OVS interface external_ids:iface-id is updated from 'foo'
> to 'bar'
> > and if 'foo' logical port was already bound, then we still need to check
> what was
> > the old value from the 'b_ctx_out->local_iface_ids' and release that port
> binding
> > before considering claiming the port binding for 'bar'.
> >
> > In my opinion calling delete followed by create will not help. And we
> still need to maintain the
> > logic of checking the necessary fields as the ovsdb tracking doesn't say
> what changed.
> > And I don't think calling delete, followed by create is going to make the
> code simpler. Or will work
> > without any changes later if there is a need to handle port binding
> differently.
> >
>
> Maybe deletion/addition is not accurate here. I didn't mean we don't need
> to compare the iface-id. iface-id is a key here and we need to be sure if
> it is changed. Since ovsdb tracking doesn't provide the old data, I think
> your approach of maintaining the local_binding data structure served that
> purpose pretty well. The problem we are discussing here is about how to
> handle the changes when we compared and identified the iface-id
> change/no-change. So I didn't mean we can simply handle as if a interface
> is deleted/added (sorry about my wording). Maybe unbinding/binding is a
> better word (or releasing/claiming), which means deletion/addition of a
> local_binding entry (and all the data related to it). Now the first loop is
> already doing the "releasing", for the second loop, since the rest of cases
> include both "claiming" and "updating claiming". My proposal is just to
> handle the later in a simple way with confidence.
>
> The miss in the below condition is just an example why handling each of the
> other fields could be complex and error prone:
>
> >> >> >> > +        if (iface_id && ofport > 0) {
> >> >> >>
> >> >> >> What if iface-id didn't change, but ofport changed from non-zero
> to 0?
> >> >> How
> >> >> >> should we handle the change?
>
> For the case of "iface-id didn't change, but ofport changed from non-zero
> to 0", according to the criteria you gave, the binding should be released.
> My proposal is that we don't need to check the ofport here at all. In this
> loop, just follow the principle of "releasing the old" and "claiming the
> new", we will first release the binding, and then handle the claiming,
> which would fail because the criteria for binding is not met.


> Although you could fix it by adding some more condition checks here for
> ofport to achieve the same result, I think it makes the change-handler more
> complex unnecessarily. If there are more conditions to check in the future
> it would be harder to maintain. The conditions check is better to be
> encapsulated in the functions that handles the claim/release.
>

Thanks for the comments.
In v8, I have added more comments to make it more cleared.
Please take a look and we can discuss further there.


Thanks
Numan


> Similar handlings in lflow processing is, lflow uuid is the key. If a lflow
> is changed, delete everything related to the lflow, and re-consider it.
> Same for port-binding handling in physical flows. Here, the key is
> <iface-id, ifname>. If one of the pair is gone, the related bindings should
> be released; if a new pair is added; the related binding should be created;
> if an existing pair is updated with whatever fields, release the binding
> and reclaim it so than any property of the binding is up to date. To figure
> out the addition/deletion/update of the <iface-id, ifname> pair, the
> approach you did with local_binding structure is very helpful, which
> overcame the ovsdb tracking defect.
>
> >
> >>
> >> At least this has provided me a lot of
> >> convenience in the earlier I-P implementations, and I was even surprised
> >> that there were so many new features added but most of them didn't
> needed
> >> to be aware of the I-P details, thanks to this principle. I hope this
> >> clarifies the idea behind.
> >
> >
> > Ok. But I'm not sure if we should apply this principle as a strict rule.
> >
> Of course, there will always be exceptions. When applying this rule create
> more trouble than benefit we don't need to follow, but at least it is a way
> help us to evaluate if there is anything missing in the I-P, to avoid
> pitfalls.
>
> >>
> >> > It also creates unnecessary deletion and re-adding of OVS flows
> related
> >> to that
> >> > logical port when we handle flow computation for changed runtime data.
> >> >
> >> > If we handle this way, there won't be any runtime data changes.
> >> >
> >>
> >> I agree it may be less optimized, but considering that we are
> incrementally
> >> processing the changes here, even if there are many interfaces deteled
> and
> >> added unnecesarily, the cost of I-P for such changes is just nothing
> >> comparing with the full recompute. I believe it would be good enough
> from
> >> the end user point of view, if we can make it incrementally processed,
> >> correctly.
> >>
> >
> > But the idea of this whole patch series is also to improve the
> performance as much
> > as we can, because of the scale issues we have  seen.
> >
> I think all the performance problem was because of recomputing, so I guess
> the purpose of this series is to do I-P as much as we can. I assume once
> the change is handled incrementally, there is no performance problem to be
> worried about. For the interface changes, one interface may correspond to
> one physical flow only. Even tens of interfaces are changed together on the
> chassis we only need to handle tens of flow computations, which is nothing
> compared to the millions of flow computations that caused the performance
> problems.
>
> My suggestions here are the principles that I think is helpful to achieve
> the correctness and maintenability, but if you think the same has been
> thought through and can be achieved in different ways, I respect your
> decision ;)
>
> > I would also like to hear the opinion of Mark, Dumitru and others .
> >
> > Thanks
> > Numan
> >
> >>
> >> > Thanks
> >> > Numan
> >> >
> >> >>
> >> >> >
> >> >> >>
> >> >> >>
> >> >> >> 2. Some other condition checks should be done for this loop, such
> as:
> >> if
> >> >> >> (!is_iface_vif(iface_rec)) ...
> >> >> >
> >> >> >
> >> >> > This is not required because the first loop checks for it and if
> there
> >> is
> >> >> any
> >> >> > non vif interface change, then this function returns false, before
> the
> >> >> > second loop.
> >> >> >
> >> >> OK.
> >> >>
> >> >> >>
> >> >> >>
> >> >> >> > +        if (ovsrec_interface_is_deleted(iface_rec)) {
> >> >> >> > +            continue;
> >> >> >> > +        }
> >> >> >> > +
> >> >> >> > +        const char *iface_id =
> smap_get(&iface_rec->external_ids,
> >> >> >> "iface-id");
> >> >> >> > +        int64_t ofport = iface_rec->n_ofport ?
> *iface_rec->ofport
> >> : 0;
> >> >> >> > +        if (iface_id && ofport > 0) {
> >> >> >>
> >> >> >> What if iface-id didn't change, but ofport changed from non-zero
> to 0?
> >> >> How
> >> >> >> should we handle the change?
> >> >> >>
> >> >> >
> >> >> > Good point. I think we should consider this as
> handle_iface_delete.
> >> >> >
> >> >> > I'll rename the function handle_iface_delete() to
> >> handle_iface_release()
> >> >> > and handle_iface_add() to handle_iface_claim() to be more clear.
> >> >> >
> >> >> >> A general approach can be: whenever there is an update
> >> >> >> (!ovsrec_interface_is_new()), we firstly handle it as interface
> >> delete,
> >> >> and
> >> >> >> then handle an interface add.
> >> >> >
> >> >> >
> >> >> > But why delete and add if can determine that it is going to be
> either
> >> one
> >> >> > i.e release or claim and I think we can definitely determine.
> >> >> > Also in patch 6/7 where we do tracking, it becomes a bit
> complicated
> >> >> > if we handle it as delete and then add as we may add 2 such
> tracking
> >> >> > data.
> >> >> >
> >> >> It is for simplicity and correctness. Please see the comment above.
> >> >>
> >> >> > Thanks
> >> >> > Numan
> >> >> >
> >> >> >>
> >> >> >> > +            *changed = true;
> >> >> >> > +            handled = handle_iface_add(iface_rec, iface_id,
> >> b_ctx_in,
> >> >> >> > +                                       b_ctx_out, qos_map_ptr);
> >> >> >> > +            if (!handled) {
> >> >> >> > +                break;
> >> >> >> > +            }
> >> >> >> > +        }
> >> >> >> > +    }
> >> >> >> > +
> >> >> >> > +    if (handled && qos_map_ptr &&
> >> set_noop_qos(b_ctx_in->ovs_idl_txn,
> >> >> >> > +
> >> b_ctx_in->port_table,
> >> >> >> > +
> b_ctx_in->qos_table,
> >> >> >> > +
> >> >> >> b_ctx_out->egress_ifaces)) {
> >> >> >> > +        const char *entry;
> >> >> >> > +        SSET_FOR_EACH (entry, b_ctx_out->egress_ifaces) {
> >> >> >> > +            setup_qos(entry, &qos_map);
> >> >> >> > +        }
> >> >> >> > +    }
> >> >> >> > +
> >> >> >> > +    destroy_qos_map(&qos_map);
> >> >> >> > +    return handled;
> >> >> >> >  }
> >> >> >> >
> >> >> >> > -/* Returns true if the database is all cleaned up, false if
> more
> >> work
> >> >> is
> >> >> >> > - * required. */
> >> >> >> > -bool
> >> >> >> > -binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn,
> >> >> >> > -                const struct sbrec_port_binding_table
> >> >> >> *port_binding_table,
> >> >> >> > -                const struct sbrec_chassis *chassis_rec)
> >> >> >> > +static void
> >> >> >> > +handle_deleted_lport(const struct sbrec_port_binding *pb,
> >> >> >> > +                     struct binding_ctx_in *b_ctx_in,
> >> >> >> > +                     struct binding_ctx_out *b_ctx_out)
> >> >> >> >  {
> >> >> >> > -    if (!ovnsb_idl_txn) {
> >> >> >> > +    struct local_datapath *ld =
> >> >> >> > +        get_local_datapath(b_ctx_out->local_datapaths,
> >> >> >> > +                           pb->datapath->tunnel_key);
> >> >> >> > +    if (ld) {
> >> >> >> > +        remove_pb_from_local_datapath(pb,
> b_ctx_in->chassis_rec,
> >> >> >> > +                                      b_ctx_out, ld);
> >> >> >> > +    }
> >> >> >> > +}
> >> >> >> > +
> >> >> >> > +static struct local_binding *
> >> >> >> > +get_lbinding_for_lport(const struct sbrec_port_binding *pb,
> >> >> >> > +                       enum en_lport_type lport_type,
> >> >> >> > +                       struct binding_ctx_out *b_ctx_out)
> >> >> >> > +{
> >> >> >> > +    ovs_assert(lport_type == LP_VIF || lport_type ==
> LP_VIRTUAL);
> >> >> >> > +
> >> >> >> > +    if (lport_type == LP_VIF && !is_lport_container(pb)) {
> >> >> >> > +        return local_binding_find(b_ctx_out->local_bindings,
> >> >> >> pb->logical_port);
> >> >> >> > +    }
> >> >> >> > +
> >> >> >> > +    struct local_binding *parent_lbinding = NULL;
> >> >> >> > +
> >> >> >> > +    if (lport_type == LP_VIRTUAL) {
> >> >> >> > +        parent_lbinding =
> >> >> local_binding_find(b_ctx_out->local_bindings,
> >> >> >> > +
> pb->virtual_parent);
> >> >> >> > +    } else {
> >> >> >> > +        parent_lbinding =
> >> >> local_binding_find(b_ctx_out->local_bindings,
> >> >> >> > +                                             pb->parent_port);
> >> >> >> > +    }
> >> >> >> > +
> >> >> >> > +    return parent_lbinding
> >> >> >> > +           ? local_binding_find(&parent_lbinding->children,
> >> >> >> pb->logical_port)
> >> >> >> > +           : NULL;
> >> >> >> > +}
> >> >> >> > +
> >> >> >> > +static bool
> >> >> >> > +handle_deleted_vif_lport(const struct sbrec_port_binding *pb,
> >> >> >> > +                         enum en_lport_type lport_type,
> >> >> >> > +                         struct binding_ctx_in *b_ctx_in,
> >> >> >> > +                         struct binding_ctx_out *b_ctx_out,
> >> >> >> > +                         bool *changed)
> >> >> >> > +{
> >> >> >> > +    struct local_binding *lbinding =
> >> >> >> > +        get_lbinding_for_lport(pb, lport_type, b_ctx_out);
> >> >> >> > +
> >> >> >> > +    if (lbinding) {
> >> >> >> > +        lbinding->pb = NULL;
> >> >> >> > +        /* The port_binding 'pb' is deleted. So there is no
> need to
> >> >> >> > +         * clear the 'chassis' column of 'pb'. But we need to
> do
> >> >> >> > +         * for the local_binding's children. */
> >> >> >> > +        if (lbinding->type == BT_VIF &&
> >> >> >> > +
> >>  !release_local_binding_children(b_ctx_in->chassis_rec,
> >> >> >> > +                                                lbinding,
> >> >> >> > +
> >> >> >>  !b_ctx_in->ovnsb_idl_txn)) {
> >> >> >> > +            return false;
> >> >> >> > +        }
> >> >> >> > +
> >> >> >> > +        *changed = true;
> >> >> >> > +    }
> >> >> >> > +
> >> >> >> > +    handle_deleted_lport(pb, b_ctx_in, b_ctx_out);
> >> >> >> > +    return true;
> >> >> >> > +}
> >> >> >> > +
> >> >> >> > +static bool
> >> >> >> > +handle_updated_vif_lport(const struct sbrec_port_binding *pb,
> >> >> >> > +                         enum en_lport_type lport_type,
> >> >> >> > +                         struct binding_ctx_in *b_ctx_in,
> >> >> >> > +                         struct binding_ctx_out *b_ctx_out,
> >> >> >> > +                         struct hmap *qos_map,
> >> >> >> > +                         bool *changed)
> >> >> >> > +{
> >> >> >> > +    bool claimed = (pb->chassis == b_ctx_in->chassis_rec);
> >> >> >> > +    bool handled = true;
> >> >> >> > +
> >> >> >> > +    if (lport_type == LP_VIRTUAL) {
> >> >> >> > +        handled = consider_virtual_lport(pb, b_ctx_in,
> b_ctx_out,
> >> >> >> qos_map);
> >> >> >> > +    } else if (lport_type == LP_VIF && is_lport_container(pb))
> {
> >> >> >> > +        handled = consider_container_lport(pb, b_ctx_in,
> b_ctx_out,
> >> >> >> qos_map);
> >> >> >> > +    } else {
> >> >> >> > +        handled = consider_vif_lport(pb, b_ctx_in, b_ctx_out,
> NULL,
> >> >> >> qos_map);
> >> >> >> > +    }
> >> >> >> > +
> >> >> >> > +    if (!handled) {
> >> >> >> >          return false;
> >> >> >> >      }
> >> >> >> > -    if (!chassis_rec) {
> >> >> >> > +
> >> >> >> > +    bool now_claimed = (pb->chassis == b_ctx_in->chassis_rec);
> >> >> >> > +    bool changed_ = (claimed != now_claimed);
> >> >> >> > +
> >> >> >> > +    if (changed_) {
> >> >> >> > +        *changed = true;
> >> >> >> > +    }
> >> >> >> > +
> >> >> >> > +    if (lport_type == LP_VIRTUAL ||
> >> >> >> > +        (lport_type == LP_VIF && is_lport_container(pb)) ||
> >> >> !changed_) {
> >> >> >> >          return true;
> >> >> >> >      }
> >> >> >> >
> >> >> >> > -    const struct sbrec_port_binding *binding_rec;
> >> >> >> > -    bool any_changes = false;
> >> >> >> > -    SBREC_PORT_BINDING_TABLE_FOR_EACH (binding_rec,
> >> >> port_binding_table) {
> >> >> >> > -        if (binding_rec->chassis == chassis_rec) {
> >> >> >> > -            if (binding_rec->encap)
> >> >> >> > -                sbrec_port_binding_set_encap(binding_rec,
> NULL);
> >> >> >> > -            sbrec_port_binding_set_chassis(binding_rec, NULL);
> >> >> >> > -            any_changes = true;
> >> >> >> > +    struct local_binding *lbinding =
> >> >> >> > +        local_binding_find(b_ctx_out->local_bindings,
> >> >> pb->logical_port);
> >> >> >> > +
> >> >> >> > +    ovs_assert(lbinding);
> >> >> >> > +
> >> >> >> > +    struct shash_node *node;
> >> >> >> > +    SHASH_FOR_EACH (node, &lbinding->children) {
> >> >> >> > +        struct local_binding *child = node->data;
> >> >> >> > +        if (child->type == BT_CONTAINER) {
> >> >> >> > +            handled = consider_container_lport(child->pb,
> b_ctx_in,
> >> >> >> b_ctx_out,
> >> >> >> > +                                               qos_map);
> >> >> >> > +            if (!handled) {
> >> >> >> > +                return false;
> >> >> >> > +            }
> >> >> >> >          }
> >> >> >> >      }
> >> >> >> >
> >> >> >> > -    if (any_changes) {
> >> >> >> > -        ovsdb_idl_txn_add_comment(
> >> >> >> > -            ovnsb_idl_txn,
> >> >> >> > -            "ovn-controller: removing all port bindings for
> '%s'",
> >> >> >> > -            chassis_rec->name);
> >> >> >> > +    return true;
> >> >> >> > +}
> >> >> >> > +
> >> >> >> > +/* Returns true if the port binding changes resulted in local
> >> binding
> >> >> >> > + * updates, false otherwise.
> >> >> >> > + */
> >> >> >> > +bool
> >> >> >> > +binding_handle_port_binding_changes(struct binding_ctx_in
> >> *b_ctx_in,
> >> >> >> > +                                    struct binding_ctx_out
> >> *b_ctx_out,
> >> >> >> > +                                    bool *changed)
> >> >> >> > +{
> >> >> >> > +    bool handled = true;
> >> >> >> > +    *changed = false;
> >> >> >> > +
> >> >> >> > +    const struct sbrec_port_binding *pb;
> >> >> >> > +
> >> >> >> > +    /* Run the tracked port binding loop twice. One to handle
> >> deleted
> >> >> >> > +     * changes. And another to handle add/update changes.
> >> >> >> > +     * This will ensure correctness.
> >> >> >> > +     */
> >> >> >> > +    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
> >> >> >> > +
> >> >> >> b_ctx_in->port_binding_table) {
> >> >> >> > +        if (!sbrec_port_binding_is_deleted(pb)) {
> >> >> >> > +            continue;
> >> >> >> > +        }
> >> >> >> > +
> >> >> >> > +        enum en_lport_type lport_type = get_lport_type(pb);
> >> >> >> > +        if (lport_type == LP_VIF || lport_type == LP_VIRTUAL) {
> >> >> >> > +            handled = handle_deleted_vif_lport(pb, lport_type,
> >> >> b_ctx_in,
> >> >> >> > +                                                b_ctx_out,
> >> changed);
> >> >> >> > +        } else {
> >> >> >> > +            handle_deleted_lport(pb, b_ctx_in, b_ctx_out);
> >> >> >> > +        }
> >> >> >> > +
> >> >> >> > +        if (!handled) {
> >> >> >> > +            break;
> >> >> >> > +        }
> >> >> >> >      }
> >> >> >> >
> >> >> >> > -    return !any_changes;
> >> >> >> > +    if (!handled) {
> >> >> >> > +        return false;
> >> >> >> > +    }
> >> >> >> > +
> >> >> >> > +    struct hmap qos_map = HMAP_INITIALIZER(&qos_map);
> >> >> >> > +    struct hmap *qos_map_ptr =
> >> >> >> > +        sset_is_empty(b_ctx_out->egress_ifaces) ? NULL :
> &qos_map;
> >> >> >> > +
> >> >> >> > +    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
> >> >> >> > +
> >> >> >> b_ctx_in->port_binding_table) {
> >> >> >> > +        /* Loop to handle create and update changes only. */
> >> >> >> > +        if (sbrec_port_binding_is_deleted(pb)) {
> >> >> >> > +            continue;
> >> >> >> > +        }
> >> >> >> > +
> >> >> >> > +        enum en_lport_type lport_type = get_lport_type(pb);
> >> >> >> > +
> >> >> >> > +        struct local_datapath *ld =
> >> >> >> > +            get_local_datapath(b_ctx_out->local_datapaths,
> >> >> >> > +                               pb->datapath->tunnel_key);
> >> >> >> > +
> >> >> >> > +        switch (lport_type) {
> >> >> >> > +        case LP_VIF:
> >> >> >> > +        case LP_VIRTUAL:
> >> >> >> > +            handled = handle_updated_vif_lport(pb, lport_type,
> >> >> b_ctx_in,
> >> >> >> > +                                               b_ctx_out,
> >> qos_map_ptr,
> >> >> >> > +                                               changed);
> >> >> >> > +            break;
> >> >> >> > +
> >> >> >> > +        case LP_PATCH:
> >> >> >> > +        case LP_LOCALPORT:
> >> >> >> > +        case LP_VTEP:
> >> >> >> > +            *changed = true;
> >> >> >> > +            update_local_lport_ids(b_ctx_out->local_lport_ids,
> pb);
> >> >> >> > +            if (lport_type ==  LP_PATCH) {
> >> >> >> > +                /* Add the peer datapath to the local datapaths
> if
> >> >> it's
> >> >> >> > +                    * not present yet.
> >> >> >> > +                    */
> >> >> >> > +                if (ld) {
> >> >> >> > +                    add_local_datapath_peer_port(pb, b_ctx_in,
> >> >> >> > +                                                    b_ctx_out,
> ld);
> >> >> >> > +                }
> >> >> >> > +            }
> >> >> >> > +            break;
> >> >> >> > +
> >> >> >> > +        case LP_L2GATEWAY:
> >> >> >> > +            *changed = true;
> >> >> >> > +            handled = consider_l2gw_lport(pb, b_ctx_in,
> b_ctx_out);
> >> >> >> > +            break;
> >> >> >> > +
> >> >> >> > +        case LP_L3GATEWAY:
> >> >> >> > +            *changed = true;
> >> >> >> > +            handled = consider_l3gw_lport(pb, b_ctx_in,
> b_ctx_out);
> >> >> >> > +            break;
> >> >> >> > +
> >> >> >> > +        case LP_CHASSISREDIRECT:
> >> >> >> > +            *changed = true;
> >> >> >> > +            handled = consider_cr_lport(pb, b_ctx_in,
> b_ctx_out);
> >> >> >> > +            break;
> >> >> >> > +
> >> >> >> > +        case LP_EXTERNAL:
> >> >> >> > +            *changed = true;
> >> >> >> > +            handled = consider_external_lport(pb, b_ctx_in,
> >> >> b_ctx_out);
> >> >> >> > +            break;
> >> >> >> > +
> >> >> >> > +        case LP_LOCALNET: {
> >> >> >> > +            *changed = true;
> >> >> >> > +            consider_localnet_lport(pb, b_ctx_in, b_ctx_out,
> >> >> >> qos_map_ptr);
> >> >> >> > +
> >> >> >> > +            struct shash bridge_mappings =
> >> >> >> > +                SHASH_INITIALIZER(&bridge_mappings);
> >> >> >> > +            add_ovs_bridge_mappings(b_ctx_in->ovs_table,
> >> >> >> > +                                    b_ctx_in->bridge_table,
> >> >> >> > +                                    &bridge_mappings);
> >> >> >> > +            update_ld_localnet_port(pb, &bridge_mappings,
> >> >> >> > +                                    b_ctx_out->egress_ifaces,
> >> >> >> > +
> b_ctx_out->local_datapaths);
> >> >> >> > +            shash_destroy(&bridge_mappings);
> >> >> >> > +            break;
> >> >> >> > +        }
> >> >> >> > +
> >> >> >> > +        case LP_REMOTE:
> >> >> >> > +        case LP_UNKNOWN:
> >> >> >> > +            break;
> >> >> >> > +        }
> >> >> >> > +
> >> >> >> > +        if (!handled) {
> >> >> >> > +            break;
> >> >> >> > +        }
> >> >> >> > +    }
> >> >> >> > +
> >> >> >> > +    if (handled && qos_map_ptr &&
> >> set_noop_qos(b_ctx_in->ovs_idl_txn,
> >> >> >> > +
> >> b_ctx_in->port_table,
> >> >> >> > +
> b_ctx_in->qos_table,
> >> >> >> > +
> >> >> >> b_ctx_out->egress_ifaces)) {
> >> >> >> > +        const char *entry;
> >> >> >> > +        SSET_FOR_EACH (entry, b_ctx_out->egress_ifaces) {
> >> >> >> > +            setup_qos(entry, &qos_map);
> >> >> >> > +        }
> >> >> >> > +    }
> >> >> >> > +
> >> >> >> > +    destroy_qos_map(&qos_map);
> >> >> >> > +    return handled;
> >> >> >> >  }
> >> >> >> > diff --git a/controller/binding.h b/controller/binding.h
> >> >> >> > index 9affc9a96..f7ace6faf 100644
> >> >> >> > --- a/controller/binding.h
> >> >> >> > +++ b/controller/binding.h
> >> >> >> > @@ -57,6 +57,7 @@ struct binding_ctx_out {
> >> >> >> >      struct sset *local_lports;
> >> >> >> >      struct sset *local_lport_ids;
> >> >> >> >      struct sset *egress_ifaces;
> >> >> >> > +    struct smap *local_iface_ids;
> >> >> >> >  };
> >> >> >> >
> >> >> >> >  void binding_register_ovs_idl(struct ovsdb_idl *);
> >> >> >> > @@ -64,9 +65,13 @@ void binding_run(struct binding_ctx_in *,
> struct
> >> >> >> binding_ctx_out *);
> >> >> >> >  bool binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn,
> >> >> >> >                       const struct sbrec_port_binding_table *,
> >> >> >> >                       const struct sbrec_chassis *);
> >> >> >> > -bool binding_evaluate_port_binding_changes(struct
> binding_ctx_in *,
> >> >> >> > -                                           struct
> binding_ctx_out
> >> *);
> >> >> >> >
> >> >> >> >  void local_bindings_init(struct shash *local_bindings);
> >> >> >> >  void local_bindings_destroy(struct shash *local_bindings);
> >> >> >> > +bool binding_handle_ovs_interface_changes(struct binding_ctx_in
> *,
> >> >> >> > +                                          struct
> binding_ctx_out *,
> >> >> >> > +                                          bool *changed);
> >> >> >> > +bool binding_handle_port_binding_changes(struct binding_ctx_in
> *,
> >> >> >> > +                                         struct binding_ctx_out
> *,
> >> >> >> > +                                         bool *changed);
> >> >> >> >  #endif /* controller/binding.h */
> >> >> >> > diff --git a/controller/ovn-controller.c
> >> b/controller/ovn-controller.c
> >> >> >> > index 6b759966b..08b074415 100644
> >> >> >> > --- a/controller/ovn-controller.c
> >> >> >> > +++ b/controller/ovn-controller.c
> >> >> >> > @@ -753,6 +753,7 @@ enum sb_engine_node {
> >> >> >> >      OVS_NODE(open_vswitch, "open_vswitch") \
> >> >> >> >      OVS_NODE(bridge, "bridge") \
> >> >> >> >      OVS_NODE(port, "port") \
> >> >> >> > +    OVS_NODE(interface, "interface") \
> >> >> >> >      OVS_NODE(qos, "qos")
> >> >> >> >
> >> >> >> >  enum ovs_engine_node {
> >> >> >> > @@ -974,6 +975,7 @@ struct ed_type_runtime_data {
> >> >> >> >      struct sset active_tunnels;
> >> >> >> >
> >> >> >> >      struct sset egress_ifaces;
> >> >> >> > +    struct smap local_iface_ids;
> >> >> >> >  };
> >> >> >> >
> >> >> >> >  static void *
> >> >> >> > @@ -987,6 +989,7 @@ en_runtime_data_init(struct engine_node
> *node
> >> >> >> OVS_UNUSED,
> >> >> >> >      sset_init(&data->local_lport_ids);
> >> >> >> >      sset_init(&data->active_tunnels);
> >> >> >> >      sset_init(&data->egress_ifaces);
> >> >> >> > +    smap_init(&data->local_iface_ids);
> >> >> >> >      local_bindings_init(&data->local_bindings);
> >> >> >> >      return data;
> >> >> >> >  }
> >> >> >> > @@ -1000,6 +1003,7 @@ en_runtime_data_cleanup(void *data)
> >> >> >> >      sset_destroy(&rt_data->local_lport_ids);
> >> >> >> >      sset_destroy(&rt_data->active_tunnels);
> >> >> >> >      sset_destroy(&rt_data->egress_ifaces);
> >> >> >> > +    smap_destroy(&rt_data->local_iface_ids);
> >> >> >> >      struct local_datapath *cur_node, *next_node;
> >> >> >> >      HMAP_FOR_EACH_SAFE (cur_node, next_node, hmap_node,
> >> >> >> >                          &rt_data->local_datapaths) {
> >> >> >> > @@ -1041,6 +1045,10 @@ init_binding_ctx(struct engine_node
> *node,
> >> >> >> >          (struct ovsrec_port_table *)EN_OVSDB_GET(
> >> >> >> >              engine_get_input("OVS_port", node));
> >> >> >> >
> >> >> >> > +    struct ovsrec_interface_table *iface_table =
> >> >> >> > +        (struct ovsrec_interface_table *)EN_OVSDB_GET(
> >> >> >> > +            engine_get_input("OVS_interface", node));
> >> >> >> > +
> >> >> >> >      struct ovsrec_qos_table *qos_table =
> >> >> >> >          (struct ovsrec_qos_table *)EN_OVSDB_GET(
> >> >> >> >              engine_get_input("OVS_qos", node));
> >> >> >> > @@ -1070,6 +1078,7 @@ init_binding_ctx(struct engine_node *node,
> >> >> >> >      b_ctx_in->sbrec_port_binding_by_datapath =
> >> >> >> sbrec_port_binding_by_datapath;
> >> >> >> >      b_ctx_in->sbrec_port_binding_by_name =
> >> sbrec_port_binding_by_name;
> >> >> >> >      b_ctx_in->port_table = port_table;
> >> >> >> > +    b_ctx_in->iface_table = iface_table;
> >> >> >> >      b_ctx_in->qos_table = qos_table;
> >> >> >> >      b_ctx_in->port_binding_table = pb_table;
> >> >> >> >      b_ctx_in->br_int = br_int;
> >> >> >> > @@ -1083,6 +1092,7 @@ init_binding_ctx(struct engine_node *node,
> >> >> >> >      b_ctx_out->local_lport_ids = &rt_data->local_lport_ids;
> >> >> >> >      b_ctx_out->egress_ifaces = &rt_data->egress_ifaces;
> >> >> >> >      b_ctx_out->local_bindings = &rt_data->local_bindings;
> >> >> >> > +    b_ctx_out->local_iface_ids = &rt_data->local_iface_ids;
> >> >> >> >  }
> >> >> >> >
> >> >> >> >  static void
> >> >> >> > @@ -1111,10 +1121,12 @@ en_runtime_data_run(struct engine_node
> >> *node,
> >> >> >> void *data)
> >> >> >> >          sset_destroy(local_lport_ids);
> >> >> >> >          sset_destroy(active_tunnels);
> >> >> >> >          sset_destroy(&rt_data->egress_ifaces);
> >> >> >> > +        smap_destroy(&rt_data->local_iface_ids);
> >> >> >> >          sset_init(local_lports);
> >> >> >> >          sset_init(local_lport_ids);
> >> >> >> >          sset_init(active_tunnels);
> >> >> >> >          sset_init(&rt_data->egress_ifaces);
> >> >> >> > +        smap_init(&rt_data->local_iface_ids);
> >> >> >> >          local_bindings_init(&rt_data->local_bindings);
> >> >> >> >      }
> >> >> >> >
> >> >> >> > @@ -1140,6 +1152,34 @@ en_runtime_data_run(struct engine_node
> *node,
> >> >> void
> >> >> >> *data)
> >> >> >> >      engine_set_node_state(node, EN_UPDATED);
> >> >> >> >  }
> >> >> >> >
> >> >> >> > +static bool
> >> >> >> > +runtime_data_ovs_interface_handler(struct engine_node *node,
> void
> >> >> *data)
> >> >> >> > +{
> >> >> >> > +    struct ed_type_runtime_data *rt_data = data;
> >> >> >> > +    struct binding_ctx_in b_ctx_in;
> >> >> >> > +    struct binding_ctx_out b_ctx_out;
> >> >> >> > +    init_binding_ctx(node, rt_data, &b_ctx_in, &b_ctx_out);
> >> >> >> > +
> >> >> >> > +    bool changed = false;
> >> >> >> > +    if (!binding_handle_ovs_interface_changes(&b_ctx_in,
> >> &b_ctx_out,
> >> >> >> > +                                              &changed)) {
> >> >> >> > +        return false;
> >> >> >> > +    }
> >> >> >> > +
> >> >> >> > +    if (changed) {
> >> >> >> > +        engine_set_node_state(node, EN_UPDATED);
> >> >> >> > +    }
> >> >> >> > +
> >> >> >> > +    return true;
> >> >> >> > +}
> >> >> >> > +
> >> >> >> > +static bool
> >> >> >> > +runtime_data_noop_handler(struct engine_node *node OVS_UNUSED,
> >> >> >> > +                          void *data OVS_UNUSED)
> >> >> >> > +{
> >> >> >> > +    return true;
> >> >> >> > +}
> >> >> >> > +
> >> >> >> >  static bool
> >> >> >> >  runtime_data_sb_port_binding_handler(struct engine_node *node,
> void
> >> >> >> *data)
> >> >> >> >  {
> >> >> >> > @@ -1147,11 +1187,21 @@
> runtime_data_sb_port_binding_handler(struct
> >> >> >> engine_node *node, void *data)
> >> >> >> >      struct binding_ctx_in b_ctx_in;
> >> >> >> >      struct binding_ctx_out b_ctx_out;
> >> >> >> >      init_binding_ctx(node, rt_data, &b_ctx_in, &b_ctx_out);
> >> >> >> > +    if (!b_ctx_in.chassis_rec) {
> >> >> >> > +        return false;
> >> >> >> > +    }
> >> >> >> > +
> >> >> >> > +    bool changed = false;
> >> >> >> > +    if (!binding_handle_port_binding_changes(&b_ctx_in,
> &b_ctx_out,
> >> >> >> > +                                             &changed)) {
> >> >> >> > +        return false;
> >> >> >> > +    }
> >> >> >> >
> >> >> >> > -    bool changed =
> binding_evaluate_port_binding_changes(&b_ctx_in,
> >> >> >> > -
> >> &b_ctx_out);
> >> >> >> > +    if (changed) {
> >> >> >> > +        engine_set_node_state(node, EN_UPDATED);
> >> >> >> > +    }
> >> >> >> >
> >> >> >> > -    return !changed;
> >> >> >> > +    return true;
> >> >> >> >  }
> >> >> >> >
> >> >> >> >  /* Connection tracking zones. */
> >> >> >> > @@ -1894,7 +1944,10 @@ main(int argc, char *argv[])
> >> >> >> >
> >> >> >> >      engine_add_input(&en_runtime_data, &en_ovs_open_vswitch,
> NULL);
> >> >> >> >      engine_add_input(&en_runtime_data, &en_ovs_bridge, NULL);
> >> >> >> > -    engine_add_input(&en_runtime_data, &en_ovs_port, NULL);
> >> >> >> > +    engine_add_input(&en_runtime_data, &en_ovs_port,
> >> >> >> > +                     runtime_data_noop_handler);
> >> >> >> > +    engine_add_input(&en_runtime_data, &en_ovs_interface,
> >> >> >> > +                     runtime_data_ovs_interface_handler);
> >> >> >> >      engine_add_input(&en_runtime_data, &en_ovs_qos, NULL);
> >> >> >> >
> >> >> >> >      engine_add_input(&en_runtime_data, &en_sb_chassis, NULL);
> >> >> >> > diff --git a/tests/ovn-performance.at b/tests/
> ovn-performance.at
> >> >> >> > index a8a15f8fe..5dfc6f7ca 100644
> >> >> >> > --- a/tests/ovn-performance.at
> >> >> >> > +++ b/tests/ovn-performance.at
> >> >> >> > @@ -239,6 +239,19 @@ for i in 1 2; do
> >> >> >> >      ovn_attach n1 br-phys 192.168.0.$i
> >> >> >> >  done
> >> >> >> >
> >> >> >> > +# Wait for the tunnel ports to be created and up.
> >> >> >> > +# Otherwise this may affect the lflow_run count.
> >> >> >> > +
> >> >> >> > +OVS_WAIT_UNTIL([
> >> >> >> > +    test $(as hv1 ovs-vsctl list interface ovn-hv2-0 | \
> >> >> >> > +grep tunnel_egress_iface_carrier=up | wc -l) -eq 1
> >> >> >> > +])
> >> >> >> > +
> >> >> >> > +OVS_WAIT_UNTIL([
> >> >> >> > +    test $(as hv2 ovs-vsctl list interface ovn-hv1-0 | \
> >> >> >> > +grep tunnel_egress_iface_carrier=up | wc -l) -eq 1
> >> >> >> > +])
> >> >> >> > +
> >> >> >> >  # Add router lr1
> >> >> >> >  OVN_CONTROLLER_EXPECT_HIT(
> >> >> >> >      [hv1 hv2], [lflow_run],
> >> >> >> > --
> >> >> >> > 2.26.2
> >> >> >> >
> >> >> >> > _______________________________________________
> >> >> >> > dev mailing list
> >> >> >> > dev@openvswitch.org
> >> >> >> > https://mail.openvswitch.org/mailman/listinfo/ovs-dev
> >> >> >> _______________________________________________
> >> >> >> dev mailing list
> >> >> >> dev@openvswitch.org
> >> >> >> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
> >> >> >>
> >> >> _______________________________________________
> >> >> dev mailing list
> >> >> dev@openvswitch.org
> >> >> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
> >> >>
> >> _______________________________________________
> >> dev mailing list
> >> dev@openvswitch.org
> >> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
> >>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
>
Mark Michelson May 26, 2020, 2 p.m. UTC | #9
* SNIPPED *

>>>
>> What you described is correct (I believe). However, it is the declarative
>> way of thinking, which is perfect for the always recompute implementation,
>> but it is hard to reason the correctness and to maintain in longer term for
>> incremental processing. Incremental processing focuses on the data change
>> instead of the desired status. In this case you may have already thought
>> through and sure about the correctness even some data change is ignored.
>> However, in the long run, it is hard to remember all the details about
>> which field change needs to be handled in which way, and why some other
>> changes are ignored. More importantly, when a developer who is not so
>> familiar with this changle handling logic want to add a new feature, which
>> requires another field, e.g. another key-value in external_ids, it is easy
>> to forget to update here, and not easy to catch in code review either, and
>> even we do aware it needs to be updated here, the if condition would get so
>> complex even with just 3 fields to be considered.
> 
> 
> 
> I think adding comments should be enough for a new user. And I'll make sure
> I add proper comments.
> In the case of binding an interface, I really don't see why would anything
> change in the future. Even
> if it changes, I think the test cases should fail if that is the case.

I know you already put up v8 but I wanted to comment here as well. I 
agree that proper comments can help make the code's intent clearer, but 
I also agree with Han that it may still not be obvious when developing a 
new change that this section requires an update.

However, the key is Numan's argument that test cases should fail. If 
test cases are still passing after a new developer messes this up, it's 
a good indication that the test cases need hardening.

> 
> 
> 
>> This is why I think we
>> should make it "simple and stupid" to handle updates as if the old one was
>> deleted and then a new one is added.
> 
> 
> 
> If suppose an OVS interface external_ids:iface-id is updated from 'foo' to
> 'bar'
> and if 'foo' logical port was already bound, then we still need to check
> what was
> the old value from the 'b_ctx_out->local_iface_ids' and release that port
> binding
> before considering claiming the port binding for 'bar'.
> 
> In my opinion calling delete followed by create will not help. And we still
> need to maintain the
> logic of checking the necessary fields as the ovsdb tracking doesn't say
> what changed.
> And I don't think calling delete, followed by create is going to make the
> code simpler. Or will work
> without any changes later if there is a need to handle port binding
> differently.
> 
> 
> 
>> At least this has provided me a lot of
>> convenience in the earlier I-P implementations, and I was even surprised
>> that there were so many new features added but most of them didn't needed
>> to be aware of the I-P details, thanks to this principle. I hope this
>> clarifies the idea behind.
>>
> 
> Ok. But I'm not sure if we should apply this principle as a strict rule.

And I would also say that this particular case is very different 
compared to other objects tracked by I-P. This is an object owned by OVS 
rather than OVN. By definition, the majority of fields are going to be 
irrelevant to OVN. So only tracking specific fields makes good sense here.

> 
> 
>>> It also creates unnecessary deletion and re-adding of OVS flows related
>> to that
>>> logical port when we handle flow computation for changed runtime data.
>>>
>>> If we handle this way, there won't be any runtime data changes.
>>>
>>
>> I agree it may be less optimized, but considering that we are incrementally
>> processing the changes here, even if there are many interfaces deteled and
>> added unnecesarily, the cost of I-P for such changes is just nothing
>> comparing with the full recompute. I believe it would be good enough from
>> the end user point of view, if we can make it incrementally processed,
>> correctly.
>>
>>
> But the idea of this whole patch series is also to improve the performance
> as much
> as we can, because of the scale issues we have  seen.
> 
> I would also like to hear the opinion of Mark, Dumitru and others .
> 
> Thanks
> Numan
> 
> 
>>> Thanks
>>> Numan
>>>
>>>>
>>>>>
>>>>>>
>>>>>>
>>>>>> 2. Some other condition checks should be done for this loop, such as:
>> if
>>>>>> (!is_iface_vif(iface_rec)) ...
>>>>>
>>>>>
>>>>> This is not required because the first loop checks for it and if there
>> is
>>>> any
>>>>> non vif interface change, then this function returns false, before the
>>>>> second loop.
>>>>>
>>>> OK.
>>>>
>>>>>>
>>>>>>
>>>>>>> +        if (ovsrec_interface_is_deleted(iface_rec)) {
>>>>>>> +            continue;
>>>>>>> +        }
>>>>>>> +
>>>>>>> +        const char *iface_id = smap_get(&iface_rec->external_ids,
>>>>>> "iface-id");
>>>>>>> +        int64_t ofport = iface_rec->n_ofport ? *iface_rec->ofport
>> : 0;
>>>>>>> +        if (iface_id && ofport > 0) {
>>>>>>
>>>>>> What if iface-id didn't change, but ofport changed from non-zero to
>> 0?
>>>> How
>>>>>> should we handle the change?
>>>>>>
>>>>>
>>>>> Good point. I think we should consider this as  handle_iface_delete.
>>>>>
>>>>> I'll rename the function handle_iface_delete() to
>> handle_iface_release()
>>>>> and handle_iface_add() to handle_iface_claim() to be more clear.
>>>>>
>>>>>> A general approach can be: whenever there is an update
>>>>>> (!ovsrec_interface_is_new()), we firstly handle it as interface
>> delete,
>>>> and
>>>>>> then handle an interface add.
>>>>>
>>>>>
>>>>> But why delete and add if can determine that it is going to be either
>> one
>>>>> i.e release or claim and I think we can definitely determine.
>>>>> Also in patch 6/7 where we do tracking, it becomes a bit complicated
>>>>> if we handle it as delete and then add as we may add 2 such tracking
>>>>> data.
>>>>>
>>>> It is for simplicity and correctness. Please see the comment above.
>>>>
>>>>> Thanks
>>>>> Numan
>>>>>
>>>>>>
>>>>>>> +            *changed = true;
>>>>>>> +            handled = handle_iface_add(iface_rec, iface_id,
>> b_ctx_in,
>>>>>>> +                                       b_ctx_out, qos_map_ptr);
>>>>>>> +            if (!handled) {
>>>>>>> +                break;
>>>>>>> +            }
>>>>>>> +        }
>>>>>>> +    }
>>>>>>> +
>>>>>>> +    if (handled && qos_map_ptr &&
>> set_noop_qos(b_ctx_in->ovs_idl_txn,
>>>>>>> +
>> b_ctx_in->port_table,
>>>>>>> +
>>   b_ctx_in->qos_table,
>>>>>>> +
>>>>>> b_ctx_out->egress_ifaces)) {
>>>>>>> +        const char *entry;
>>>>>>> +        SSET_FOR_EACH (entry, b_ctx_out->egress_ifaces) {
>>>>>>> +            setup_qos(entry, &qos_map);
>>>>>>> +        }
>>>>>>> +    }
>>>>>>> +
>>>>>>> +    destroy_qos_map(&qos_map);
>>>>>>> +    return handled;
>>>>>>>   }
>>>>>>>
>>>>>>> -/* Returns true if the database is all cleaned up, false if more
>> work
>>>> is
>>>>>>> - * required. */
>>>>>>> -bool
>>>>>>> -binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn,
>>>>>>> -                const struct sbrec_port_binding_table
>>>>>> *port_binding_table,
>>>>>>> -                const struct sbrec_chassis *chassis_rec)
>>>>>>> +static void
>>>>>>> +handle_deleted_lport(const struct sbrec_port_binding *pb,
>>>>>>> +                     struct binding_ctx_in *b_ctx_in,
>>>>>>> +                     struct binding_ctx_out *b_ctx_out)
>>>>>>>   {
>>>>>>> -    if (!ovnsb_idl_txn) {
>>>>>>> +    struct local_datapath *ld =
>>>>>>> +        get_local_datapath(b_ctx_out->local_datapaths,
>>>>>>> +                           pb->datapath->tunnel_key);
>>>>>>> +    if (ld) {
>>>>>>> +        remove_pb_from_local_datapath(pb, b_ctx_in->chassis_rec,
>>>>>>> +                                      b_ctx_out, ld);
>>>>>>> +    }
>>>>>>> +}
>>>>>>> +
>>>>>>> +static struct local_binding *
>>>>>>> +get_lbinding_for_lport(const struct sbrec_port_binding *pb,
>>>>>>> +                       enum en_lport_type lport_type,
>>>>>>> +                       struct binding_ctx_out *b_ctx_out)
>>>>>>> +{
>>>>>>> +    ovs_assert(lport_type == LP_VIF || lport_type == LP_VIRTUAL);
>>>>>>> +
>>>>>>> +    if (lport_type == LP_VIF && !is_lport_container(pb)) {
>>>>>>> +        return local_binding_find(b_ctx_out->local_bindings,
>>>>>> pb->logical_port);
>>>>>>> +    }
>>>>>>> +
>>>>>>> +    struct local_binding *parent_lbinding = NULL;
>>>>>>> +
>>>>>>> +    if (lport_type == LP_VIRTUAL) {
>>>>>>> +        parent_lbinding =
>>>> local_binding_find(b_ctx_out->local_bindings,
>>>>>>> +                                             pb->virtual_parent);
>>>>>>> +    } else {
>>>>>>> +        parent_lbinding =
>>>> local_binding_find(b_ctx_out->local_bindings,
>>>>>>> +                                             pb->parent_port);
>>>>>>> +    }
>>>>>>> +
>>>>>>> +    return parent_lbinding
>>>>>>> +           ? local_binding_find(&parent_lbinding->children,
>>>>>> pb->logical_port)
>>>>>>> +           : NULL;
>>>>>>> +}
>>>>>>> +
>>>>>>> +static bool
>>>>>>> +handle_deleted_vif_lport(const struct sbrec_port_binding *pb,
>>>>>>> +                         enum en_lport_type lport_type,
>>>>>>> +                         struct binding_ctx_in *b_ctx_in,
>>>>>>> +                         struct binding_ctx_out *b_ctx_out,
>>>>>>> +                         bool *changed)
>>>>>>> +{
>>>>>>> +    struct local_binding *lbinding =
>>>>>>> +        get_lbinding_for_lport(pb, lport_type, b_ctx_out);
>>>>>>> +
>>>>>>> +    if (lbinding) {
>>>>>>> +        lbinding->pb = NULL;
>>>>>>> +        /* The port_binding 'pb' is deleted. So there is no need
>> to
>>>>>>> +         * clear the 'chassis' column of 'pb'. But we need to do
>>>>>>> +         * for the local_binding's children. */
>>>>>>> +        if (lbinding->type == BT_VIF &&
>>>>>>> +
>>   !release_local_binding_children(b_ctx_in->chassis_rec,
>>>>>>> +                                                lbinding,
>>>>>>> +
>>>>>>   !b_ctx_in->ovnsb_idl_txn)) {
>>>>>>> +            return false;
>>>>>>> +        }
>>>>>>> +
>>>>>>> +        *changed = true;
>>>>>>> +    }
>>>>>>> +
>>>>>>> +    handle_deleted_lport(pb, b_ctx_in, b_ctx_out);
>>>>>>> +    return true;
>>>>>>> +}
>>>>>>> +
>>>>>>> +static bool
>>>>>>> +handle_updated_vif_lport(const struct sbrec_port_binding *pb,
>>>>>>> +                         enum en_lport_type lport_type,
>>>>>>> +                         struct binding_ctx_in *b_ctx_in,
>>>>>>> +                         struct binding_ctx_out *b_ctx_out,
>>>>>>> +                         struct hmap *qos_map,
>>>>>>> +                         bool *changed)
>>>>>>> +{
>>>>>>> +    bool claimed = (pb->chassis == b_ctx_in->chassis_rec);
>>>>>>> +    bool handled = true;
>>>>>>> +
>>>>>>> +    if (lport_type == LP_VIRTUAL) {
>>>>>>> +        handled = consider_virtual_lport(pb, b_ctx_in, b_ctx_out,
>>>>>> qos_map);
>>>>>>> +    } else if (lport_type == LP_VIF && is_lport_container(pb)) {
>>>>>>> +        handled = consider_container_lport(pb, b_ctx_in,
>> b_ctx_out,
>>>>>> qos_map);
>>>>>>> +    } else {
>>>>>>> +        handled = consider_vif_lport(pb, b_ctx_in, b_ctx_out,
>> NULL,
>>>>>> qos_map);
>>>>>>> +    }
>>>>>>> +
>>>>>>> +    if (!handled) {
>>>>>>>           return false;
>>>>>>>       }
>>>>>>> -    if (!chassis_rec) {
>>>>>>> +
>>>>>>> +    bool now_claimed = (pb->chassis == b_ctx_in->chassis_rec);
>>>>>>> +    bool changed_ = (claimed != now_claimed);
>>>>>>> +
>>>>>>> +    if (changed_) {
>>>>>>> +        *changed = true;
>>>>>>> +    }
>>>>>>> +
>>>>>>> +    if (lport_type == LP_VIRTUAL ||
>>>>>>> +        (lport_type == LP_VIF && is_lport_container(pb)) ||
>>>> !changed_) {
>>>>>>>           return true;
>>>>>>>       }
>>>>>>>
>>>>>>> -    const struct sbrec_port_binding *binding_rec;
>>>>>>> -    bool any_changes = false;
>>>>>>> -    SBREC_PORT_BINDING_TABLE_FOR_EACH (binding_rec,
>>>> port_binding_table) {
>>>>>>> -        if (binding_rec->chassis == chassis_rec) {
>>>>>>> -            if (binding_rec->encap)
>>>>>>> -                sbrec_port_binding_set_encap(binding_rec, NULL);
>>>>>>> -            sbrec_port_binding_set_chassis(binding_rec, NULL);
>>>>>>> -            any_changes = true;
>>>>>>> +    struct local_binding *lbinding =
>>>>>>> +        local_binding_find(b_ctx_out->local_bindings,
>>>> pb->logical_port);
>>>>>>> +
>>>>>>> +    ovs_assert(lbinding);
>>>>>>> +
>>>>>>> +    struct shash_node *node;
>>>>>>> +    SHASH_FOR_EACH (node, &lbinding->children) {
>>>>>>> +        struct local_binding *child = node->data;
>>>>>>> +        if (child->type == BT_CONTAINER) {
>>>>>>> +            handled = consider_container_lport(child->pb,
>> b_ctx_in,
>>>>>> b_ctx_out,
>>>>>>> +                                               qos_map);
>>>>>>> +            if (!handled) {
>>>>>>> +                return false;
>>>>>>> +            }
>>>>>>>           }
>>>>>>>       }
>>>>>>>
>>>>>>> -    if (any_changes) {
>>>>>>> -        ovsdb_idl_txn_add_comment(
>>>>>>> -            ovnsb_idl_txn,
>>>>>>> -            "ovn-controller: removing all port bindings for '%s'",
>>>>>>> -            chassis_rec->name);
>>>>>>> +    return true;
>>>>>>> +}
>>>>>>> +
>>>>>>> +/* Returns true if the port binding changes resulted in local
>> binding
>>>>>>> + * updates, false otherwise.
>>>>>>> + */
>>>>>>> +bool
>>>>>>> +binding_handle_port_binding_changes(struct binding_ctx_in
>> *b_ctx_in,
>>>>>>> +                                    struct binding_ctx_out
>> *b_ctx_out,
>>>>>>> +                                    bool *changed)
>>>>>>> +{
>>>>>>> +    bool handled = true;
>>>>>>> +    *changed = false;
>>>>>>> +
>>>>>>> +    const struct sbrec_port_binding *pb;
>>>>>>> +
>>>>>>> +    /* Run the tracked port binding loop twice. One to handle
>> deleted
>>>>>>> +     * changes. And another to handle add/update changes.
>>>>>>> +     * This will ensure correctness.
>>>>>>> +     */
>>>>>>> +    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
>>>>>>> +
>>>>>> b_ctx_in->port_binding_table) {
>>>>>>> +        if (!sbrec_port_binding_is_deleted(pb)) {
>>>>>>> +            continue;
>>>>>>> +        }
>>>>>>> +
>>>>>>> +        enum en_lport_type lport_type = get_lport_type(pb);
>>>>>>> +        if (lport_type == LP_VIF || lport_type == LP_VIRTUAL) {
>>>>>>> +            handled = handle_deleted_vif_lport(pb, lport_type,
>>>> b_ctx_in,
>>>>>>> +                                                b_ctx_out,
>> changed);
>>>>>>> +        } else {
>>>>>>> +            handle_deleted_lport(pb, b_ctx_in, b_ctx_out);
>>>>>>> +        }
>>>>>>> +
>>>>>>> +        if (!handled) {
>>>>>>> +            break;
>>>>>>> +        }
>>>>>>>       }
>>>>>>>
>>>>>>> -    return !any_changes;
>>>>>>> +    if (!handled) {
>>>>>>> +        return false;
>>>>>>> +    }
>>>>>>> +
>>>>>>> +    struct hmap qos_map = HMAP_INITIALIZER(&qos_map);
>>>>>>> +    struct hmap *qos_map_ptr =
>>>>>>> +        sset_is_empty(b_ctx_out->egress_ifaces) ? NULL : &qos_map;
>>>>>>> +
>>>>>>> +    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
>>>>>>> +
>>>>>> b_ctx_in->port_binding_table) {
>>>>>>> +        /* Loop to handle create and update changes only. */
>>>>>>> +        if (sbrec_port_binding_is_deleted(pb)) {
>>>>>>> +            continue;
>>>>>>> +        }
>>>>>>> +
>>>>>>> +        enum en_lport_type lport_type = get_lport_type(pb);
>>>>>>> +
>>>>>>> +        struct local_datapath *ld =
>>>>>>> +            get_local_datapath(b_ctx_out->local_datapaths,
>>>>>>> +                               pb->datapath->tunnel_key);
>>>>>>> +
>>>>>>> +        switch (lport_type) {
>>>>>>> +        case LP_VIF:
>>>>>>> +        case LP_VIRTUAL:
>>>>>>> +            handled = handle_updated_vif_lport(pb, lport_type,
>>>> b_ctx_in,
>>>>>>> +                                               b_ctx_out,
>> qos_map_ptr,
>>>>>>> +                                               changed);
>>>>>>> +            break;
>>>>>>> +
>>>>>>> +        case LP_PATCH:
>>>>>>> +        case LP_LOCALPORT:
>>>>>>> +        case LP_VTEP:
>>>>>>> +            *changed = true;
>>>>>>> +            update_local_lport_ids(b_ctx_out->local_lport_ids,
>> pb);
>>>>>>> +            if (lport_type ==  LP_PATCH) {
>>>>>>> +                /* Add the peer datapath to the local datapaths if
>>>> it's
>>>>>>> +                    * not present yet.
>>>>>>> +                    */
>>>>>>> +                if (ld) {
>>>>>>> +                    add_local_datapath_peer_port(pb, b_ctx_in,
>>>>>>> +                                                    b_ctx_out,
>> ld);
>>>>>>> +                }
>>>>>>> +            }
>>>>>>> +            break;
>>>>>>> +
>>>>>>> +        case LP_L2GATEWAY:
>>>>>>> +            *changed = true;
>>>>>>> +            handled = consider_l2gw_lport(pb, b_ctx_in,
>> b_ctx_out);
>>>>>>> +            break;
>>>>>>> +
>>>>>>> +        case LP_L3GATEWAY:
>>>>>>> +            *changed = true;
>>>>>>> +            handled = consider_l3gw_lport(pb, b_ctx_in,
>> b_ctx_out);
>>>>>>> +            break;
>>>>>>> +
>>>>>>> +        case LP_CHASSISREDIRECT:
>>>>>>> +            *changed = true;
>>>>>>> +            handled = consider_cr_lport(pb, b_ctx_in, b_ctx_out);
>>>>>>> +            break;
>>>>>>> +
>>>>>>> +        case LP_EXTERNAL:
>>>>>>> +            *changed = true;
>>>>>>> +            handled = consider_external_lport(pb, b_ctx_in,
>>>> b_ctx_out);
>>>>>>> +            break;
>>>>>>> +
>>>>>>> +        case LP_LOCALNET: {
>>>>>>> +            *changed = true;
>>>>>>> +            consider_localnet_lport(pb, b_ctx_in, b_ctx_out,
>>>>>> qos_map_ptr);
>>>>>>> +
>>>>>>> +            struct shash bridge_mappings =
>>>>>>> +                SHASH_INITIALIZER(&bridge_mappings);
>>>>>>> +            add_ovs_bridge_mappings(b_ctx_in->ovs_table,
>>>>>>> +                                    b_ctx_in->bridge_table,
>>>>>>> +                                    &bridge_mappings);
>>>>>>> +            update_ld_localnet_port(pb, &bridge_mappings,
>>>>>>> +                                    b_ctx_out->egress_ifaces,
>>>>>>> +                                    b_ctx_out->local_datapaths);
>>>>>>> +            shash_destroy(&bridge_mappings);
>>>>>>> +            break;
>>>>>>> +        }
>>>>>>> +
>>>>>>> +        case LP_REMOTE:
>>>>>>> +        case LP_UNKNOWN:
>>>>>>> +            break;
>>>>>>> +        }
>>>>>>> +
>>>>>>> +        if (!handled) {
>>>>>>> +            break;
>>>>>>> +        }
>>>>>>> +    }
>>>>>>> +
>>>>>>> +    if (handled && qos_map_ptr &&
>> set_noop_qos(b_ctx_in->ovs_idl_txn,
>>>>>>> +
>> b_ctx_in->port_table,
>>>>>>> +
>>   b_ctx_in->qos_table,
>>>>>>> +
>>>>>> b_ctx_out->egress_ifaces)) {
>>>>>>> +        const char *entry;
>>>>>>> +        SSET_FOR_EACH (entry, b_ctx_out->egress_ifaces) {
>>>>>>> +            setup_qos(entry, &qos_map);
>>>>>>> +        }
>>>>>>> +    }
>>>>>>> +
>>>>>>> +    destroy_qos_map(&qos_map);
>>>>>>> +    return handled;
>>>>>>>   }
>>>>>>> diff --git a/controller/binding.h b/controller/binding.h
>>>>>>> index 9affc9a96..f7ace6faf 100644
>>>>>>> --- a/controller/binding.h
>>>>>>> +++ b/controller/binding.h
>>>>>>> @@ -57,6 +57,7 @@ struct binding_ctx_out {
>>>>>>>       struct sset *local_lports;
>>>>>>>       struct sset *local_lport_ids;
>>>>>>>       struct sset *egress_ifaces;
>>>>>>> +    struct smap *local_iface_ids;
>>>>>>>   };
>>>>>>>
>>>>>>>   void binding_register_ovs_idl(struct ovsdb_idl *);
>>>>>>> @@ -64,9 +65,13 @@ void binding_run(struct binding_ctx_in *, struct
>>>>>> binding_ctx_out *);
>>>>>>>   bool binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn,
>>>>>>>                        const struct sbrec_port_binding_table *,
>>>>>>>                        const struct sbrec_chassis *);
>>>>>>> -bool binding_evaluate_port_binding_changes(struct binding_ctx_in
>> *,
>>>>>>> -                                           struct binding_ctx_out
>> *);
>>>>>>>
>>>>>>>   void local_bindings_init(struct shash *local_bindings);
>>>>>>>   void local_bindings_destroy(struct shash *local_bindings);
>>>>>>> +bool binding_handle_ovs_interface_changes(struct binding_ctx_in *,
>>>>>>> +                                          struct binding_ctx_out
>> *,
>>>>>>> +                                          bool *changed);
>>>>>>> +bool binding_handle_port_binding_changes(struct binding_ctx_in *,
>>>>>>> +                                         struct binding_ctx_out *,
>>>>>>> +                                         bool *changed);
>>>>>>>   #endif /* controller/binding.h */
>>>>>>> diff --git a/controller/ovn-controller.c
>> b/controller/ovn-controller.c
>>>>>>> index 6b759966b..08b074415 100644
>>>>>>> --- a/controller/ovn-controller.c
>>>>>>> +++ b/controller/ovn-controller.c
>>>>>>> @@ -753,6 +753,7 @@ enum sb_engine_node {
>>>>>>>       OVS_NODE(open_vswitch, "open_vswitch") \
>>>>>>>       OVS_NODE(bridge, "bridge") \
>>>>>>>       OVS_NODE(port, "port") \
>>>>>>> +    OVS_NODE(interface, "interface") \
>>>>>>>       OVS_NODE(qos, "qos")
>>>>>>>
>>>>>>>   enum ovs_engine_node {
>>>>>>> @@ -974,6 +975,7 @@ struct ed_type_runtime_data {
>>>>>>>       struct sset active_tunnels;
>>>>>>>
>>>>>>>       struct sset egress_ifaces;
>>>>>>> +    struct smap local_iface_ids;
>>>>>>>   };
>>>>>>>
>>>>>>>   static void *
>>>>>>> @@ -987,6 +989,7 @@ en_runtime_data_init(struct engine_node *node
>>>>>> OVS_UNUSED,
>>>>>>>       sset_init(&data->local_lport_ids);
>>>>>>>       sset_init(&data->active_tunnels);
>>>>>>>       sset_init(&data->egress_ifaces);
>>>>>>> +    smap_init(&data->local_iface_ids);
>>>>>>>       local_bindings_init(&data->local_bindings);
>>>>>>>       return data;
>>>>>>>   }
>>>>>>> @@ -1000,6 +1003,7 @@ en_runtime_data_cleanup(void *data)
>>>>>>>       sset_destroy(&rt_data->local_lport_ids);
>>>>>>>       sset_destroy(&rt_data->active_tunnels);
>>>>>>>       sset_destroy(&rt_data->egress_ifaces);
>>>>>>> +    smap_destroy(&rt_data->local_iface_ids);
>>>>>>>       struct local_datapath *cur_node, *next_node;
>>>>>>>       HMAP_FOR_EACH_SAFE (cur_node, next_node, hmap_node,
>>>>>>>                           &rt_data->local_datapaths) {
>>>>>>> @@ -1041,6 +1045,10 @@ init_binding_ctx(struct engine_node *node,
>>>>>>>           (struct ovsrec_port_table *)EN_OVSDB_GET(
>>>>>>>               engine_get_input("OVS_port", node));
>>>>>>>
>>>>>>> +    struct ovsrec_interface_table *iface_table =
>>>>>>> +        (struct ovsrec_interface_table *)EN_OVSDB_GET(
>>>>>>> +            engine_get_input("OVS_interface", node));
>>>>>>> +
>>>>>>>       struct ovsrec_qos_table *qos_table =
>>>>>>>           (struct ovsrec_qos_table *)EN_OVSDB_GET(
>>>>>>>               engine_get_input("OVS_qos", node));
>>>>>>> @@ -1070,6 +1078,7 @@ init_binding_ctx(struct engine_node *node,
>>>>>>>       b_ctx_in->sbrec_port_binding_by_datapath =
>>>>>> sbrec_port_binding_by_datapath;
>>>>>>>       b_ctx_in->sbrec_port_binding_by_name =
>> sbrec_port_binding_by_name;
>>>>>>>       b_ctx_in->port_table = port_table;
>>>>>>> +    b_ctx_in->iface_table = iface_table;
>>>>>>>       b_ctx_in->qos_table = qos_table;
>>>>>>>       b_ctx_in->port_binding_table = pb_table;
>>>>>>>       b_ctx_in->br_int = br_int;
>>>>>>> @@ -1083,6 +1092,7 @@ init_binding_ctx(struct engine_node *node,
>>>>>>>       b_ctx_out->local_lport_ids = &rt_data->local_lport_ids;
>>>>>>>       b_ctx_out->egress_ifaces = &rt_data->egress_ifaces;
>>>>>>>       b_ctx_out->local_bindings = &rt_data->local_bindings;
>>>>>>> +    b_ctx_out->local_iface_ids = &rt_data->local_iface_ids;
>>>>>>>   }
>>>>>>>
>>>>>>>   static void
>>>>>>> @@ -1111,10 +1121,12 @@ en_runtime_data_run(struct engine_node
>> *node,
>>>>>> void *data)
>>>>>>>           sset_destroy(local_lport_ids);
>>>>>>>           sset_destroy(active_tunnels);
>>>>>>>           sset_destroy(&rt_data->egress_ifaces);
>>>>>>> +        smap_destroy(&rt_data->local_iface_ids);
>>>>>>>           sset_init(local_lports);
>>>>>>>           sset_init(local_lport_ids);
>>>>>>>           sset_init(active_tunnels);
>>>>>>>           sset_init(&rt_data->egress_ifaces);
>>>>>>> +        smap_init(&rt_data->local_iface_ids);
>>>>>>>           local_bindings_init(&rt_data->local_bindings);
>>>>>>>       }
>>>>>>>
>>>>>>> @@ -1140,6 +1152,34 @@ en_runtime_data_run(struct engine_node
>> *node,
>>>> void
>>>>>> *data)
>>>>>>>       engine_set_node_state(node, EN_UPDATED);
>>>>>>>   }
>>>>>>>
>>>>>>> +static bool
>>>>>>> +runtime_data_ovs_interface_handler(struct engine_node *node, void
>>>> *data)
>>>>>>> +{
>>>>>>> +    struct ed_type_runtime_data *rt_data = data;
>>>>>>> +    struct binding_ctx_in b_ctx_in;
>>>>>>> +    struct binding_ctx_out b_ctx_out;
>>>>>>> +    init_binding_ctx(node, rt_data, &b_ctx_in, &b_ctx_out);
>>>>>>> +
>>>>>>> +    bool changed = false;
>>>>>>> +    if (!binding_handle_ovs_interface_changes(&b_ctx_in,
>> &b_ctx_out,
>>>>>>> +                                              &changed)) {
>>>>>>> +        return false;
>>>>>>> +    }
>>>>>>> +
>>>>>>> +    if (changed) {
>>>>>>> +        engine_set_node_state(node, EN_UPDATED);
>>>>>>> +    }
>>>>>>> +
>>>>>>> +    return true;
>>>>>>> +}
>>>>>>> +
>>>>>>> +static bool
>>>>>>> +runtime_data_noop_handler(struct engine_node *node OVS_UNUSED,
>>>>>>> +                          void *data OVS_UNUSED)
>>>>>>> +{
>>>>>>> +    return true;
>>>>>>> +}
>>>>>>> +
>>>>>>>   static bool
>>>>>>>   runtime_data_sb_port_binding_handler(struct engine_node *node,
>> void
>>>>>> *data)
>>>>>>>   {
>>>>>>> @@ -1147,11 +1187,21 @@ runtime_data_sb_port_binding_handler(struct
>>>>>> engine_node *node, void *data)
>>>>>>>       struct binding_ctx_in b_ctx_in;
>>>>>>>       struct binding_ctx_out b_ctx_out;
>>>>>>>       init_binding_ctx(node, rt_data, &b_ctx_in, &b_ctx_out);
>>>>>>> +    if (!b_ctx_in.chassis_rec) {
>>>>>>> +        return false;
>>>>>>> +    }
>>>>>>> +
>>>>>>> +    bool changed = false;
>>>>>>> +    if (!binding_handle_port_binding_changes(&b_ctx_in,
>> &b_ctx_out,
>>>>>>> +                                             &changed)) {
>>>>>>> +        return false;
>>>>>>> +    }
>>>>>>>
>>>>>>> -    bool changed =
>> binding_evaluate_port_binding_changes(&b_ctx_in,
>>>>>>> -
>> &b_ctx_out);
>>>>>>> +    if (changed) {
>>>>>>> +        engine_set_node_state(node, EN_UPDATED);
>>>>>>> +    }
>>>>>>>
>>>>>>> -    return !changed;
>>>>>>> +    return true;
>>>>>>>   }
>>>>>>>
>>>>>>>   /* Connection tracking zones. */
>>>>>>> @@ -1894,7 +1944,10 @@ main(int argc, char *argv[])
>>>>>>>
>>>>>>>       engine_add_input(&en_runtime_data, &en_ovs_open_vswitch,
>> NULL);
>>>>>>>       engine_add_input(&en_runtime_data, &en_ovs_bridge, NULL);
>>>>>>> -    engine_add_input(&en_runtime_data, &en_ovs_port, NULL);
>>>>>>> +    engine_add_input(&en_runtime_data, &en_ovs_port,
>>>>>>> +                     runtime_data_noop_handler);
>>>>>>> +    engine_add_input(&en_runtime_data, &en_ovs_interface,
>>>>>>> +                     runtime_data_ovs_interface_handler);
>>>>>>>       engine_add_input(&en_runtime_data, &en_ovs_qos, NULL);
>>>>>>>
>>>>>>>       engine_add_input(&en_runtime_data, &en_sb_chassis, NULL);
>>>>>>> diff --git a/tests/ovn-performance.at b/tests/ovn-performance.at
>>>>>>> index a8a15f8fe..5dfc6f7ca 100644
>>>>>>> --- a/tests/ovn-performance.at
>>>>>>> +++ b/tests/ovn-performance.at
>>>>>>> @@ -239,6 +239,19 @@ for i in 1 2; do
>>>>>>>       ovn_attach n1 br-phys 192.168.0.$i
>>>>>>>   done
>>>>>>>
>>>>>>> +# Wait for the tunnel ports to be created and up.
>>>>>>> +# Otherwise this may affect the lflow_run count.
>>>>>>> +
>>>>>>> +OVS_WAIT_UNTIL([
>>>>>>> +    test $(as hv1 ovs-vsctl list interface ovn-hv2-0 | \
>>>>>>> +grep tunnel_egress_iface_carrier=up | wc -l) -eq 1
>>>>>>> +])
>>>>>>> +
>>>>>>> +OVS_WAIT_UNTIL([
>>>>>>> +    test $(as hv2 ovs-vsctl list interface ovn-hv1-0 | \
>>>>>>> +grep tunnel_egress_iface_carrier=up | wc -l) -eq 1
>>>>>>> +])
>>>>>>> +
>>>>>>>   # Add router lr1
>>>>>>>   OVN_CONTROLLER_EXPECT_HIT(
>>>>>>>       [hv1 hv2], [lflow_run],
>>>>>>> --
>>>>>>> 2.26.2
>>>>>>>
>>>>>>> _______________________________________________
>>>>>>> dev mailing list
>>>>>>> dev@openvswitch.org
>>>>>>> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>>>>>> _______________________________________________
>>>>>> dev mailing list
>>>>>> dev@openvswitch.org
>>>>>> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>>>>>>
>>>> _______________________________________________
>>>> dev mailing list
>>>> dev@openvswitch.org
>>>> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>>>>
>> _______________________________________________
>> dev mailing list
>> dev@openvswitch.org
>> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>>
>>
> _______________________________________________
> 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 2997fcae8..d5e8e4773 100644
--- a/controller/binding.c
+++ b/controller/binding.c
@@ -360,17 +360,6 @@  destroy_qos_map(struct hmap *qos_map)
     hmap_destroy(qos_map);
 }
 
-static void
-update_local_lport_ids(struct sset *local_lport_ids,
-                       const struct sbrec_port_binding *binding_rec)
-{
-        char buf[16];
-        snprintf(buf, sizeof(buf), "%"PRId64"_%"PRId64,
-                 binding_rec->datapath->tunnel_key,
-                 binding_rec->tunnel_key);
-        sset_add(local_lport_ids, buf);
-}
-
 /*
  * Get the encap from the chassis for this port. The interface
  * may have an external_ids:encap-ip=<encap-ip> set; if so we
@@ -449,10 +438,10 @@  is_network_plugged(const struct sbrec_port_binding *binding_rec,
 }
 
 static void
-consider_localnet_port(const struct sbrec_port_binding *binding_rec,
-                       struct shash *bridge_mappings,
-                       struct sset *egress_ifaces,
-                       struct hmap *local_datapaths)
+update_ld_localnet_port(const struct sbrec_port_binding *binding_rec,
+                        struct shash *bridge_mappings,
+                        struct sset *egress_ifaces,
+                        struct hmap *local_datapaths)
 {
     /* Ignore localnet ports for unplugged networks. */
     if (!is_network_plugged(binding_rec, bridge_mappings)) {
@@ -482,6 +471,28 @@  consider_localnet_port(const struct sbrec_port_binding *binding_rec,
     ld->localnet_port = binding_rec;
 }
 
+static void
+update_local_lport_ids(struct sset *local_lport_ids,
+                       const struct sbrec_port_binding *binding_rec)
+{
+        char buf[16];
+        snprintf(buf, sizeof(buf), "%"PRId64"_%"PRId64,
+                 binding_rec->datapath->tunnel_key,
+                 binding_rec->tunnel_key);
+        sset_add(local_lport_ids, buf);
+}
+
+static void
+remove_local_lport_ids(const struct sbrec_port_binding *binding_rec,
+                       struct sset *local_lport_ids)
+{
+        char buf[16];
+        snprintf(buf, sizeof(buf), "%"PRId64"_%"PRId64,
+                 binding_rec->datapath->tunnel_key,
+                 binding_rec->tunnel_key);
+        sset_find_and_delete(local_lport_ids, buf);
+}
+
 /* Local bindings. binding.c module binds the logical port (represented by
  * Port_Binding rows) and sets the 'chassis' column when it is sees the
  * OVS interface row (of type "" or "internal") with the
@@ -599,6 +610,14 @@  local_bindings_destroy(struct shash *local_bindings)
     shash_destroy(local_bindings);
 }
 
+static
+void local_binding_delete(struct shash *local_bindings,
+                          struct local_binding *lbinding)
+{
+    shash_find_and_delete(local_bindings, lbinding->name);
+    local_binding_destroy(lbinding);
+}
+
 static void
 local_binding_add_child(struct local_binding *lbinding,
                         struct local_binding *child)
@@ -625,6 +644,7 @@  is_lport_container(const struct sbrec_port_binding *pb)
     return !pb->type[0] && pb->parent_port && pb->parent_port[0];
 }
 
+/* Corresponds to each Port_Binding.type. */
 enum en_lport_type {
     LP_UNKNOWN,
     LP_VIF,
@@ -670,12 +690,20 @@  get_lport_type(const struct sbrec_port_binding *pb)
     return LP_UNKNOWN;
 }
 
-static void
+/* Returns false if lport is not claimed due to 'sb_readonly'.
+ * Returns true otherwise.
+ */
+static bool
 claim_lport(const struct sbrec_port_binding *pb,
             const struct sbrec_chassis *chassis_rec,
-            const struct ovsrec_interface *iface_rec)
+            const struct ovsrec_interface *iface_rec,
+            bool sb_readonly)
 {
     if (pb->chassis != chassis_rec) {
+        if (sb_readonly) {
+            return false;
+        }
+
         if (pb->chassis) {
             VLOG_INFO("Changing chassis for lport %s from %s to %s.",
                     pb->logical_port, pb->chassis->name,
@@ -694,26 +722,48 @@  claim_lport(const struct sbrec_port_binding *pb,
     struct sbrec_encap *encap_rec =
         sbrec_get_port_encap(chassis_rec, iface_rec);
     if (encap_rec && pb->encap != encap_rec) {
+        if (sb_readonly) {
+            return false;
+        }
         sbrec_port_binding_set_encap(pb, encap_rec);
     }
+
+    return true;
 }
 
-static void
-release_lport(const struct sbrec_port_binding *pb)
+/* Returns false if lport is not released due to 'sb_readonly'.
+ * Returns true otherwise.
+ */
+static bool
+release_lport(const struct sbrec_port_binding *pb, bool sb_readonly)
 {
     if (!pb) {
-        return;
+        return true;
     }
 
-    VLOG_INFO("Releasing lport %s from this chassis.", pb->logical_port);
     if (pb->encap) {
+        if (sb_readonly) {
+            return false;
+        }
         sbrec_port_binding_set_encap(pb, NULL);
     }
-    sbrec_port_binding_set_chassis(pb, NULL);
+
+    if (pb->chassis) {
+        if (sb_readonly) {
+            return false;
+        }
+        sbrec_port_binding_set_chassis(pb, NULL);
+    }
 
     if (pb->virtual_parent) {
+        if (sb_readonly) {
+            return false;
+        }
         sbrec_port_binding_set_virtual_parent(pb, NULL);
     }
+
+    VLOG_INFO("Releasing lport %s from this chassis.", pb->logical_port);
+    return true;
 }
 
 static bool
@@ -738,7 +788,41 @@  can_bind_on_this_chassis(const struct sbrec_chassis *chassis_rec,
            || !strcmp(requested_chassis, chassis_rec->hostname);
 }
 
-static void
+static bool
+release_local_binding_children(const struct sbrec_chassis *chassis_rec,
+                               struct local_binding *lbinding,
+                               bool sb_readonly)
+{
+    struct shash_node *node;
+    SHASH_FOR_EACH (node, &lbinding->children) {
+        struct local_binding *l = node->data;
+        if (is_lbinding_this_chassis(l, chassis_rec)) {
+            if (!release_lport(l->pb, sb_readonly)) {
+                return false;
+            }
+        }
+    }
+
+    return true;
+}
+
+static bool
+release_local_binding(const struct sbrec_chassis *chassis_rec,
+                      struct local_binding *lbinding, bool sb_readonly)
+{
+    if (!release_local_binding_children(chassis_rec, lbinding,
+                                        sb_readonly)) {
+        return false;
+    }
+
+    if (is_lbinding_this_chassis(lbinding, chassis_rec)) {
+        return release_lport(lbinding->pb, sb_readonly);
+    }
+
+    return true;
+}
+
+static bool
 consider_vif_lport_(const struct sbrec_port_binding *pb,
                     bool can_bind, const char *vif_chassis,
                     struct binding_ctx_in *b_ctx_in,
@@ -750,7 +834,10 @@  consider_vif_lport_(const struct sbrec_port_binding *pb,
     if (lbinding_set) {
         if (can_bind) {
             /* We can claim the lport. */
-            claim_lport(pb, b_ctx_in->chassis_rec, lbinding->iface);
+            if (!claim_lport(pb, b_ctx_in->chassis_rec, lbinding->iface,
+                             !b_ctx_in->ovnsb_idl_txn)){
+                return false;
+            }
 
             add_local_datapath(b_ctx_in->sbrec_datapath_binding_by_key,
                             b_ctx_in->sbrec_port_binding_by_datapath,
@@ -775,13 +862,14 @@  consider_vif_lport_(const struct sbrec_port_binding *pb,
     if (pb->chassis == b_ctx_in->chassis_rec) {
         /* Release the lport if there is no lbinding. */
         if (!lbinding_set || !can_bind) {
-            release_lport(pb);
+            return release_lport(pb, !b_ctx_in->ovnsb_idl_txn);
         }
     }
 
+    return true;
 }
 
-static void
+static bool
 consider_vif_lport(const struct sbrec_port_binding *pb,
                    struct binding_ctx_in *b_ctx_in,
                    struct binding_ctx_out *b_ctx_out,
@@ -801,11 +889,11 @@  consider_vif_lport(const struct sbrec_port_binding *pb,
         lbinding->pb = pb;
     }
 
-    consider_vif_lport_(pb, can_bind, vif_chassis, b_ctx_in,
-                        b_ctx_out, lbinding, qos_map);
+    return consider_vif_lport_(pb, can_bind, vif_chassis, b_ctx_in,
+                               b_ctx_out, lbinding, qos_map);
 }
 
-static void
+static bool
 consider_container_lport(const struct sbrec_port_binding *pb,
                          struct binding_ctx_in *b_ctx_in,
                          struct binding_ctx_out *b_ctx_out,
@@ -815,7 +903,39 @@  consider_container_lport(const struct sbrec_port_binding *pb,
     parent_lbinding = local_binding_find(b_ctx_out->local_bindings,
                                          pb->parent_port);
 
-    if (parent_lbinding && !parent_lbinding->pb) {
+    if (!parent_lbinding) {
+        /* There is no local_binding for parent port. Create it
+         * without OVS interface row. This is the only exception
+         * for creating the 'struct local_binding' object without
+         * corresponding OVS interface row.
+         *
+         * This is required for the following reasons:
+         *   - If a logical port P1 is created and then
+         *     few container ports - C1, C2, .. are created first by CMS.
+         *   - And later when OVS interface row  is created for P1, then
+         *     we want the these container ports also be claimed by the
+         *     chassis.
+         * */
+        parent_lbinding = local_binding_create(pb->parent_port, NULL, NULL,
+                                               BT_VIF);
+        local_binding_add(b_ctx_out->local_bindings, parent_lbinding);
+    }
+
+    struct local_binding *container_lbinding =
+        local_binding_find_child(parent_lbinding, pb->logical_port);
+
+    if (!container_lbinding) {
+        container_lbinding = local_binding_create(pb->logical_port,
+                                                  parent_lbinding->iface,
+                                                  pb, BT_CONTAINER);
+        local_binding_add_child(parent_lbinding, container_lbinding);
+    } else {
+        ovs_assert(container_lbinding->type == BT_CONTAINER);
+        container_lbinding->pb = pb;
+        container_lbinding->iface = parent_lbinding->iface;
+    }
+
+    if (!parent_lbinding->pb) {
         parent_lbinding->pb = lport_lookup_by_name(
             b_ctx_in->sbrec_port_binding_by_name, pb->parent_port);
 
@@ -824,37 +944,28 @@  consider_container_lport(const struct sbrec_port_binding *pb,
              * So call consider_vif_lport() to process it first. */
             consider_vif_lport(parent_lbinding->pb, b_ctx_in, b_ctx_out,
                                parent_lbinding, qos_map);
-        }
-    }
+        } else {
+            /* The parent lport doesn't exist. Call release_lport() to
+             * release the container lport, if it was bound earlier. */
+            if (is_lbinding_this_chassis(container_lbinding,
+                                         b_ctx_in->chassis_rec)) {
+               return release_lport(pb, !b_ctx_in->ovnsb_idl_txn);
+            }
 
-    if (!parent_lbinding || !parent_lbinding->pb) {
-        /* Call release_lport, to release the container lport, if
-         * it was bound earlier. */
-        if (pb->chassis == b_ctx_in->chassis_rec) {
-            release_lport(pb);
+            return true;
         }
-        return;
     }
 
-    struct local_binding *container_lbinding =
-        local_binding_find_child(parent_lbinding, pb->logical_port);
-    ovs_assert(!container_lbinding);
-
-    container_lbinding = local_binding_create(pb->logical_port,
-                                              parent_lbinding->iface,
-                                              pb, BT_CONTAINER);
-    local_binding_add_child(parent_lbinding, container_lbinding);
-
     const char *vif_chassis = smap_get(&parent_lbinding->pb->options,
                                        "requested-chassis");
     bool can_bind = can_bind_on_this_chassis(b_ctx_in->chassis_rec,
                                              vif_chassis);
 
-    consider_vif_lport_(pb, can_bind, vif_chassis, b_ctx_in, b_ctx_out,
-                        container_lbinding, qos_map);
+    return consider_vif_lport_(pb, can_bind, vif_chassis, b_ctx_in, b_ctx_out,
+                               container_lbinding, qos_map);
 }
 
-static void
+static bool
 consider_virtual_lport(const struct sbrec_port_binding *pb,
                        struct binding_ctx_in *b_ctx_in,
                        struct binding_ctx_out *b_ctx_out,
@@ -881,11 +992,16 @@  consider_virtual_lport(const struct sbrec_port_binding *pb,
     if (is_lbinding_this_chassis(parent_lbinding, b_ctx_in->chassis_rec)) {
         virtual_lbinding =
             local_binding_find_child(parent_lbinding, pb->logical_port);
-        ovs_assert(!virtual_lbinding);
-        virtual_lbinding = local_binding_create(pb->logical_port,
-                                                parent_lbinding->iface,
-                                                pb, BT_VIRTUAL);
-        local_binding_add_child(parent_lbinding, virtual_lbinding);
+        if (!virtual_lbinding) {
+            virtual_lbinding = local_binding_create(pb->logical_port,
+                                                    parent_lbinding->iface,
+                                                    pb, BT_VIRTUAL);
+            local_binding_add_child(parent_lbinding, virtual_lbinding);
+        } else {
+            ovs_assert(virtual_lbinding->type == BT_VIRTUAL);
+            virtual_lbinding->pb = pb;
+            virtual_lbinding->iface = parent_lbinding->iface;
+        }
     }
 
     return consider_vif_lport_(pb, true, NULL, b_ctx_in, b_ctx_out,
@@ -895,14 +1011,13 @@  consider_virtual_lport(const struct sbrec_port_binding *pb,
 /* Considers either claiming the lport or releasing the lport
  * for non VIF lports.
  */
-static void
+static bool
 consider_nonvif_lport_(const struct sbrec_port_binding *pb,
                        bool our_chassis,
                        bool has_local_l3gateway,
                        struct binding_ctx_in *b_ctx_in,
                        struct binding_ctx_out *b_ctx_out)
 {
-    ovs_assert(b_ctx_in->ovnsb_idl_txn);
     if (our_chassis) {
         sset_add(b_ctx_out->local_lports, pb->logical_port);
         add_local_datapath(b_ctx_in->sbrec_datapath_binding_by_key,
@@ -912,13 +1027,16 @@  consider_nonvif_lport_(const struct sbrec_port_binding *pb,
                            b_ctx_out->local_datapaths);
 
         update_local_lport_ids(b_ctx_out->local_lport_ids, pb);
-        claim_lport(pb, b_ctx_in->chassis_rec, NULL);
+        return claim_lport(pb, b_ctx_in->chassis_rec, NULL,
+                           !b_ctx_in->ovnsb_idl_txn);
     } else if (pb->chassis == b_ctx_in->chassis_rec) {
-        release_lport(pb);
+        return release_lport(pb, !b_ctx_in->ovnsb_idl_txn);
     }
+
+    return true;
 }
 
-static void
+static bool
 consider_l2gw_lport(const struct sbrec_port_binding *pb,
                     struct binding_ctx_in *b_ctx_in,
                     struct binding_ctx_out *b_ctx_out)
@@ -927,10 +1045,10 @@  consider_l2gw_lport(const struct sbrec_port_binding *pb,
     bool our_chassis = chassis_id && !strcmp(chassis_id,
                                              b_ctx_in->chassis_rec->name);
 
-    consider_nonvif_lport_(pb, our_chassis, false, b_ctx_in, b_ctx_out);
+    return consider_nonvif_lport_(pb, our_chassis, false, b_ctx_in, b_ctx_out);
 }
 
-static void
+static bool
 consider_l3gw_lport(const struct sbrec_port_binding *pb,
                     struct binding_ctx_in *b_ctx_in,
                     struct binding_ctx_out *b_ctx_out)
@@ -939,7 +1057,7 @@  consider_l3gw_lport(const struct sbrec_port_binding *pb,
     bool our_chassis = chassis_id && !strcmp(chassis_id,
                                              b_ctx_in->chassis_rec->name);
 
-    consider_nonvif_lport_(pb, our_chassis, true, b_ctx_in, b_ctx_out);
+    return consider_nonvif_lport_(pb, our_chassis, true, b_ctx_in, b_ctx_out);
 }
 
 static void
@@ -958,7 +1076,7 @@  consider_localnet_lport(const struct sbrec_port_binding *pb,
     update_local_lport_ids(b_ctx_out->local_lport_ids, pb);
 }
 
-static void
+static bool
 consider_ha_lport(const struct sbrec_port_binding *pb,
                   struct binding_ctx_in *b_ctx_in,
                   struct binding_ctx_out *b_ctx_out)
@@ -986,18 +1104,18 @@  consider_ha_lport(const struct sbrec_port_binding *pb,
                            b_ctx_out->local_datapaths);
     }
 
-    consider_nonvif_lport_(pb, our_chassis, false, b_ctx_in, b_ctx_out);
+    return consider_nonvif_lport_(pb, our_chassis, false, b_ctx_in, b_ctx_out);
 }
 
-static void
+static bool
 consider_cr_lport(const struct sbrec_port_binding *pb,
                   struct binding_ctx_in *b_ctx_in,
                   struct binding_ctx_out *b_ctx_out)
 {
-    consider_ha_lport(pb, b_ctx_in, b_ctx_out);
+    return consider_ha_lport(pb, b_ctx_in, b_ctx_out);
 }
 
-static void
+static bool
 consider_external_lport(const struct sbrec_port_binding *pb,
                         struct binding_ctx_in *b_ctx_in,
                         struct binding_ctx_out *b_ctx_out)
@@ -1050,6 +1168,8 @@  build_local_bindings(struct binding_ctx_in *b_ctx_in,
                 }
 
                 sset_add(b_ctx_out->local_lports, iface_id);
+                smap_replace(b_ctx_out->local_iface_ids, iface_rec->name,
+                             iface_id);
             }
 
             /* Check if this is a tunnel interface. */
@@ -1165,9 +1285,9 @@  binding_run(struct binding_ctx_in *b_ctx_in, struct binding_ctx_out *b_ctx_out)
      * corresponding local datapath accordingly. */
     struct localnet_lport *lnet_lport;
     LIST_FOR_EACH_POP (lnet_lport, list_node, &localnet_lports) {
-        consider_localnet_port(lnet_lport->pb, &bridge_mappings,
-                               b_ctx_out->egress_ifaces,
-                               b_ctx_out->local_datapaths);
+        update_ld_localnet_port(lnet_lport->pb, &bridge_mappings,
+                                b_ctx_out->egress_ifaces,
+                                b_ctx_out->local_datapaths);
         free(lnet_lport);
     }
 
@@ -1185,95 +1305,625 @@  binding_run(struct binding_ctx_in *b_ctx_in, struct binding_ctx_out *b_ctx_out)
     destroy_qos_map(&qos_map);
 }
 
-/* Returns true if port-binding changes potentially require flow changes on
- * the current chassis. Returns false if we are sure there is no impact. */
+/* Returns true if the database is all cleaned up, false if more work is
+ * required. */
 bool
-binding_evaluate_port_binding_changes(struct binding_ctx_in *b_ctx_in,
-                                      struct binding_ctx_out *b_ctx_out)
+binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn,
+                const struct sbrec_port_binding_table *port_binding_table,
+                const struct sbrec_chassis *chassis_rec)
 {
-    if (!b_ctx_in->chassis_rec) {
+    if (!ovnsb_idl_txn) {
+        return false;
+    }
+    if (!chassis_rec) {
         return true;
     }
 
-    bool changed = false;
-
     const struct sbrec_port_binding *binding_rec;
-    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (binding_rec,
-                                               b_ctx_in->port_binding_table) {
-        /* XXX: currently OVSDB change tracking doesn't support getting old
-         * data when the operation is update, so if a port-binding moved from
-         * this chassis to another, there is no easy way to find out the
-         * change. To workaround this problem, we just makes sure if
-         * any port *related to* this chassis has any change, then trigger
-         * recompute.
-         *
-         * - If a regular VIF is unbound from this chassis, the local ovsdb
-         *   interface table will be updated, which will trigger recompute.
-         *
-         * - If the port is not a regular VIF, always trigger recompute. */
-        if (binding_rec->chassis == b_ctx_in->chassis_rec) {
-            changed = true;
+    bool any_changes = false;
+    SBREC_PORT_BINDING_TABLE_FOR_EACH (binding_rec, port_binding_table) {
+        if (binding_rec->chassis == chassis_rec) {
+            if (binding_rec->encap) {
+                sbrec_port_binding_set_encap(binding_rec, NULL);
+            }
+            sbrec_port_binding_set_chassis(binding_rec, NULL);
+            any_changes = true;
+        }
+    }
+
+    if (any_changes) {
+        ovsdb_idl_txn_add_comment(
+            ovnsb_idl_txn,
+            "ovn-controller: removing all port bindings for '%s'",
+            chassis_rec->name);
+    }
+
+    return !any_changes;
+}
+
+static void
+add_local_datapath_peer_port(const struct sbrec_port_binding *pb,
+                             struct binding_ctx_in *b_ctx_in,
+                             struct binding_ctx_out *b_ctx_out,
+                             struct local_datapath *ld)
+{
+    const char *peer_name = smap_get(&pb->options, "peer");
+    if (strcmp(pb->type, "patch") || !peer_name) {
+        return;
+    }
+
+    const struct sbrec_port_binding *peer;
+    peer = lport_lookup_by_name(b_ctx_in->sbrec_port_binding_by_name,
+                                peer_name);
+
+    if (!peer || !peer->datapath) {
+        return;
+    }
+
+    bool present = false;
+    for (size_t i = 0; i < ld->n_peer_ports; i++) {
+        if (ld->peer_ports[i].local == pb) {
+            present = true;
             break;
         }
+    }
 
-        if (!strcmp(binding_rec->type, "remote")) {
-            continue;
+    if (!present) {
+        ld->n_peer_ports++;
+        if (ld->n_peer_ports > ld->n_allocated_peer_ports) {
+            ld->peer_ports =
+                x2nrealloc(ld->peer_ports,
+                           &ld->n_allocated_peer_ports,
+                           sizeof *ld->peer_ports);
+        }
+        ld->peer_ports[ld->n_peer_ports - 1].local = pb;
+        ld->peer_ports[ld->n_peer_ports - 1].remote = peer;
+    }
+
+    struct local_datapath *peer_ld =
+        get_local_datapath(b_ctx_out->local_datapaths,
+                           peer->datapath->tunnel_key);
+    if (!peer_ld) {
+        add_local_datapath__(b_ctx_in->sbrec_datapath_binding_by_key,
+                             b_ctx_in->sbrec_port_binding_by_datapath,
+                             b_ctx_in->sbrec_port_binding_by_name,
+                             peer->datapath, false,
+                             1, b_ctx_out->local_datapaths);
+        return;
+    }
+
+    for (size_t i = 0; i < peer_ld->n_peer_ports; i++) {
+        if (peer_ld->peer_ports[i].local == peer) {
+            return;
         }
+    }
 
-        if (strcmp(binding_rec->type, "")) {
-            changed = true;
+    peer_ld->n_peer_ports++;
+    if (peer_ld->n_peer_ports > peer_ld->n_allocated_peer_ports) {
+        peer_ld->peer_ports =
+            x2nrealloc(peer_ld->peer_ports,
+                        &peer_ld->n_allocated_peer_ports,
+                        sizeof *peer_ld->peer_ports);
+    }
+    peer_ld->peer_ports[peer_ld->n_peer_ports - 1].local = peer;
+    peer_ld->peer_ports[peer_ld->n_peer_ports - 1].remote = pb;
+}
+
+static void
+remove_local_datapath_peer_port(const struct sbrec_port_binding *pb,
+                                struct local_datapath *ld,
+                                struct hmap *local_datapaths)
+{
+    size_t i = 0;
+    for (i = 0; i < ld->n_peer_ports; i++) {
+        if (ld->peer_ports[i].local == pb) {
             break;
         }
+    }
+
+    if (i == ld->n_peer_ports) {
+        return;
+    }
+
+    const struct sbrec_port_binding *peer = ld->peer_ports[i].remote;
 
-        struct local_binding *lbinding = NULL;
-        if (!binding_rec->parent_port || !binding_rec->parent_port[0]) {
-            lbinding = local_binding_find(b_ctx_out->local_bindings,
-                                          binding_rec->logical_port);
+    /* Possible improvement: We can shrint the allocated peer ports
+     * if (ld->n_peer_ports < ld->n_allocated_peer_ports / 2).
+     */
+    ld->peer_ports[i].local = ld->peer_ports[ld->n_peer_ports - 1].local;
+    ld->peer_ports[i].remote = ld->peer_ports[ld->n_peer_ports - 1].remote;
+    ld->n_peer_ports--;
+
+    struct local_datapath *peer_ld =
+        get_local_datapath(local_datapaths, peer->datapath->tunnel_key);
+    if (peer_ld) {
+        remove_local_datapath_peer_port(peer, peer_ld, local_datapaths);
+    }
+}
+
+static void
+remove_pb_from_local_datapath(const struct sbrec_port_binding *pb,
+                              const struct sbrec_chassis *chassis_rec,
+                              struct binding_ctx_out *b_ctx_out,
+                              struct local_datapath *ld)
+{
+    remove_local_lport_ids(pb, b_ctx_out->local_lport_ids);
+    if (!strcmp(pb->type, "patch") ||
+        !strcmp(pb->type, "l3gateway")) {
+        remove_local_datapath_peer_port(pb, ld, b_ctx_out->local_datapaths);
+    } else if (!strcmp(pb->type, "localnet")) {
+        if (ld->localnet_port && !strcmp(ld->localnet_port->logical_port,
+                                         pb->logical_port)) {
+            ld->localnet_port = NULL;
+        }
+    } else if (!strcmp(pb->type, "l3gateway")) {
+        const char *chassis_id = smap_get(&pb->options,
+                                          "l3gateway-chassis");
+        if (chassis_id && !strcmp(chassis_id, chassis_rec->name)) {
+            ld->has_local_l3gateway = false;
+        }
+    }
+}
+
+static bool
+handle_iface_add(const struct ovsrec_interface *iface_rec,
+                 const char *iface_id,
+                 struct binding_ctx_in *b_ctx_in,
+                 struct binding_ctx_out *b_ctx_out,
+                 struct hmap *qos_map)
+{
+    sset_add(b_ctx_out->local_lports, iface_id);
+    smap_replace(b_ctx_out->local_iface_ids, iface_rec->name, iface_id);
+
+    struct local_binding *lbinding =
+        local_binding_find(b_ctx_out->local_bindings, iface_id);
+
+    if (!lbinding) {
+        lbinding = local_binding_create(iface_id, iface_rec, NULL, BT_VIF);
+        local_binding_add(b_ctx_out->local_bindings, lbinding);
+    } else {
+        lbinding->iface = iface_rec;
+    }
+
+    if (!lbinding->pb || strcmp(lbinding->name, lbinding->pb->logical_port)) {
+        lbinding->pb = lport_lookup_by_name(
+            b_ctx_in->sbrec_port_binding_by_name, lbinding->name);
+        if (lbinding->pb && !strcmp(lbinding->pb->type, "virtual")) {
+            lbinding->pb = NULL;
+        }
+    }
+
+    if (lbinding->pb) {
+        if (!consider_vif_lport(lbinding->pb, b_ctx_in, b_ctx_out,
+                                lbinding, qos_map)) {
+            return false;
+        }
+    }
+
+    /* Update the child local_binding's iface (if any children) and try to
+     *  claim the container lbindings. */
+    struct shash_node *node;
+    SHASH_FOR_EACH (node, &lbinding->children) {
+        struct local_binding *child = node->data;
+        child->iface = iface_rec;
+        if (child->type == BT_CONTAINER) {
+            if (!consider_container_lport(child->pb, b_ctx_in, b_ctx_out,
+                                          qos_map)) {
+                return false;
+            }
+        }
+    }
+
+    return true;
+}
+
+static bool
+handle_iface_delete(const char *lport_name, const char *iface_name,
+                    struct binding_ctx_in *b_ctx_in,
+                    struct binding_ctx_out *b_ctx_out, bool *changed)
+{
+    struct local_binding *lbinding;
+    lbinding = local_binding_find(b_ctx_out->local_bindings,
+                                  lport_name);
+    if (lbinding && lbinding->pb &&
+        lbinding->pb->chassis == b_ctx_in->chassis_rec) {
+
+        if (!release_local_binding(b_ctx_in->chassis_rec, lbinding,
+                                   !b_ctx_in->ovnsb_idl_txn)) {
+            return false;
+        }
+
+        struct local_datapath *ld =
+            get_local_datapath(b_ctx_out->local_datapaths,
+                                lbinding->pb->datapath->tunnel_key);
+        if (ld) {
+            remove_pb_from_local_datapath(lbinding->pb,
+                                            b_ctx_in->chassis_rec,
+                                            b_ctx_out, ld);
+        }
+        local_binding_delete(b_ctx_out->local_bindings, lbinding);
+        *changed = true;
+    }
+
+    sset_find_and_delete(b_ctx_out->local_lports, lport_name);
+    smap_remove(b_ctx_out->local_iface_ids, iface_name);
+
+    return true;
+}
+
+static bool
+is_iface_vif(const struct ovsrec_interface *iface_rec)
+{
+    if (iface_rec->type && iface_rec->type[0] &&
+        strcmp(iface_rec->type, "internal")) {
+        return false;
+    }
+
+    return true;
+}
+
+/* Returns true if the ovs interface changes were handled successfully,
+ * false otherwise.
+ */
+bool
+binding_handle_ovs_interface_changes(struct binding_ctx_in *b_ctx_in,
+                                     struct binding_ctx_out *b_ctx_out,
+                                     bool *changed)
+{
+    if (!b_ctx_in->chassis_rec) {
+        return false;
+    }
+
+    bool handled = true;
+    *changed = false;
+
+    /* Run the tracked interfaces loop twice. One to handle deleted
+     * changes. And another to handle add/update changes.
+     * This will ensure correctness.
+     */
+    const struct ovsrec_interface *iface_rec;
+    OVSREC_INTERFACE_TABLE_FOR_EACH_TRACKED (iface_rec,
+                                             b_ctx_in->iface_table) {
+        if (!is_iface_vif(iface_rec)) {
+            /* Right now are not handling ovs_interface changes of
+             * other types. This can be enhanced to handle of
+             * types - patch and tunnel. */
+            handled = false;
+            break;
+        }
+
+        const char *iface_id = smap_get(&iface_rec->external_ids, "iface-id");
+        const char *old_iface_id = smap_get(b_ctx_out->local_iface_ids,
+                                            iface_rec->name);
+        const char *cleared_iface_id = NULL;
+        if (!ovsrec_interface_is_deleted(iface_rec)) {
+            if (iface_id) {
+                /* Check if iface_id is changed. If so we need to
+                 * release the old port binding and associate this
+                 * inteface to new port binding. */
+                if (old_iface_id && strcmp(iface_id, old_iface_id)) {
+                    cleared_iface_id = old_iface_id;
+                }
+            } else if (old_iface_id) {
+                cleared_iface_id = old_iface_id;
+            }
         } else {
-            lbinding = local_binding_find(b_ctx_out->local_bindings,
-                                          binding_rec->parent_port);
+            cleared_iface_id = iface_id;
+            iface_id = NULL;
         }
 
-        if (lbinding) {
-            changed = true;
+        if (cleared_iface_id) {
+            handled = handle_iface_delete(cleared_iface_id, iface_rec->name,
+                                          b_ctx_in, b_ctx_out, changed);
+        }
+
+        if (!handled) {
             break;
         }
     }
 
-    return changed;
+    if (!handled) {
+        return false;
+    }
+
+    struct hmap qos_map = HMAP_INITIALIZER(&qos_map);
+    struct hmap *qos_map_ptr =
+        sset_is_empty(b_ctx_out->egress_ifaces) ? NULL : &qos_map;
+
+    OVSREC_INTERFACE_TABLE_FOR_EACH_TRACKED (iface_rec,
+                                             b_ctx_in->iface_table) {
+        /* Loop to handle create and update changes only. */
+        if (ovsrec_interface_is_deleted(iface_rec)) {
+            continue;
+        }
+
+        const char *iface_id = smap_get(&iface_rec->external_ids, "iface-id");
+        int64_t ofport = iface_rec->n_ofport ? *iface_rec->ofport : 0;
+        if (iface_id && ofport > 0) {
+            *changed = true;
+            handled = handle_iface_add(iface_rec, iface_id, b_ctx_in,
+                                       b_ctx_out, qos_map_ptr);
+            if (!handled) {
+                break;
+            }
+        }
+    }
+
+    if (handled && qos_map_ptr && set_noop_qos(b_ctx_in->ovs_idl_txn,
+                                               b_ctx_in->port_table,
+                                               b_ctx_in->qos_table,
+                                               b_ctx_out->egress_ifaces)) {
+        const char *entry;
+        SSET_FOR_EACH (entry, b_ctx_out->egress_ifaces) {
+            setup_qos(entry, &qos_map);
+        }
+    }
+
+    destroy_qos_map(&qos_map);
+    return handled;
 }
 
-/* Returns true if the database is all cleaned up, false if more work is
- * required. */
-bool
-binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn,
-                const struct sbrec_port_binding_table *port_binding_table,
-                const struct sbrec_chassis *chassis_rec)
+static void
+handle_deleted_lport(const struct sbrec_port_binding *pb,
+                     struct binding_ctx_in *b_ctx_in,
+                     struct binding_ctx_out *b_ctx_out)
 {
-    if (!ovnsb_idl_txn) {
+    struct local_datapath *ld =
+        get_local_datapath(b_ctx_out->local_datapaths,
+                           pb->datapath->tunnel_key);
+    if (ld) {
+        remove_pb_from_local_datapath(pb, b_ctx_in->chassis_rec,
+                                      b_ctx_out, ld);
+    }
+}
+
+static struct local_binding *
+get_lbinding_for_lport(const struct sbrec_port_binding *pb,
+                       enum en_lport_type lport_type,
+                       struct binding_ctx_out *b_ctx_out)
+{
+    ovs_assert(lport_type == LP_VIF || lport_type == LP_VIRTUAL);
+
+    if (lport_type == LP_VIF && !is_lport_container(pb)) {
+        return local_binding_find(b_ctx_out->local_bindings, pb->logical_port);
+    }
+
+    struct local_binding *parent_lbinding = NULL;
+
+    if (lport_type == LP_VIRTUAL) {
+        parent_lbinding = local_binding_find(b_ctx_out->local_bindings,
+                                             pb->virtual_parent);
+    } else {
+        parent_lbinding = local_binding_find(b_ctx_out->local_bindings,
+                                             pb->parent_port);
+    }
+
+    return parent_lbinding
+           ? local_binding_find(&parent_lbinding->children, pb->logical_port)
+           : NULL;
+}
+
+static bool
+handle_deleted_vif_lport(const struct sbrec_port_binding *pb,
+                         enum en_lport_type lport_type,
+                         struct binding_ctx_in *b_ctx_in,
+                         struct binding_ctx_out *b_ctx_out,
+                         bool *changed)
+{
+    struct local_binding *lbinding =
+        get_lbinding_for_lport(pb, lport_type, b_ctx_out);
+
+    if (lbinding) {
+        lbinding->pb = NULL;
+        /* The port_binding 'pb' is deleted. So there is no need to
+         * clear the 'chassis' column of 'pb'. But we need to do
+         * for the local_binding's children. */
+        if (lbinding->type == BT_VIF &&
+                !release_local_binding_children(b_ctx_in->chassis_rec,
+                                                lbinding,
+                                                !b_ctx_in->ovnsb_idl_txn)) {
+            return false;
+        }
+
+        *changed = true;
+    }
+
+    handle_deleted_lport(pb, b_ctx_in, b_ctx_out);
+    return true;
+}
+
+static bool
+handle_updated_vif_lport(const struct sbrec_port_binding *pb,
+                         enum en_lport_type lport_type,
+                         struct binding_ctx_in *b_ctx_in,
+                         struct binding_ctx_out *b_ctx_out,
+                         struct hmap *qos_map,
+                         bool *changed)
+{
+    bool claimed = (pb->chassis == b_ctx_in->chassis_rec);
+    bool handled = true;
+
+    if (lport_type == LP_VIRTUAL) {
+        handled = consider_virtual_lport(pb, b_ctx_in, b_ctx_out, qos_map);
+    } else if (lport_type == LP_VIF && is_lport_container(pb)) {
+        handled = consider_container_lport(pb, b_ctx_in, b_ctx_out, qos_map);
+    } else {
+        handled = consider_vif_lport(pb, b_ctx_in, b_ctx_out, NULL, qos_map);
+    }
+
+    if (!handled) {
         return false;
     }
-    if (!chassis_rec) {
+
+    bool now_claimed = (pb->chassis == b_ctx_in->chassis_rec);
+    bool changed_ = (claimed != now_claimed);
+
+    if (changed_) {
+        *changed = true;
+    }
+
+    if (lport_type == LP_VIRTUAL ||
+        (lport_type == LP_VIF && is_lport_container(pb)) || !changed_) {
         return true;
     }
 
-    const struct sbrec_port_binding *binding_rec;
-    bool any_changes = false;
-    SBREC_PORT_BINDING_TABLE_FOR_EACH (binding_rec, port_binding_table) {
-        if (binding_rec->chassis == chassis_rec) {
-            if (binding_rec->encap)
-                sbrec_port_binding_set_encap(binding_rec, NULL);
-            sbrec_port_binding_set_chassis(binding_rec, NULL);
-            any_changes = true;
+    struct local_binding *lbinding =
+        local_binding_find(b_ctx_out->local_bindings, pb->logical_port);
+
+    ovs_assert(lbinding);
+
+    struct shash_node *node;
+    SHASH_FOR_EACH (node, &lbinding->children) {
+        struct local_binding *child = node->data;
+        if (child->type == BT_CONTAINER) {
+            handled = consider_container_lport(child->pb, b_ctx_in, b_ctx_out,
+                                               qos_map);
+            if (!handled) {
+                return false;
+            }
         }
     }
 
-    if (any_changes) {
-        ovsdb_idl_txn_add_comment(
-            ovnsb_idl_txn,
-            "ovn-controller: removing all port bindings for '%s'",
-            chassis_rec->name);
+    return true;
+}
+
+/* Returns true if the port binding changes resulted in local binding
+ * updates, false otherwise.
+ */
+bool
+binding_handle_port_binding_changes(struct binding_ctx_in *b_ctx_in,
+                                    struct binding_ctx_out *b_ctx_out,
+                                    bool *changed)
+{
+    bool handled = true;
+    *changed = false;
+
+    const struct sbrec_port_binding *pb;
+
+    /* Run the tracked port binding loop twice. One to handle deleted
+     * changes. And another to handle add/update changes.
+     * This will ensure correctness.
+     */
+    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
+                                               b_ctx_in->port_binding_table) {
+        if (!sbrec_port_binding_is_deleted(pb)) {
+            continue;
+        }
+
+        enum en_lport_type lport_type = get_lport_type(pb);
+        if (lport_type == LP_VIF || lport_type == LP_VIRTUAL) {
+            handled = handle_deleted_vif_lport(pb, lport_type, b_ctx_in,
+                                                b_ctx_out, changed);
+        } else {
+            handle_deleted_lport(pb, b_ctx_in, b_ctx_out);
+        }
+
+        if (!handled) {
+            break;
+        }
     }
 
-    return !any_changes;
+    if (!handled) {
+        return false;
+    }
+
+    struct hmap qos_map = HMAP_INITIALIZER(&qos_map);
+    struct hmap *qos_map_ptr =
+        sset_is_empty(b_ctx_out->egress_ifaces) ? NULL : &qos_map;
+
+    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
+                                               b_ctx_in->port_binding_table) {
+        /* Loop to handle create and update changes only. */
+        if (sbrec_port_binding_is_deleted(pb)) {
+            continue;
+        }
+
+        enum en_lport_type lport_type = get_lport_type(pb);
+
+        struct local_datapath *ld =
+            get_local_datapath(b_ctx_out->local_datapaths,
+                               pb->datapath->tunnel_key);
+
+        switch (lport_type) {
+        case LP_VIF:
+        case LP_VIRTUAL:
+            handled = handle_updated_vif_lport(pb, lport_type, b_ctx_in,
+                                               b_ctx_out, qos_map_ptr,
+                                               changed);
+            break;
+
+        case LP_PATCH:
+        case LP_LOCALPORT:
+        case LP_VTEP:
+            *changed = true;
+            update_local_lport_ids(b_ctx_out->local_lport_ids, pb);
+            if (lport_type ==  LP_PATCH) {
+                /* Add the peer datapath to the local datapaths if it's
+                    * not present yet.
+                    */
+                if (ld) {
+                    add_local_datapath_peer_port(pb, b_ctx_in,
+                                                    b_ctx_out, ld);
+                }
+            }
+            break;
+
+        case LP_L2GATEWAY:
+            *changed = true;
+            handled = consider_l2gw_lport(pb, b_ctx_in, b_ctx_out);
+            break;
+
+        case LP_L3GATEWAY:
+            *changed = true;
+            handled = consider_l3gw_lport(pb, b_ctx_in, b_ctx_out);
+            break;
+
+        case LP_CHASSISREDIRECT:
+            *changed = true;
+            handled = consider_cr_lport(pb, b_ctx_in, b_ctx_out);
+            break;
+
+        case LP_EXTERNAL:
+            *changed = true;
+            handled = consider_external_lport(pb, b_ctx_in, b_ctx_out);
+            break;
+
+        case LP_LOCALNET: {
+            *changed = true;
+            consider_localnet_lport(pb, b_ctx_in, b_ctx_out, qos_map_ptr);
+
+            struct shash bridge_mappings =
+                SHASH_INITIALIZER(&bridge_mappings);
+            add_ovs_bridge_mappings(b_ctx_in->ovs_table,
+                                    b_ctx_in->bridge_table,
+                                    &bridge_mappings);
+            update_ld_localnet_port(pb, &bridge_mappings,
+                                    b_ctx_out->egress_ifaces,
+                                    b_ctx_out->local_datapaths);
+            shash_destroy(&bridge_mappings);
+            break;
+        }
+
+        case LP_REMOTE:
+        case LP_UNKNOWN:
+            break;
+        }
+
+        if (!handled) {
+            break;
+        }
+    }
+
+    if (handled && qos_map_ptr && set_noop_qos(b_ctx_in->ovs_idl_txn,
+                                               b_ctx_in->port_table,
+                                               b_ctx_in->qos_table,
+                                               b_ctx_out->egress_ifaces)) {
+        const char *entry;
+        SSET_FOR_EACH (entry, b_ctx_out->egress_ifaces) {
+            setup_qos(entry, &qos_map);
+        }
+    }
+
+    destroy_qos_map(&qos_map);
+    return handled;
 }
diff --git a/controller/binding.h b/controller/binding.h
index 9affc9a96..f7ace6faf 100644
--- a/controller/binding.h
+++ b/controller/binding.h
@@ -57,6 +57,7 @@  struct binding_ctx_out {
     struct sset *local_lports;
     struct sset *local_lport_ids;
     struct sset *egress_ifaces;
+    struct smap *local_iface_ids;
 };
 
 void binding_register_ovs_idl(struct ovsdb_idl *);
@@ -64,9 +65,13 @@  void binding_run(struct binding_ctx_in *, struct binding_ctx_out *);
 bool binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn,
                      const struct sbrec_port_binding_table *,
                      const struct sbrec_chassis *);
-bool binding_evaluate_port_binding_changes(struct binding_ctx_in *,
-                                           struct binding_ctx_out *);
 
 void local_bindings_init(struct shash *local_bindings);
 void local_bindings_destroy(struct shash *local_bindings);
+bool binding_handle_ovs_interface_changes(struct binding_ctx_in *,
+                                          struct binding_ctx_out *,
+                                          bool *changed);
+bool binding_handle_port_binding_changes(struct binding_ctx_in *,
+                                         struct binding_ctx_out *,
+                                         bool *changed);
 #endif /* controller/binding.h */
diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
index 6b759966b..08b074415 100644
--- a/controller/ovn-controller.c
+++ b/controller/ovn-controller.c
@@ -753,6 +753,7 @@  enum sb_engine_node {
     OVS_NODE(open_vswitch, "open_vswitch") \
     OVS_NODE(bridge, "bridge") \
     OVS_NODE(port, "port") \
+    OVS_NODE(interface, "interface") \
     OVS_NODE(qos, "qos")
 
 enum ovs_engine_node {
@@ -974,6 +975,7 @@  struct ed_type_runtime_data {
     struct sset active_tunnels;
 
     struct sset egress_ifaces;
+    struct smap local_iface_ids;
 };
 
 static void *
@@ -987,6 +989,7 @@  en_runtime_data_init(struct engine_node *node OVS_UNUSED,
     sset_init(&data->local_lport_ids);
     sset_init(&data->active_tunnels);
     sset_init(&data->egress_ifaces);
+    smap_init(&data->local_iface_ids);
     local_bindings_init(&data->local_bindings);
     return data;
 }
@@ -1000,6 +1003,7 @@  en_runtime_data_cleanup(void *data)
     sset_destroy(&rt_data->local_lport_ids);
     sset_destroy(&rt_data->active_tunnels);
     sset_destroy(&rt_data->egress_ifaces);
+    smap_destroy(&rt_data->local_iface_ids);
     struct local_datapath *cur_node, *next_node;
     HMAP_FOR_EACH_SAFE (cur_node, next_node, hmap_node,
                         &rt_data->local_datapaths) {
@@ -1041,6 +1045,10 @@  init_binding_ctx(struct engine_node *node,
         (struct ovsrec_port_table *)EN_OVSDB_GET(
             engine_get_input("OVS_port", node));
 
+    struct ovsrec_interface_table *iface_table =
+        (struct ovsrec_interface_table *)EN_OVSDB_GET(
+            engine_get_input("OVS_interface", node));
+
     struct ovsrec_qos_table *qos_table =
         (struct ovsrec_qos_table *)EN_OVSDB_GET(
             engine_get_input("OVS_qos", node));
@@ -1070,6 +1078,7 @@  init_binding_ctx(struct engine_node *node,
     b_ctx_in->sbrec_port_binding_by_datapath = sbrec_port_binding_by_datapath;
     b_ctx_in->sbrec_port_binding_by_name = sbrec_port_binding_by_name;
     b_ctx_in->port_table = port_table;
+    b_ctx_in->iface_table = iface_table;
     b_ctx_in->qos_table = qos_table;
     b_ctx_in->port_binding_table = pb_table;
     b_ctx_in->br_int = br_int;
@@ -1083,6 +1092,7 @@  init_binding_ctx(struct engine_node *node,
     b_ctx_out->local_lport_ids = &rt_data->local_lport_ids;
     b_ctx_out->egress_ifaces = &rt_data->egress_ifaces;
     b_ctx_out->local_bindings = &rt_data->local_bindings;
+    b_ctx_out->local_iface_ids = &rt_data->local_iface_ids;
 }
 
 static void
@@ -1111,10 +1121,12 @@  en_runtime_data_run(struct engine_node *node, void *data)
         sset_destroy(local_lport_ids);
         sset_destroy(active_tunnels);
         sset_destroy(&rt_data->egress_ifaces);
+        smap_destroy(&rt_data->local_iface_ids);
         sset_init(local_lports);
         sset_init(local_lport_ids);
         sset_init(active_tunnels);
         sset_init(&rt_data->egress_ifaces);
+        smap_init(&rt_data->local_iface_ids);
         local_bindings_init(&rt_data->local_bindings);
     }
 
@@ -1140,6 +1152,34 @@  en_runtime_data_run(struct engine_node *node, void *data)
     engine_set_node_state(node, EN_UPDATED);
 }
 
+static bool
+runtime_data_ovs_interface_handler(struct engine_node *node, void *data)
+{
+    struct ed_type_runtime_data *rt_data = data;
+    struct binding_ctx_in b_ctx_in;
+    struct binding_ctx_out b_ctx_out;
+    init_binding_ctx(node, rt_data, &b_ctx_in, &b_ctx_out);
+
+    bool changed = false;
+    if (!binding_handle_ovs_interface_changes(&b_ctx_in, &b_ctx_out,
+                                              &changed)) {
+        return false;
+    }
+
+    if (changed) {
+        engine_set_node_state(node, EN_UPDATED);
+    }
+
+    return true;
+}
+
+static bool
+runtime_data_noop_handler(struct engine_node *node OVS_UNUSED,
+                          void *data OVS_UNUSED)
+{
+    return true;
+}
+
 static bool
 runtime_data_sb_port_binding_handler(struct engine_node *node, void *data)
 {
@@ -1147,11 +1187,21 @@  runtime_data_sb_port_binding_handler(struct engine_node *node, void *data)
     struct binding_ctx_in b_ctx_in;
     struct binding_ctx_out b_ctx_out;
     init_binding_ctx(node, rt_data, &b_ctx_in, &b_ctx_out);
+    if (!b_ctx_in.chassis_rec) {
+        return false;
+    }
+
+    bool changed = false;
+    if (!binding_handle_port_binding_changes(&b_ctx_in, &b_ctx_out,
+                                             &changed)) {
+        return false;
+    }
 
-    bool changed = binding_evaluate_port_binding_changes(&b_ctx_in,
-                                                         &b_ctx_out);
+    if (changed) {
+        engine_set_node_state(node, EN_UPDATED);
+    }
 
-    return !changed;
+    return true;
 }
 
 /* Connection tracking zones. */
@@ -1894,7 +1944,10 @@  main(int argc, char *argv[])
 
     engine_add_input(&en_runtime_data, &en_ovs_open_vswitch, NULL);
     engine_add_input(&en_runtime_data, &en_ovs_bridge, NULL);
-    engine_add_input(&en_runtime_data, &en_ovs_port, NULL);
+    engine_add_input(&en_runtime_data, &en_ovs_port,
+                     runtime_data_noop_handler);
+    engine_add_input(&en_runtime_data, &en_ovs_interface,
+                     runtime_data_ovs_interface_handler);
     engine_add_input(&en_runtime_data, &en_ovs_qos, NULL);
 
     engine_add_input(&en_runtime_data, &en_sb_chassis, NULL);
diff --git a/tests/ovn-performance.at b/tests/ovn-performance.at
index a8a15f8fe..5dfc6f7ca 100644
--- a/tests/ovn-performance.at
+++ b/tests/ovn-performance.at
@@ -239,6 +239,19 @@  for i in 1 2; do
     ovn_attach n1 br-phys 192.168.0.$i
 done
 
+# Wait for the tunnel ports to be created and up.
+# Otherwise this may affect the lflow_run count.
+
+OVS_WAIT_UNTIL([
+    test $(as hv1 ovs-vsctl list interface ovn-hv2-0 | \
+grep tunnel_egress_iface_carrier=up | wc -l) -eq 1
+])
+
+OVS_WAIT_UNTIL([
+    test $(as hv2 ovs-vsctl list interface ovn-hv1-0 | \
+grep tunnel_egress_iface_carrier=up | wc -l) -eq 1
+])
+
 # Add router lr1
 OVN_CONTROLLER_EXPECT_HIT(
     [hv1 hv2], [lflow_run],