diff mbox series

[ovs-dev,v1,8/8] northd: Handle load balancer changes for a logical router.

Message ID 20230624064910.12227-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-_ovn-kubernetes success github build: passed
ovsrobot/github-robot-_Build_and_Test fail github build: failed

Commit Message

Numan Siddique June 24, 2023, 6:49 a.m. UTC
From: Numan Siddique <numans@ovn.org>

When a logical router gets updated due to load balancer
or load balancer groups changes, it is now incrementally
handled in the 'northd' engine node.  Other logical router
updates result in the full recompute of 'northd' engine node.

lflow engine node still falls back to recompute due to
logical router changes.

Signed-off-by: Numan Siddique <numans@ovn.org>
---
 lib/lb.c                 |  32 +++-
 lib/lb.h                 |   5 +
 northd/en-lflow.c        |   5 +
 northd/en-northd.c       |  20 ++
 northd/en-northd.h       |   1 +
 northd/inc-proc-northd.c |   3 +-
 northd/northd.c          | 392 ++++++++++++++++++++++++++++++++++-----
 northd/northd.h          |   5 +
 tests/ovn-northd.at      |  12 +-
 9 files changed, 422 insertions(+), 53 deletions(-)
diff mbox series

Patch

diff --git a/lib/lb.c b/lib/lb.c
index a0426cc42e..a8b694d0b3 100644
--- a/lib/lb.c
+++ b/lib/lb.c
@@ -1082,7 +1082,10 @@  ovn_lb_datapaths_add_lr(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_lr_map, ods[i]->index);
+        if (!bitmap_is_set(lb_dps->nb_lr_map, ods[i]->index)) {
+            bitmap_set1(lb_dps->nb_lr_map, ods[i]->index);
+            lb_dps->n_nb_lr++;
+        }
     }
 }
 
@@ -1110,6 +1113,18 @@  ovn_lb_datapaths_remove_ls(struct ovn_lb_datapaths *lb_dps, size_t n,
     }
 }
 
