diff mbox series

[ovs-dev,v3,5/8] northd: Handle load balancer changes for a logical switch.

Message ID 20230725173946.2804691-1-numans@ovn.org
State Superseded
Headers show
Series northd: I-P for load balancer and lb groups | expand

Checks

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

Commit Message

Numan Siddique July 25, 2023, 5:39 p.m. UTC
From: Numan Siddique <numans@ovn.org>

'lb_data' engine node now also handles logical switch changes.
Its data maintains ls to lb related information. i.e if a
logical switch sw0 has lb1, lb2 and lb3 associated then
it stores this info in its data.  And when a new load balancer
lb4 is associated to it, it stores this information in its
tracked data so that 'northd' engine node can handle it
accordingly.  Tracked data will have information like:
  changed ls -> {sw0 : {associated_lbs: [lb4]}

In the 'northd' engne node, an additional handler for 'lb_data'
is added after the 'nbrec_logical_switch' changes.  With this patch
we now have 2 handlers for 'lb_data' in 'northd' engine node.

----
engine_add_input(&en_northd, &en_lb_data, northd_lb_data_handler_pre_od);
engine_add_input(&en_northd, &en_nb_logical_switch,
                 northd_nb_logical_switch_handler);
engine_add_input(&en_northd, &en_lb_data, northd_lb_data_handler_post_od);
----

The first handler 'northd_lb_data_handler_pre_od' is called before the
'northd_nb_logical_switch_handler' handler and it just creates or
deletes the lb_datapaths hmap for the tracked lbs.

The second handler 'northd_lb_data_handler_post_od' is called after
the 'northd_nb_logical_switch_handler' handler and it updates the
ovn_lb_datapaths's 'nb_ls_map' bitmap.

Eg.  If the lb_data has the below tracked data:

tracked_data = {'crupdated_lbs': [lb1, lb2],
                'deleted_lbs': [lb3],
                'crupdated_lb_groups': [lbg1, lbg2],
                'crupdated_ls_lbs': [{ls: sw0, assoc_lbs: [lb1],
                                     {ls: sw1, assoc_lbs: [lb1, lb2]}

then the first handler northd_lb_data_handler_pre_od(), creates the
ovn_lb_datapaths object for lb1 and lb2 and deletes lb3 from
the ovn_lb_datapaths hmap.  Similarly for the created or updated lb
groups lbg1 and lbg2.

The second handler northd_lb_data_handler_post_od() updates the
nb_ls_bitmap of lb1 for sw0 and sw1 and nb_ls_bitmap of lb2 for sw1.

This second handler is added mainly so that the actual logical switch
handler northd_nb_logical_switch_handler() has done modifying the
'od' struct.

Signed-off-by: Numan Siddique <numans@ovn.org>
---
 lib/lb.c                 |   5 +-
 northd/en-lb-data.c      | 169 +++++++++++++++++++++++++++++++++++++++
 northd/en-lb-data.h      |  17 ++++
 northd/en-lflow.c        |   6 ++
 northd/en-northd.c       |  40 +++++++--
 northd/en-northd.h       |   3 +-
 northd/inc-proc-northd.c |   5 +-
 northd/northd.c          | 152 ++++++++++++++++++++++++++++-------
 northd/northd.h          |  15 ++--
 tests/ovn-northd.at      |  22 ++++-
 10 files changed, 387 insertions(+), 47 deletions(-)
diff mbox series

Patch

diff --git a/lib/lb.c b/lib/lb.c
index 06f7804808..e1ac9fcf08 100644
--- a/lib/lb.c
+++ b/lib/lb.c
@@ -1087,7 +1087,10 @@  ovn_lb_datapaths_add_ls(struct ovn_lb_datapaths *lb_dps, size_t n,
                         struct ovn_datapath **ods)
 {
     for (size_t i = 0; i < n; i++) {
-        bitmap_set1(lb_dps->nb_ls_map, ods[i]->index);
+        if (!bitmap_is_set(lb_dps->nb_ls_map, ods[i]->index)) {
+            bitmap_set1(lb_dps->nb_ls_map, ods[i]->index);
+            lb_dps->n_nb_ls++;
+        }
     }
 }
 
diff --git a/northd/en-lb-data.c b/northd/en-lb-data.c
index 64e929e19f..a835ac58d9 100644
--- a/northd/en-lb-data.c
+++ b/northd/en-lb-data.c
@@ -38,6 +38,14 @@  static void lb_data_destroy(struct ed_type_lb_data *);
 static void build_lbs(const struct nbrec_load_balancer_table *,
                       const struct nbrec_load_balancer_group_table *,
                       struct hmap *lbs, struct hmap *lb_groups);
+static void build_od_lb_info(const struct nbrec_logical_switch_table *,
+                             struct hmap *od_lb_info);
+static struct od_lb_data *find_od_lb_data(struct hmap *od_lb_info,
+                                          const struct uuid *od_uuid);
+static void destroy_od_lb_data(struct od_lb_data *od_lb_data);
+static struct od_lb_data *create_od_lb_data(struct hmap *od_lb_info,
+                                            const struct uuid *od_uuid);
+
 static struct ovn_lb_group *create_lb_group(
     const struct nbrec_load_balancer_group *, struct hmap *lbs,
     struct hmap *lb_groups);
@@ -53,6 +61,7 @@  static inline struct crupdated_lb_group *
                                            struct tracked_lb_data *);
 static inline void add_deleted_lb_group_to_tracked_data(
     struct ovn_lb_group *, struct tracked_lb_data *);
+static bool is_ls_lbs_changed(const struct nbrec_logical_switch *nbs);
 
 /* 'lb_data' engine node manages the NB load balancers and load balancer
  * groups.  For each NB LB, it creates 'struct ovn_northd_lb' and
@@ -79,9 +88,13 @@  en_lb_data_run(struct engine_node *node, void *data)
         EN_OVSDB_GET(engine_get_input("NB_load_balancer", node));
     const struct nbrec_load_balancer_group_table *nb_lbg_table =
         EN_OVSDB_GET(engine_get_input("NB_load_balancer_group", node));
+    const struct nbrec_logical_switch_table *nb_ls_table =
+        EN_OVSDB_GET(engine_get_input("NB_logical_switch", node));
 
     lb_data->tracked = false;
     build_lbs(nb_lb_table, nb_lbg_table, &lb_data->lbs, &lb_data->lb_groups);
+    build_od_lb_info(nb_ls_table, &lb_data->od_lb_info);
+
     engine_set_node_state(node, EN_UPDATED);
 }
 
@@ -230,18 +243,97 @@  lb_data_load_balancer_group_handler(struct engine_node *node, void *data)
     return true;
 }
 
+struct od_lb_data {
+    struct hmap_node hmap_node;
+    struct uuid od_uuid;
+    struct uuidset *lbs;
+    struct uuidset *lbgrps;
+};
+
+bool
+lb_data_logical_switch_handler(struct engine_node *node, void *data)
+{
+    struct ed_type_lb_data *lb_data = (struct ed_type_lb_data *) data;
+    const struct nbrec_logical_switch_table *nb_ls_table =
+        EN_OVSDB_GET(engine_get_input("NB_logical_switch", node));
+
+    struct tracked_lb_data *trk_lb_data = &lb_data->tracked_lb_data;
+    lb_data->tracked = true;
+
+    bool changed = false;
+    const struct nbrec_logical_switch *nbs;
+    NBREC_LOGICAL_SWITCH_TABLE_FOR_EACH_TRACKED (nbs, nb_ls_table) {
+        if (nbrec_logical_switch_is_deleted(nbs)) {
+            struct od_lb_data *od_lb_data =
+                find_od_lb_data(&lb_data->od_lb_info, &nbs->header_.uuid);
+            if (od_lb_data) {
+                hmap_remove(&lb_data->od_lb_info, &od_lb_data->hmap_node);
+                destroy_od_lb_data(od_lb_data);
+            }
+        } else {
+            if (!is_ls_lbs_changed(nbs)) {
+                continue;
+            }
+            struct crupdated_od_lb_data *codlb = xzalloc(sizeof *codlb);
+            codlb->od_uuid = nbs->header_.uuid;
+            uuidset_init(&codlb->assoc_lbs);
+
+            struct od_lb_data *od_lb_data =
+                find_od_lb_data(&lb_data->od_lb_info, &nbs->header_.uuid);
+            if (!od_lb_data) {
+                od_lb_data = create_od_lb_data(&lb_data->od_lb_info,
+                                                &nbs->header_.uuid);
+            }
+
+            struct uuidset *pre_lb_uuids = od_lb_data->lbs;
+            od_lb_data->lbs = xzalloc(sizeof *od_lb_data->lbs);
+            uuidset_init(od_lb_data->lbs);
+
+            for (size_t i = 0; i < nbs->n_load_balancer; i++) {
+                const struct uuid *lb_uuid =
+                    &nbs->load_balancer[i]->header_.uuid;
+                uuidset_insert(od_lb_data->lbs, lb_uuid);
+
+                if (!uuidset_find_and_delete(pre_lb_uuids,
+                                                lb_uuid)) {
+                    /* Add this lb to the tracked data. */
+                    uuidset_insert(&codlb->assoc_lbs, lb_uuid);
+                    changed = true;
+                }
+            }
+
+            if (!uuidset_is_empty(pre_lb_uuids)) {
+                trk_lb_data->has_dissassoc_lbs_from_od = true;
+                changed = true;
+            }
+
+            uuidset_destroy(pre_lb_uuids);
+            free(pre_lb_uuids);
+
+            ovs_list_insert(&trk_lb_data->crupdated_ls_lbs, &codlb->list_node);
+        }
+    }
+
+    if (changed) {
+        engine_set_node_state(node, EN_UPDATED);
+    }
+    return true;
+}
+
 /* static functions. */
 static void
 lb_data_init(struct ed_type_lb_data *lb_data)
 {
     hmap_init(&lb_data->lbs);
     hmap_init(&lb_data->lb_groups);
+    hmap_init(&lb_data->od_lb_info);
 
     struct tracked_lb_data *trk_lb_data = &lb_data->tracked_lb_data;
     hmapx_init(&trk_lb_data->crupdated_lbs);
     hmapx_init(&trk_lb_data->deleted_lbs);
     hmapx_init(&trk_lb_data->crupdated_lb_groups);
     hmapx_init(&trk_lb_data->deleted_lb_groups);
+    ovs_list_init(&trk_lb_data->crupdated_ls_lbs);
 }
 
 static void