+void
+ovn_lb_datapaths_remove_lr(struct ovn_lb_datapaths *lb_dps, size_t n,
+                           struct ovn_datapath **ods)
+{
+    for (size_t i = 0; i < n; i++) {
+        if (bitmap_is_set(lb_dps->nb_lr_map, ods[i]->index)) {
+            bitmap_set0(lb_dps->nb_lr_map, ods[i]->index);
+            lb_dps->n_nb_lr--;
+        }
+    }
+}
+
 struct ovn_lb_group_datapaths *
 ovn_lb_group_datapaths_create(const struct ovn_lb_group *lb_group,
                               size_t n_ls_datapaths,
@@ -1175,5 +1190,18 @@  void
 ovn_lb_group_datapaths_add_lr(struct ovn_lb_group_datapaths *lbg_dps,
                               struct ovn_datapath *lr)
 {
-    bitmap_set1(lbg_dps->nb_lr_map, lr->index);
+    if (!bitmap_is_set(lbg_dps->nb_lr_map, lr->index)) {
+        bitmap_set1(lbg_dps->nb_lr_map, lr->index);
+        lbg_dps->n_nb_lr++;
+    }
+}
+
+void
+ovn_lb_group_datapaths_remove_lr(struct ovn_lb_group_datapaths *lbg_dps,
+                                 struct ovn_datapath *lr)
+{
+    if (bitmap_is_set(lbg_dps->nb_lr_map, lr->index)) {
+        bitmap_set1(lbg_dps->nb_lr_map, lr->index);
+        lbg_dps->n_nb_lr--;
+    }
 }
diff --git a/lib/lb.h b/lib/lb.h
index 08723e8ef3..91eec0199b 100644
--- a/lib/lb.h
+++ b/lib/lb.h
@@ -185,6 +185,8 @@  void ovn_lb_datapaths_set_ls(struct ovn_lb_datapaths *, size_t n,
 
 void ovn_lb_datapaths_remove_ls(struct ovn_lb_datapaths *, size_t n,
                                 struct ovn_datapath **);
+void ovn_lb_datapaths_remove_lr(struct ovn_lb_datapaths *, size_t n,
+                                struct ovn_datapath **);
 
 struct ovn_lb_group_datapaths {
     struct hmap_node hmap_node;
@@ -214,6 +216,9 @@  void ovn_lb_group_datapaths_remove_ls(struct ovn_lb_group_datapaths *,
 
 void ovn_lb_group_datapaths_add_lr(struct ovn_lb_group_datapaths *,
                                    struct ovn_datapath *lr);
+void ovn_lb_group_datapaths_remove_lr(struct ovn_lb_group_datapaths *,
+                                      struct ovn_datapath *lr);
+
 struct ovn_controller_lb {
     struct hmap_node hmap_node;
 
diff --git a/northd/en-lflow.c b/northd/en-lflow.c
index db1bcbccd6..ea51f4e3e0 100644
--- a/northd/en-lflow.c
+++ b/northd/en-lflow.c
@@ -102,6 +102,11 @@  lflow_northd_handler(struct engine_node *node,
     if (!northd_data->change_tracked) {
         return false;
     }
+
+    if (northd_data->lrouters_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 b3c03c54bd..a321eff323 100644
--- a/northd/en-northd.c
+++ b/northd/en-northd.c
@@ -188,6 +188,26 @@  northd_nb_logical_switch_handler(struct engine_node *node,
     return true;
 }
 
+bool
+northd_nb_logical_router_handler(struct engine_node *node,
+                                 void *data)
+{
+    struct northd_data *nd = data;
+    struct northd_input input_data;
+
+    northd_get_input_data(node, &input_data);
+
+    if (!northd_handle_lr_changes(&input_data, nd)) {
+        return false;
+    }
+
+    if (nd->change_tracked) {
+        engine_set_node_state(node, EN_UPDATED);
+    }
+
+    return true;
+}
+
 bool
 northd_sb_port_binding_handler(struct engine_node *node,
                                void *data)
diff --git a/northd/en-northd.h b/northd/en-northd.h
index 5674f4390c..739aa5e87f 100644
--- a/northd/en-northd.h
+++ b/northd/en-northd.h
@@ -16,6 +16,7 @@  void en_northd_cleanup(void *data);
 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_nb_logical_router_handler(struct engine_node *, void *data);
 bool northd_sb_port_binding_handler(struct engine_node *, void *data);
 bool northd_northd_lb_data_handler(struct engine_node *, void *data);
 
diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
index 8f509ccdad..cdbd0a65c3 100644
--- a/northd/inc-proc-northd.c
+++ b/northd/inc-proc-northd.c
@@ -156,7 +156,6 @@  void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
 
     engine_add_input(&en_northd, &en_nb_port_group, NULL);
     engine_add_input(&en_northd, &en_nb_acl, NULL);
-    engine_add_input(&en_northd, &en_nb_logical_router, NULL);
     engine_add_input(&en_northd, &en_nb_mirror, NULL);
     engine_add_input(&en_northd, &en_nb_meter, NULL);
     engine_add_input(&en_northd, &en_nb_static_mac_binding, NULL);
@@ -185,6 +184,8 @@  void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
                      northd_nb_nb_global_handler);
     engine_add_input(&en_northd, &en_nb_logical_switch,
                      northd_nb_logical_switch_handler);
+    engine_add_input(&en_northd, &en_nb_logical_router,
+                     northd_nb_logical_router_handler);
 
     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 f8adcad763..52889bf92f 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -3977,6 +3977,90 @@  associate_ls_lb_groups(struct ovn_datapath *ls_od,
     }
 }
 
+static void
+associate_lr_lbs(struct ovn_datapath *lr_od, struct hmap *lb_datapaths_map)
+{
+    struct ovn_lb_datapaths *lb_dps;
+
+    free(lr_od->lb_uuids);
+    lr_od->lb_uuids = xcalloc(lr_od->nbr->n_load_balancer,
+                              sizeof *lr_od->lb_uuids);
+    lr_od->n_lb_uuids = lr_od->nbr->n_load_balancer;
+
+    if (!lr_od->lb_ips) {
+        lr_od->lb_ips = ovn_lb_ip_set_create();
+    }
+
+    for (size_t i = 0; i < lr_od->nbr->n_load_balancer; i++) {
+        const struct uuid *lb_uuid =
+            &lr_od->nbr->load_balancer[i]->header_.uuid;
+        lb_dps = ovn_lb_datapaths_find(lb_datapaths_map, lb_uuid);
+        ovs_assert(lb_dps);
+        ovn_lb_datapaths_add_lr(lb_dps, 1, &lr_od);
+        build_lrouter_lb_ips(lr_od->lb_ips, lb_dps->lb);
+        lr_od->lb_uuids[i] = *lb_uuid;
+    }
+}
+
+static void
+associate_lr_lb_groups(struct ovn_datapath *lr_od,
+                       struct hmap *lb_group_datapaths_map,
+                       struct hmap *lb_datapaths_map)
+{
+    const struct nbrec_load_balancer_group *nbrec_lb_group;
+    struct ovn_lb_group_datapaths *lb_group_dps;
+
+    free(lr_od->lb_group_uuids);
+    lr_od->lb_group_uuids = xcalloc(lr_od->nbr->n_load_balancer_group,
+                                    sizeof *lr_od->lb_group_uuids);
+    lr_od->n_lb_group_uuids = lr_od->nbr->n_load_balancer_group;
+
+    /* Checking load balancer groups first, starting from the largest one,
+     * to more efficiently copy IP sets. */
+    size_t largest_group = 0;
+
+    for (size_t i = 1; i < lr_od->nbr->n_load_balancer_group; i++) {
+        if (lr_od->nbr->load_balancer_group[i]->n_load_balancer >
+            lr_od->nbr->load_balancer_group[largest_group]->n_load_balancer) {
+            largest_group = i;
+        }
+    }
+
+    for (size_t i = 0; i < lr_od->nbr->n_load_balancer_group; i++) {
+        size_t idx = (i + largest_group) % lr_od->nbr->n_load_balancer_group;
+
+        nbrec_lb_group = lr_od->nbr->load_balancer_group[idx];
+        const struct uuid *lb_group_uuid = &nbrec_lb_group->header_.uuid;
+
+        lb_group_dps =
+            ovn_lb_group_datapaths_find(lb_group_datapaths_map,
+                                        lb_group_uuid);
+        ovs_assert(lb_group_dps);
+        ovn_lb_group_datapaths_add_lr(lb_group_dps, lr_od);
+
+        if (!lr_od->lb_ips) {
+            lr_od->lb_ips =
+                ovn_lb_ip_set_clone(lb_group_dps->lb_group->lb_ips);
+        } else {
+            for (size_t j = 0; j < lb_group_dps->lb_group->n_lbs; j++) {
+                build_lrouter_lb_ips(lr_od->lb_ips,
+                                     lb_group_dps->lb_group->lbs[j]);
+            }
+        }
+
+        lr_od->lb_group_uuids[i] = *lb_group_uuid;
+
+        struct ovn_lb_datapaths *lb_dps;
+        for (size_t j = 0; j < lb_group_dps->lb_group->n_lbs; j++) {
+            const struct uuid *lb_uuid =
+                &lb_group_dps->lb_group->lbs[j]->nlb->header_.uuid;
+            lb_dps = ovn_lb_datapaths_find(lb_datapaths_map, lb_uuid);
+            ovs_assert(lb_dps);
+            ovn_lb_datapaths_add_lr(lb_dps, 1, &lr_od);
+        }
+    }
+}
+
 static void
 build_lb_datapaths(const struct hmap *lbs, const struct hmap *lb_groups,
                    struct ovn_datapaths *ls_datapaths,
@@ -3984,7 +4068,6 @@  build_lb_datapaths(const struct hmap *lbs, const struct hmap *lb_groups,
                    struct hmap *lb_datapaths_map,
                    struct hmap *lb_group_datapaths_map)
 {
-    const struct nbrec_load_balancer_group *nbrec_lb_group;
     struct ovn_lb_group_datapaths *lb_group_dps;
     const struct ovn_lb_group *lb_group;
     struct ovn_lb_datapaths *lb_dps;
@@ -4018,53 +4101,8 @@  build_lb_datapaths(const struct hmap *lbs, const struct hmap *lb_groups,
 
     HMAP_FOR_EACH (od, key_node, &lr_datapaths->datapaths) {
         ovs_assert(od->nbr);
-
-        /* Checking load balancer groups first, starting from the largest one,
-         * to more efficiently copy IP sets. */
-        size_t largest_group = 0;
-
-        for (size_t i = 1; i < od->nbr->n_load_balancer_group; i++) {
-            if (od->nbr->load_balancer_group[i]->n_load_balancer >
-                od->nbr->load_balancer_group[largest_group]->n_load_balancer) {
-                largest_group = i;
-            }
-        }
-
-        for (size_t i = 0; i < od->nbr->n_load_balancer_group; i++) {
-            size_t idx = (i + largest_group) % od->nbr->n_load_balancer_group;
-
-            nbrec_lb_group = od->nbr->load_balancer_group[idx];
-            const struct uuid *lb_group_uuid = &nbrec_lb_group->header_.uuid;
-
-            lb_group_dps =
-                ovn_lb_group_datapaths_find(lb_group_datapaths_map,
-                                            lb_group_uuid);
-            ovs_assert(lb_group_dps);
-            ovn_lb_group_datapaths_add_lr(lb_group_dps, od);
-
-            if (!od->lb_ips) {
-                od->lb_ips =
-                    ovn_lb_ip_set_clone(lb_group_dps->lb_group->lb_ips);
-            } else {
-                for (size_t j = 0; j < lb_group_dps->lb_group->n_lbs; j++) {
-                    build_lrouter_lb_ips(od->lb_ips,
-                                         lb_group_dps->lb_group->lbs[j]);
-                }
-            }
-        }
-
-        if (!od->lb_ips) {
-            od->lb_ips = ovn_lb_ip_set_create();
-        }
-
-        for (size_t i = 0; i < od->nbr->n_load_balancer; i++) {
-            const struct uuid *lb_uuid =
-                &od->nbr->load_balancer[i]->header_.uuid;
-            lb_dps = ovn_lb_datapaths_find(lb_datapaths_map, lb_uuid);
-            ovs_assert(lb_dps);
-            ovn_lb_datapaths_add_lr(lb_dps, 1, &od);
-            build_lrouter_lb_ips(od->lb_ips, lb_dps->lb);
-        }
+        associate_lr_lb_groups(od, lb_group_datapaths_map, lb_datapaths_map);
+        associate_lr_lbs(od, lb_datapaths_map);
     }
 
     HMAP_FOR_EACH (lb_group_dps, hmap_node, lb_group_datapaths_map) {
@@ -4934,6 +4972,7 @@  destroy_northd_data_tracked_changes(struct northd_data *nd)
     }
 
     nd->change_tracked = false;
+    nd->lrouters_changed = false;
 }
 
 /* Check if a changed LSP can be handled incrementally within the I-P engine
@@ -5429,6 +5468,263 @@  fail:
     return false;
 }
 
+/* Returns true if the logical router has changes which are not
+ * incrementally handled.
+ * Presently supports i-p for the below changes:
+ *    - load balancers and load balancer groups.
+ */
+static bool
+check_unsupported_inc_proc_for_lr_changes(
+    const struct nbrec_logical_router *lr)
+{
+    /* Check if the columns are changed in this row. */
+    enum nbrec_logical_router_column_id col;
+    for (col = 0; col < NBREC_LOGICAL_ROUTER_N_COLUMNS; col++) {
+        if (nbrec_logical_router_is_updated(lr, col)) {
+            if (col == NBREC_LOGICAL_ROUTER_COL_LOAD_BALANCER ||
+                col == NBREC_LOGICAL_ROUTER_COL_LOAD_BALANCER_GROUP) {
+                continue;
+            }
+            return true;
+        }
+    }
+
+    /* Check if the referenced rows are changed.
+       XXX: Need a better OVSDB IDL interface for this check. */
+    for (size_t i = 0; i < lr->n_ports; i++) {
+        if (nbrec_logical_router_port_row_get_seqno(lr->ports[i],
+                                OVSDB_IDL_CHANGE_MODIFY) > 0) {
+            return true;
+        }
+    }
+    if (lr->copp && nbrec_copp_row_get_seqno(lr->copp,
+                                OVSDB_IDL_CHANGE_MODIFY) > 0) {
+        return true;
+    }
+    for (size_t i = 0; i < lr->n_nat; i++) {
+        if (nbrec_nat_row_get_seqno(lr->nat[i],
+                                OVSDB_IDL_CHANGE_MODIFY) > 0) {
+            return true;
+        }
+    }
+    for (size_t i = 0; i < lr->n_policies; i++) {
+        if (nbrec_logical_router_policy_row_get_seqno(lr->policies[i],
+                                OVSDB_IDL_CHANGE_MODIFY) > 0) {
+            return true;
+        }
+    }
+    for (size_t i = 0; i < lr->n_static_routes; i++) {
+        if (nbrec_logical_router_static_route_row_get_seqno(
+            lr->static_routes[i], OVSDB_IDL_CHANGE_MODIFY) > 0) {
+            return true;
+        }
+    }
+    return false;
+}
+
+static bool
+lr_check_and_handle_lb_and_lbgrp_changes(
+    const struct nbrec_logical_router *changed_lr,
+    struct hmap *lb_datapaths_map, struct hmap *lb_group_datapaths_map,
+    struct ovn_datapath *od, bool *updated)
+{
+    ovs_assert(od->nbr);
+    bool lbs_changed = true;
+    if (!nbrec_logical_router_is_updated(changed_lr,
+                        NBREC_LOGICAL_ROUTER_COL_LOAD_BALANCER) &&
+        !nbrec_logical_router_is_updated(changed_lr,
+                        NBREC_LOGICAL_ROUTER_COL_LOAD_BALANCER_GROUP)) {
+        lbs_changed = false;
+        for (size_t i = 0; i < changed_lr->n_load_balancer_group; i++) {
+            if (nbrec_load_balancer_group_row_get_seqno(
+                    changed_lr->load_balancer_group[i],
+                    OVSDB_IDL_CHANGE_MODIFY) > 0) {
+                lbs_changed = true;
+                break;
+            }
+        }
+
+        if (!lbs_changed) {
+            for (size_t i = 0; i < changed_lr->n_load_balancer; i++) {
+                if (nbrec_load_balancer_row_get_seqno(
+                        changed_lr->load_balancer[i],
+                        OVSDB_IDL_CHANGE_MODIFY) > 0) {
+                    lbs_changed = true;
+                    break;
+                }
+            }
+        }
+
+        if (!lbs_changed) {
+            return true;
+        }
+    }
+
+    /* We need to do 2 things to handle the router for load balancer or
+     * load balancer groups.
+     *
+     * 1. Disassociate the logical router datapath from the already associated
+     *    load balancers/load balancer groups.
+     * 2. Associate the logical router datapath with the updated
+     *    load balancers/groups.
+     * */
+    struct ovn_lb_datapaths *lb_dps;
+    size_t i, j;
+    for (i = 0; i < od->n_lb_uuids; i++) {
+        lb_dps = ovn_lb_datapaths_find(lb_datapaths_map, &od->lb_uuids[i]);
+        if (lb_dps) {
+            if (lb_dps->lb->routable) {
+                /* Can't handler if the load balancer has routable flag set.
+                 * This requires updating the router ports's routable
+                 * addresses. */
+                return false;
+            }
+            ovn_lb_datapaths_remove_lr(lb_dps, 1, &od);
+        }
+    }
+
+    struct ovn_lb_group_datapaths *lbg_dps;
+    for (i = 0; i < od->n_lb_group_uuids; i++) {
+        lbg_dps = ovn_lb_group_datapaths_find(lb_group_datapaths_map,
+                                              &od->lb_group_uuids[i]);
+        if (lbg_dps) {
+            ovn_lb_group_datapaths_remove_lr(lbg_dps, od);
+
+            if (install_ls_lb_from_router && od->n_ls_peers) {
+                ovn_lb_group_datapaths_remove_ls(lbg_dps, od->n_ls_peers,
+                                                 od->ls_peers);
+            }
+        }
+
+        for (j = 0; j < lbg_dps->lb_group->n_lbs; j++) {
+            const struct uuid *lb_uuid =
+                &lbg_dps->lb_group->lbs[j]->nlb->header_.uuid;
+            lb_dps = ovn_lb_datapaths_find(lb_datapaths_map, lb_uuid);
+            if (lb_dps) {
+                if (lb_dps->lb->routable) {
+                    /* Can't handler if the load balancer has routable flag
+                     * set.  This requires updating the router ports's
+                     * routable addresses. */
+                    return false;
+                }
+                ovn_lb_datapaths_remove_lr(lb_dps, 1, &od);
+
+                if (install_ls_lb_from_router && od->n_ls_peers) {
+                    ovn_lb_datapaths_remove_ls(lb_dps, od->n_ls_peers,
+                                               od->ls_peers);
+                }
+            }
+        }
+    }
+
+    ovn_lb_ip_set_destroy(od->lb_ips);
+    od->lb_ips = NULL;
+    associate_lr_lb_groups(od, lb_group_datapaths_map, lb_datapaths_map);
+    associate_lr_lbs(od, lb_datapaths_map);
+
+    /* Do the job of build_lrouter_lbs_reachable_ips and
+     * build_lswitch_lbs_from_lrouter for this lrouter datapath. */
+    for (i = 0; i < od->nbr->n_load_balancer; i++) {
+        lb_dps = ovn_lb_datapaths_find(lb_datapaths_map,
+                                &od->nbr->load_balancer[i]->header_.uuid);
+        ovs_assert(lb_dps);
+        if (lb_dps->lb->routable) {
+            /* Can't handler if the load balancer has routable flag set.
+             * This requires updating the router ports's routable addresses. */
+            return false;
+        }
+        build_lrouter_lb_reachable_ips(od, lb_dps->lb);
+
+        if (install_ls_lb_from_router && od->n_ls_peers) {
+            ovn_lb_datapaths_add_ls(lb_dps, od->n_ls_peers, od->ls_peers);
+        }
+    }
+
+    for (i = 0; i < od->nbr->n_load_balancer_group; i++) {
+        const struct nbrec_load_balancer_group *nbrec_lb_group =
+            od->nbr->load_balancer_group[i];
+        lbg_dps = ovn_lb_group_datapaths_find(lb_group_datapaths_map,
+                                              &nbrec_lb_group->header_.uuid);
+        ovs_assert(lbg_dps);
+        if (install_ls_lb_from_router && od->n_ls_peers) {
+            ovn_lb_group_datapaths_add_ls(lbg_dps, od->n_ls_peers,
+                                          od->ls_peers);
+        }
+
+        for (j = 0; j < lbg_dps->lb_group->n_lbs; j++) {
+            build_lrouter_lb_reachable_ips(od, lbg_dps->lb_group->lbs[j]);
+
+            if (install_ls_lb_from_router && od->n_ls_peers) {
+                const struct uuid *lb_uuid =
+                    &lbg_dps->lb_group->lbs[j]->nlb->header_.uuid;
+                lb_dps = ovn_lb_datapaths_find(lb_datapaths_map, lb_uuid);
+                ovs_assert(lb_dps);
+                if (lb_dps->lb->routable) {
+                    /* Can't handler if the load balancer has routable flag
+                     * set.  This requires updating the router ports's
+                     * routable addresses. */
+                    return false;
+                }
+                ovn_lb_datapaths_add_ls(lb_dps, od->n_ls_peers, od->ls_peers);
+            }
+        }
+    }
+
+    *updated = true;
+    /* Re-evaluate 'od->has_lb_vip' */
+    init_lb_for_datapath(od);
+    return true;
+}
+
+
+/* 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,
+ * false. */
+bool
+northd_handle_lr_changes(const struct northd_input *ni,
+                         struct northd_data *nd)
+{
+    const struct nbrec_logical_router *changed_lr;
+
+    bool updated = false;
+
+    NBREC_LOGICAL_ROUTER_TABLE_FOR_EACH_TRACKED (changed_lr,
+                                             ni->nbrec_logical_router_table) {
+        if (nbrec_logical_router_is_new(changed_lr) ||
+            nbrec_logical_router_is_deleted(changed_lr)) {
+            goto fail;
+        }
+
+        struct ovn_datapath *od = ovn_datapath_find(
+                                    &nd->lr_datapaths.datapaths,
+                                    &changed_lr->header_.uuid);
+
+        /* Presently only able to handle load balancer and
+         * load balancer group changes. */
+        if (check_unsupported_inc_proc_for_lr_changes(changed_lr)) {
+            goto fail;
+        }
+
+        if (!lr_check_and_handle_lb_and_lbgrp_changes(changed_lr,
+                                                &nd->lb_datapaths_map,
+                                                &nd->lb_group_datapaths_map,
+                                                od, &updated)) {
+            goto fail;
+        }
+    }
+
+    if (updated) {
+        nd->change_tracked = true;
+        nd->lrouters_changed = true;
+    }
+
+    return true;
+fail:
+    destroy_northd_data_tracked_changes(nd);
+    return false;
+}
+
 bool
 northd_handle_sb_port_binding_changes(
     const struct sbrec_port_binding_table *sbrec_port_binding_table,
diff --git a/northd/northd.h b/northd/northd.h
index a79cc470c3..765720849a 100644
--- a/northd/northd.h
+++ b/northd/northd.h
@@ -119,8 +119,11 @@  struct northd_data {
     struct chassis_features features;
     struct sset svc_monitor_lsps;
     struct hmap svc_monitor_map;
+
+    /* Tracked data. */
     bool change_tracked;
     struct tracked_ls_changes tracked_ls_changes;
+    bool lrouters_changed;
 };
 
 struct lflow_data {
@@ -342,6 +345,8 @@  void ovnsb_db_run(struct ovsdb_idl_txn *ovnnb_txn,
 bool northd_handle_ls_changes(struct ovsdb_idl_txn *,
                               const struct northd_input *,
                               struct northd_data *);
+bool northd_handle_lr_changes(const struct northd_input *,
+                              struct northd_data *);
 void destroy_northd_data_tracked_changes(struct northd_data *);
 void northd_destroy(struct northd_data *data);
 void northd_init(struct northd_data *data);
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index da9bc16fd9..6f6f67510e 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -9687,6 +9687,14 @@  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 norecompute recompute
 
+check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+check ovn-nbctl lr-lb-add lr0 lb1
+check_engine_stats norecompute recompute
+
+check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+check ovn-nbctl lr-lb-del lr0 lb1
+check_engine_stats norecompute recompute
+
 # Test load balancer group now
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
 lbg1_uuid=$(ovn-nbctl create load_balancer_group name=lbg1)
@@ -9723,11 +9731,11 @@  check_engine_stats norecompute recompute
 
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
 check ovn-nbctl add logical_router lr0 load_balancer_group $lbg1_uuid
-check_engine_stats recompute recompute
+check_engine_stats norecompute recompute
 
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
 check ovn-nbctl clear logical_router lr0 load_balancer_group
-check_engine_stats recompute recompute
+check_engine_stats norecompute recompute
 
 AT_CLEANUP
 ])