@@ -259,6 +351,12 @@  lb_data_destroy(struct ed_type_lb_data *lb_data)
     }
     hmap_destroy(&lb_data->lb_groups);
 
+    struct od_lb_data *od_lb_data;
+    HMAP_FOR_EACH_POP (od_lb_data, hmap_node, &lb_data->od_lb_info) {
+        destroy_od_lb_data(od_lb_data);
+    }
+    hmap_destroy(&lb_data->od_lb_info);
+
     destroy_tracked_data(lb_data);
     hmapx_destroy(&lb_data->tracked_lb_data.crupdated_lbs);
     hmapx_destroy(&lb_data->tracked_lb_data.deleted_lbs);
@@ -300,12 +398,67 @@  create_lb_group(const struct nbrec_load_balancer_group *nbrec_lb_group,
     return lb_group;
 }
 
+static void
+build_od_lb_info(const struct nbrec_logical_switch_table *nbrec_ls_table,
+                 struct hmap *od_lb_info)
+{
+    const struct nbrec_logical_switch *nbrec_ls;
+    NBREC_LOGICAL_SWITCH_TABLE_FOR_EACH (nbrec_ls, nbrec_ls_table) {
+        if (!nbrec_ls->n_load_balancer) {
+            continue;
+        }
+
+        struct od_lb_data *od_lb_data =
+            create_od_lb_data(od_lb_info, &nbrec_ls->header_.uuid);
+        for (size_t i = 0; i < nbrec_ls->n_load_balancer; i++) {
+            uuidset_insert(od_lb_data->lbs,
+                           &nbrec_ls->load_balancer[i]->header_.uuid);
+        }
+    }
+}
+
+static struct od_lb_data *
+create_od_lb_data(struct hmap *od_lb_info, const struct uuid *od_uuid)
+{
+    struct od_lb_data *od_lb_data = xzalloc(sizeof *od_lb_data);
+    od_lb_data->od_uuid = *od_uuid;
+    od_lb_data->lbs = xzalloc(sizeof *od_lb_data->lbs);
+    uuidset_init(od_lb_data->lbs);
+
+    hmap_insert(od_lb_info, &od_lb_data->hmap_node,
+                uuid_hash(&od_lb_data->od_uuid));
+    return od_lb_data;
+}
+
+static struct od_lb_data *
+find_od_lb_data(struct hmap *od_lb_info, const struct uuid *od_uuid)
+{
+    struct od_lb_data *od_lb_data;
+    HMAP_FOR_EACH_WITH_HASH (od_lb_data, hmap_node, uuid_hash(od_uuid),
+                             od_lb_info) {
+        if (uuid_equals(&od_lb_data->od_uuid, od_uuid)) {
+            return od_lb_data;
+        }
+    }
+
+    return NULL;
+}
+
+static void
+destroy_od_lb_data(struct od_lb_data *od_lb_data)
+{
+    uuidset_destroy(od_lb_data->lbs);
+    free(od_lb_data->lbs);
+    free(od_lb_data);
+}
+
 static void
 destroy_tracked_data(struct ed_type_lb_data *lb_data)
 {
     lb_data->tracked = false;
     lb_data->tracked_lb_data.has_health_checks = false;
     lb_data->tracked_lb_data.has_dissassoc_lbs_from_lb_grops = false;
+    lb_data->tracked_lb_data.has_dissassoc_lbs_from_od = false;
 
     struct hmapx_node *node;
     HMAPX_FOR_EACH_SAFE (node, &lb_data->tracked_lb_data.deleted_lbs) {
@@ -327,6 +480,15 @@  destroy_tracked_data(struct ed_type_lb_data *lb_data)
         hmapx_delete(&lb_data->tracked_lb_data.crupdated_lb_groups, node);
         free(crupdated_lbg);
     }
+
+    struct crupdated_od_lb_data *codlb;
+    LIST_FOR_EACH_SAFE (codlb, list_node,
+                        &lb_data->tracked_lb_data.crupdated_ls_lbs) {
+        ovs_list_remove(&codlb->list_node);
+        uuidset_destroy(&codlb->assoc_lbs);
+
+        free(codlb);
+    }
 }
 
 static inline void
@@ -368,3 +530,10 @@  add_deleted_lb_group_to_tracked_data(struct ovn_lb_group *lbg,
 {
     hmapx_add(&tracked_lb_data->deleted_lb_groups, lbg);
 }
+
+static bool
+is_ls_lbs_changed(const struct nbrec_logical_switch *nbs) {
+    return ((nbrec_logical_switch_is_new(nbs) && nbs->n_load_balancer)
+            ||  nbrec_logical_switch_is_updated(nbs,
+                        NBREC_LOGICAL_SWITCH_COL_LOAD_BALANCER));
+}
diff --git a/northd/en-lb-data.h b/northd/en-lb-data.h
index 01d6f7f0a3..4b2fdf770c 100644
--- a/northd/en-lb-data.h
+++ b/northd/en-lb-data.h
@@ -6,6 +6,7 @@ 
 #include "openvswitch/hmap.h"
 #include "include/openvswitch/list.h"
 #include "lib/hmapx.h"
+#include "lib/uuidset.h"
 
 #include "lib/inc-proc-eng.h"
 
@@ -20,6 +21,13 @@  struct crupdated_lb_group {
     struct hmapx assoc_lbs;
 };
 
+struct crupdated_od_lb_data {
+    struct ovs_list list_node;
+
+    struct uuid od_uuid;
+    struct uuidset assoc_lbs;
+};
+
 struct tracked_lb_data {
     /* Both created and updated lbs. hmapx node is 'struct ovn_northd_lb *'. */
     struct hmapx crupdated_lbs;
@@ -32,12 +40,19 @@  struct tracked_lb_data {
     /* Deleted lb_groups. hmapx node is  'struct ovn_lb_group *'. */
     struct hmapx deleted_lb_groups;
 
+    /* List of logical switch <-> lb changes. List node is
+     * 'struct crupdated_od_lb_data' */
+    struct ovs_list crupdated_ls_lbs;
+
     /* Indicates if any of the tracked lb has health checks enabled. */
     bool has_health_checks;
 
     /* Indicates if any lb got disassociated from a lb group
      * but not deleted. */
     bool has_dissassoc_lbs_from_lb_grops;
+
+    /* Indicates if a lb was disassociated from a logical switch. */
+    bool has_dissassoc_lbs_from_od;
 };
 
 /* struct which maintains the data of the engine node lb_data. */
@@ -47,6 +62,7 @@  struct ed_type_lb_data {
 
     /* hmap of load balancer groups.  hmap node is 'struct ovn_lb_group *' */
     struct hmap lb_groups;
+    struct hmap od_lb_info;
 
     /* tracked data*/
     bool tracked;
@@ -60,5 +76,6 @@  void en_lb_data_clear_tracked_data(void *data);
 
 bool lb_data_load_balancer_handler(struct engine_node *, void *data);
 bool lb_data_load_balancer_group_handler(struct engine_node *, void *data);
+bool lb_data_logical_switch_handler(struct engine_node *, void *data);
 
 #endif /* end of EN_NORTHD_LB_DATA_H */
diff --git a/northd/en-lflow.c b/northd/en-lflow.c
index db1bcbccd6..77e2eff056 100644
--- a/northd/en-lflow.c
+++ b/northd/en-lflow.c
@@ -102,6 +102,12 @@  lflow_northd_handler(struct engine_node *node,
     if (!northd_data->change_tracked) {
         return false;
     }
+
+    /* Fall back to recompute if lb related data has changed. */
+    if (northd_data->lb_changed) {
+        return false;
+    }
+
     const struct engine_context *eng_ctx = engine_get_context();
     struct lflow_data *lflow_data = data;
 
diff --git a/northd/en-northd.c b/northd/en-northd.c
index d59ef062df..9d1838a1a4 100644
--- a/northd/en-northd.c
+++ b/northd/en-northd.c
@@ -207,7 +207,7 @@  northd_sb_port_binding_handler(struct engine_node *node,
 }
 
 bool
-northd_lb_data_handler(struct engine_node *node, void *data)
+northd_lb_data_handler_pre_od(struct engine_node *node, void *data)
 {
     struct ed_type_lb_data *lb_data = engine_get_input_data("lb_data", node);
 
@@ -230,19 +230,47 @@  northd_lb_data_handler(struct engine_node *node, void *data)
 
     /* Fall back to recompute if load balancer groups are deleted. */
     if (!hmapx_is_empty(&lb_data->tracked_lb_data.deleted_lb_groups)) {
+    }
+
+    if (lb_data->tracked_lb_data.has_dissassoc_lbs_from_od) {
         return false;
     }
 
     struct northd_data *nd = data;
 
-    if (!northd_handle_lb_data_changes(&lb_data->tracked_lb_data,
-                                       &nd->ls_datapaths,
-                                       &nd->lr_datapaths,
-                                       &nd->lb_datapaths_map,
-                                       &nd->lb_group_datapaths_map)) {
+    if (!northd_handle_lb_data_changes_pre_od(&lb_data->tracked_lb_data,
+                                              &nd->ls_datapaths,
+                                              &nd->lr_datapaths,
+                                              &nd->lb_datapaths_map,
+                                              &nd->lb_group_datapaths_map)) {
+        return false;
+    }
+
+    engine_set_node_state(node, EN_UPDATED);
+    return true;
+}
+
+bool
+northd_lb_data_handler_post_od(struct engine_node *node, void *data)
+{
+    struct ed_type_lb_data *lb_data =
+        engine_get_input_data("lb_data", node);
+
+    ovs_assert(lb_data->tracked);
+    ovs_assert(!lb_data->tracked_lb_data.has_dissassoc_lbs_from_od);
+
+    struct northd_data *nd = data;
+
+    if (!northd_handle_lb_data_changes_post_od(&lb_data->tracked_lb_data,
+                                               &nd->ls_datapaths,
+                                               &nd->lb_datapaths_map)) {
         return false;
     }
 
+    /* Indicate the depedendant engine nodes that load balancer/group
+     * related data has changed (including association to logical
+     * switch/router). */
+    nd->lb_changed = true;
     engine_set_node_state(node, EN_UPDATED);
     return true;
 }
diff --git a/northd/en-northd.h b/northd/en-northd.h
index 3c77b64bb2..5926e7a9d3 100644
--- a/northd/en-northd.h
+++ b/northd/en-northd.h
@@ -17,6 +17,7 @@  void en_northd_clear_tracked_data(void *data);
 bool northd_nb_nb_global_handler(struct engine_node *, void *data OVS_UNUSED);
 bool northd_nb_logical_switch_handler(struct engine_node *, void *data);
 bool northd_sb_port_binding_handler(struct engine_node *, void *data);
-bool northd_lb_data_handler(struct engine_node *, void *data);
+bool northd_lb_data_handler_pre_od(struct engine_node *, void *data);
+bool northd_lb_data_handler_post_od(struct engine_node *, void *data);
 
 #endif /* EN_NORTHD_H */
diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
index 75d059645f..402c94e88c 100644
--- a/northd/inc-proc-northd.c
+++ b/northd/inc-proc-northd.c
@@ -152,6 +152,8 @@  void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
                      lb_data_load_balancer_handler);
     engine_add_input(&en_lb_data, &en_nb_load_balancer_group,
                      lb_data_load_balancer_group_handler);
+    engine_add_input(&en_lb_data, &en_nb_logical_switch,
+                     lb_data_logical_switch_handler);
 
     engine_add_input(&en_northd, &en_nb_port_group, NULL);
     engine_add_input(&en_northd, &en_nb_acl, NULL);
@@ -180,9 +182,10 @@  void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
                      northd_sb_port_binding_handler);
     engine_add_input(&en_northd, &en_nb_nb_global,
                      northd_nb_nb_global_handler);
-    engine_add_input(&en_northd, &en_lb_data, northd_lb_data_handler);
+    engine_add_input(&en_northd, &en_lb_data, northd_lb_data_handler_pre_od);
     engine_add_input(&en_northd, &en_nb_logical_switch,
                      northd_nb_logical_switch_handler);
+    engine_add_input(&en_northd, &en_lb_data, northd_lb_data_handler_post_od);
 
     engine_add_input(&en_mac_binding_aging, &en_nb_nb_global, NULL);
     engine_add_input(&en_mac_binding_aging, &en_sb_mac_binding, NULL);
diff --git a/northd/northd.c b/northd/northd.c
index cd76a73f4b..1612e80719 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -853,7 +853,6 @@  ovn_datapath_destroy(struct hmap *datapaths, struct ovn_datapath *od)
         ovn_ls_port_group_destroy(&od->nb_pgs);
         destroy_mcast_info_for_datapath(od);
         destroy_ports_for_datapath(od);
-
         free(od);
     }
 }
@@ -4919,6 +4918,7 @@  destroy_northd_data_tracked_changes(struct northd_data *nd)
     }
 
     nd->change_tracked = false;
+    nd->lb_changed = false;
 }
 
 /* Check if a changed LSP can be handled incrementally within the I-P engine
@@ -5034,6 +5034,7 @@  ls_port_create(struct ovsdb_idl_txn *ovnsb_txn, struct hmap *ls_ports,
  * incrementally handled.
  * Presently supports i-p for the below changes:
  *    - logical switch ports.
+ *    - load balancers.
  */
 static bool
 ls_changes_can_be_handled(
@@ -5042,8 +5043,11 @@  ls_changes_can_be_handled(
     /* Check if the columns are changed in this row. */
     enum nbrec_logical_switch_column_id col;
     for (col = 0; col < NBREC_LOGICAL_SWITCH_N_COLUMNS; col++) {
-        if (nbrec_logical_switch_is_updated(ls, col) &&
-            col != NBREC_LOGICAL_SWITCH_COL_PORTS) {
+        if (nbrec_logical_switch_is_updated(ls, col)) {
+            if (col == NBREC_LOGICAL_SWITCH_COL_PORTS ||
+                col == NBREC_LOGICAL_SWITCH_COL_LOAD_BALANCER) {
+                continue;
+            }
             return false;
         }
     }
@@ -5072,12 +5076,6 @@  ls_changes_can_be_handled(
             return false;
         }
     }
-    for (size_t i = 0; i < ls->n_load_balancer; i++) {
-        if (nbrec_load_balancer_row_get_seqno(ls->load_balancer[i],
-                                OVSDB_IDL_CHANGE_MODIFY) > 0) {
-            return false;
-        }
-    }
     for (size_t i = 0; i < ls->n_load_balancer_group; i++) {
         if (nbrec_load_balancer_group_row_get_seqno(ls->load_balancer_group[i],
                                 OVSDB_IDL_CHANGE_MODIFY) > 0) {
@@ -5139,7 +5137,9 @@  ls_handle_lsp_changes(struct ovsdb_idl_txn *ovnsb_idl_txn,
                                 const struct nbrec_logical_switch *changed_ls,
                                 const struct northd_input *ni,
                                 struct northd_data *nd,
-                                struct ovn_datapath *od)
+                                struct ovn_datapath *od,
+                                struct ls_change *ls_change,
+                                bool *updated)
 {
     bool ls_ports_changed = false;
     if (!nbrec_logical_switch_is_updated(changed_ls,
@@ -5160,12 +5160,6 @@  ls_handle_lsp_changes(struct ovsdb_idl_txn *ovnsb_idl_txn,
         return true;
     }
 
-    struct ls_change *ls_change = xzalloc(sizeof *ls_change);
-    ls_change->od = od;
-    ovs_list_init(&ls_change->added_ports);
-    ovs_list_init(&ls_change->deleted_ports);
-    ovs_list_init(&ls_change->updated_ports);
-
     struct ovn_port *op;
     HMAP_FOR_EACH (op, dp_node, &od->ports) {
         op->visited = false;
@@ -5255,10 +5249,7 @@  ls_handle_lsp_changes(struct ovsdb_idl_txn *ovnsb_idl_txn,
     if (!ovs_list_is_empty(&ls_change->added_ports) ||
         !ovs_list_is_empty(&ls_change->updated_ports) ||
         !ovs_list_is_empty(&ls_change->deleted_ports)) {
-        ovs_list_push_back(&nd->tracked_ls_changes.updated,
-                            &ls_change->list_node);
-    } else {
-        free(ls_change);
+        *updated = true;
     }
 
     return true;
@@ -5269,11 +5260,20 @@  fail_clean_deleted:
     }
 
 fail:
-    free(ls_change);
+
+    LIST_FOR_EACH (op, list, &ls_change->added_ports) {
+        ovs_list_remove(&op->list);
+    }
+    LIST_FOR_EACH (op, list, &ls_change->updated_ports) {
+        ovs_list_remove(&op->list);
+    }
+    LIST_FOR_EACH_SAFE (op, list, &ls_change->deleted_ports) {
+        ovs_list_remove(&op->list);
+        ovn_port_destroy_orphan(op);
+    }
     return false;
 }
 
-
 /* Return true if changes are handled incrementally, false otherwise.
  * When there are any changes, try to track what's exactly changed and set
  * northd_data->change_tracked accordingly: change tracked - true, otherwise,
@@ -5307,14 +5307,33 @@  northd_handle_ls_changes(struct ovsdb_idl_txn *ovnsb_idl_txn,
             goto fail;
         }
 
-        if (!ls_handle_lsp_changes(ovnsb_idl_txn, changed_ls, ni, nd, od)) {
+        struct ls_change *ls_change = xzalloc(sizeof *ls_change);
+        ls_change->od = od;
+        ovs_list_init(&ls_change->added_ports);
+        ovs_list_init(&ls_change->deleted_ports);
+        ovs_list_init(&ls_change->updated_ports);
+
+        bool updated = false;
+        if (!ls_handle_lsp_changes(ovnsb_idl_txn, changed_ls,
+                                   ni, nd, od, ls_change,
+                                   &updated)) {
+            destroy_tracked_ls_change(ls_change);
+            free(ls_change);
             goto fail;
         }
+
+        if (updated) {
+            ovs_list_push_back(&nd->tracked_ls_changes.updated,
+                               &ls_change->list_node);
+        } else {
+            free(ls_change);
+        }
     }
 
     if (!ovs_list_is_empty(&nd->tracked_ls_changes.updated)) {
         nd->change_tracked = true;
     }
+
     return true;
 
 fail:
@@ -5376,16 +5395,21 @@  northd_handle_sb_port_binding_changes(
 
 /* Handler for lb_data engine changes.  For every tracked lb_data
  * it creates or deletes the ovn_lb_datapaths/ovn_lb_group_datapaths
- * from the lb_datapaths hmap and lb_group_datapaths hmap. */
+ * from the lb_datapaths hmap and lb_group_datapaths hmap.
+ *
+ * This handler is called if there are any lb_data changes but before
+ * processing the logical switch changes.
+ * */
 bool
-northd_handle_lb_data_changes(struct tracked_lb_data *trk_lb_data,
-                              struct ovn_datapaths *ls_datapaths,
-                              struct ovn_datapaths *lr_datapaths,
-                              struct hmap *lb_datapaths_map,
-                              struct hmap *lb_group_datapaths_map)
+northd_handle_lb_data_changes_pre_od(struct tracked_lb_data *trk_lb_data,
+                                     struct ovn_datapaths *ls_datapaths,
+                                     struct ovn_datapaths *lr_datapaths,
+                                     struct hmap *lb_datapaths_map,
+                                     struct hmap *lb_group_datapaths_map)
 {
     struct ovn_lb_datapaths *lb_dps;
     struct ovn_northd_lb *lb;
+    struct ovn_datapath *od;
     struct hmapx_node *hmapx_node;
     HMAPX_FOR_EACH (hmapx_node, &trk_lb_data->deleted_lbs) {
         lb = hmapx_node->data;
@@ -5393,6 +5417,16 @@  northd_handle_lb_data_changes(struct tracked_lb_data *trk_lb_data,
 
         lb_dps = ovn_lb_datapaths_find(lb_datapaths_map, lb_uuid);
         ovs_assert(lb_dps);
+
+        /* Re-evaluate 'od->has_lb_vip for od's associated with the
+         * deleted lb. */
+        size_t index;
+        BITMAP_FOR_EACH_1 (index, ods_size(ls_datapaths),
+                           lb_dps->nb_ls_map) {
+            od = ls_datapaths->array[index];
+            init_lb_for_datapath(od);
+        }
+
         hmap_remove(lb_datapaths_map, &lb_dps->hmap_node);
         ovn_lb_datapaths_destroy(lb_dps);
     }
@@ -5431,6 +5465,65 @@  northd_handle_lb_data_changes(struct tracked_lb_data *trk_lb_data,
     return true;
 }
 
+/* This handler is called if there are any lb_data changes but afer processing
+ * the logical switch changes.
+ *
+ * For every tracked data it does the following:
+ * For any changes to a created or updated logical switch due to
+ * association of a load balancer (eg. ovn-nbctl ls-lb-add sw0 lb1),
+ * the logical switch datapath is added to the load balancer (represented
+ * by 'struct ovn_lb_datapaths') by calling ovn_lb_datapaths_add_ls().
+ *
+ * For every created or updated load balancer in the tracked data,
+ * it gets the associated logical switches and for each switch it
+ * re-evaluates 'od->has_lb_vip'.  Since this handler is called after
+ * handling any logical switch changes. */
+bool
+northd_handle_lb_data_changes_post_od(struct tracked_lb_data *trk_lb_data,
+                                      struct ovn_datapaths *ls_datapaths,
+                                      struct hmap *lb_datapaths_map)
+{
+    ovs_assert(!trk_lb_data->has_health_checks);
+
+    struct ovn_northd_lb *lb;
+    struct hmapx_node *hmapx_node;
+    struct ovn_lb_datapaths *lb_dps;
+    struct ovn_datapath *od;
+    struct crupdated_od_lb_data *codlb;
+
+    LIST_FOR_EACH (codlb, list_node, &trk_lb_data->crupdated_ls_lbs) {
+        od = ovn_datapath_find(&ls_datapaths->datapaths, &codlb->od_uuid);
+        ovs_assert(od);
+
+        struct uuidset_node *uuidnode;
+        UUIDSET_FOR_EACH (uuidnode, &codlb->assoc_lbs) {
+            lb_dps = ovn_lb_datapaths_find(lb_datapaths_map, &uuidnode->uuid);
+            ovs_assert(lb_dps);
+            ovn_lb_datapaths_add_ls(lb_dps, 1, &od);
+        }
+
+        /* Re-evaluate 'od->has_lb_vip' */
+        init_lb_for_datapath(od);
+    }
+
+    HMAPX_FOR_EACH (hmapx_node, &trk_lb_data->crupdated_lbs) {
+        lb = hmapx_node->data;
+        const struct uuid *lb_uuid = &lb->nlb->header_.uuid;
+
+        lb_dps = ovn_lb_datapaths_find(lb_datapaths_map, lb_uuid);
+        ovs_assert(lb_dps);
+        size_t index;
+        BITMAP_FOR_EACH_1 (index, ods_size(ls_datapaths),
+                           lb_dps->nb_ls_map) {
+            od = ls_datapaths->array[index];
+            /* Re-evaluate 'od->has_lb_vip' */
+            init_lb_for_datapath(od);
+        }
+    }
+
+    return true;
+}
+
 struct multicast_group {
     const char *name;
     uint16_t key;               /* OVN_MIN_MULTICAST...OVN_MAX_MULTICAST. */
@@ -16617,6 +16710,7 @@  bool lflow_handle_northd_ls_changes(struct ovsdb_idl_txn *ovnsb_txn,
                                     struct hmap *lflows)
 {
     struct ls_change *ls_change;
+
     LIST_FOR_EACH (ls_change, list_node, &ls_changes->updated) {
         const struct sbrec_multicast_group *sbmc_flood =
             mcast_group_lookup(lflow_input->sbrec_mcast_group_by_name_dp,
diff --git a/northd/northd.h b/northd/northd.h
index 7d17921fa2..0ed7215356 100644
--- a/northd/northd.h
+++ b/northd/northd.h
@@ -120,6 +120,8 @@  struct northd_data {
     struct hmap svc_monitor_map;
     bool change_tracked;
     struct tracked_ls_changes tracked_ls_changes;
+    bool lb_changed; /* Indicates if load balancers changed or association of
+                      * load balancer to logical switch/router changed. */
 };
 
 struct lflow_data {
@@ -348,11 +350,14 @@  bool northd_handle_sb_port_binding_changes(
     const struct sbrec_port_binding_table *, struct hmap *ls_ports);
 
 struct tracked_lb_data;
-bool northd_handle_lb_data_changes(struct tracked_lb_data *,
-                                   struct ovn_datapaths *ls_datapaths,
-                                   struct ovn_datapaths *lr_datapaths,
-                                   struct hmap *lb_datapaths_map,
-                                   struct hmap *lb_group_datapaths_map);
+bool northd_handle_lb_data_changes_pre_od(struct tracked_lb_data *,
+                                          struct ovn_datapaths *ls_datapaths,
+                                          struct ovn_datapaths *lr_datapaths,
+                                          struct hmap *lb_datapaths_map,
+                                          struct hmap *lb_group_datapaths_map);
+bool northd_handle_lb_data_changes_post_od(struct tracked_lb_data *,
+                                           struct ovn_datapaths *ls_datapaths,
+                                           struct hmap *lb_datapaths_map);
 
 void build_bfd_table(struct ovsdb_idl_txn *ovnsb_txn,
                      const struct nbrec_bfd_table *,
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index bc562d340a..5a8acd4e93 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -9789,12 +9789,26 @@  check ovn-nbctl ls-add sw0
 check ovn-nbctl --wait=sb lr-add lr0
 check_engine_stats recompute recompute
 
-# Associate lb1 to sw0. There should be a full recompute of northd engine node
+# Associate lb1 to sw0. There should be no recompute of northd engine node
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
 check ovn-nbctl --wait=sb ls-lb-add sw0 lb1
+check_engine_stats norecompute recompute
+
+# Disassociate lb1 from sw0. There should be a recompute of northd engine node.
+check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+check ovn-nbctl --wait=sb ls-lb-del sw0 lb1
 check_engine_stats recompute recompute
 
-# Disassociate lb1 from sw0. There should be a full recompute of northd engine node.
+# Associate lb1 to sw0 and also create a port sw0p1.  This should result in
+# full recompute of lflow engine node.
+check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+check as northd ovn-appctl -t NORTHD_TYPE vlog/set dbg
+check ovn-nbctl --wait=sb ls-lb-add sw0 lb1 -- lsp-add sw0 sw0p1
+check_engine_stats norecompute recompute
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+
+# Disassociate lb1 from sw0. There should be a recompute of northd engine node.
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
 check ovn-nbctl --wait=sb ls-lb-del sw0 lb1
 check_engine_stats recompute recompute
@@ -9892,12 +9906,12 @@  CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
 check ovn-nbctl --wait=sb ls-lb-add sw0 lb2
-check_engine_stats recompute recompute
+check_engine_stats norecompute recompute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
 check ovn-nbctl --wait=sb ls-lb-add sw0 lb3
-check_engine_stats recompute recompute
+check_engine_stats norecompute recompute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats