diff --git a/controller/automake.mk b/controller/automake.mk
index 2f6c50890..41f907d6e 100644
--- a/controller/automake.mk
+++ b/controller/automake.mk
@@ -33,7 +33,9 @@ controller_ovn_controller_SOURCES = \
 	controller/physical.c \
 	controller/physical.h \
 	controller/mac-learn.c \
-	controller/mac-learn.h
+	controller/mac-learn.h \
+	controller/local_data.c \
+	controller/local_data.h
 
 controller_ovn_controller_LDADD = lib/libovn.la $(OVS_LIBDIR)/libopenvswitch.la
 man_MANS += controller/ovn-controller.8
diff --git a/controller/binding.c b/controller/binding.c
index ad89739ee..96dc906f9 100644
--- a/controller/binding.c
+++ b/controller/binding.c
@@ -14,13 +14,8 @@
  */
 
 #include <config.h>
-#include "binding.h"
-#include "ha-chassis.h"
-#include "if-status.h"
-#include "lflow.h"
-#include "lport.h"
-#include "patch.h"
 
+/* OVS includes. */
 #include "lib/bitmap.h"
 #include "openvswitch/poll-loop.h"
 #include "lib/sset.h"
@@ -29,9 +24,18 @@
 #include "lib/vswitch-idl.h"
 #include "openvswitch/hmap.h"
 #include "openvswitch/vlog.h"
+
+/* OVN includes. */
+#include "binding.h"
+#include "ha-chassis.h"
+#include "if-status.h"
+#include "lflow.h"
 #include "lib/chassis-index.h"
 #include "lib/ovn-sb-idl.h"
+#include "local_data.h"
+#include "lport.h"
 #include "ovn-controller.h"
+#include "patch.h"
 
 VLOG_DEFINE_THIS_MODULE(binding);
 
@@ -76,117 +80,9 @@ binding_register_ovs_idl(struct ovsdb_idl *ovs_idl)
     ovsdb_idl_add_column(ovs_idl, &ovsrec_qos_col_type);
 }
 
-static struct tracked_binding_datapath *tracked_binding_datapath_create(
-    const struct sbrec_datapath_binding *,
-    bool is_new, struct hmap *tracked_dps);
-static struct tracked_binding_datapath *tracked_binding_datapath_find(
-    struct hmap *, const struct sbrec_datapath_binding *);
-static void tracked_binding_datapath_lport_add(
-    const struct sbrec_port_binding *, struct hmap *tracked_datapaths);
 static void update_lport_tracking(const struct sbrec_port_binding *pb,
-                                  struct hmap *tracked_dp_bindings);
-
-static void
-add_local_datapath__(struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
-                     struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
-                     struct ovsdb_idl_index *sbrec_port_binding_by_name,
-                     const struct sbrec_datapath_binding *datapath,
-                     int depth, struct hmap *local_datapaths,
-                     struct hmap *tracked_datapaths)
-{
-    uint32_t dp_key = datapath->tunnel_key;
-    struct local_datapath *ld = get_local_datapath(local_datapaths, dp_key);
-    if (ld) {
-        return;
-    }
-
-    ld = xzalloc(sizeof *ld);
-    hmap_insert(local_datapaths, &ld->hmap_node, dp_key);
-    ld->datapath = datapath;
-    ld->localnet_port = NULL;
-    shash_init(&ld->external_ports);
-
-    if (tracked_datapaths) {
-        struct tracked_binding_datapath *tdp =
-            tracked_binding_datapath_find(tracked_datapaths, datapath);
-        if (!tdp) {
-            tracked_binding_datapath_create(datapath, true, tracked_datapaths);
-        } else {
-            /* Its possible that there is already an entry in tracked datapaths
-             * for this 'datapath'. tracked_binding_datapath_lport_add() may
-             * have created it. Since the 'datapath' is added to the
-             * local datapaths, set 'tdp->is_new' to true so that the flows
-             * for this datapath are programmed properly.
-             * */
-            tdp->is_new = true;
-        }
-    }
-
-    if (depth >= 100) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-        VLOG_WARN_RL(&rl, "datapaths nested too deep");
-        return;
-    }
-
-    struct sbrec_port_binding *target =
-        sbrec_port_binding_index_init_row(sbrec_port_binding_by_datapath);
-    sbrec_port_binding_index_set_datapath(target, datapath);
-
-    const struct sbrec_port_binding *pb;
-    SBREC_PORT_BINDING_FOR_EACH_EQUAL (pb, target,
-                                       sbrec_port_binding_by_datapath) {
-        if (!strcmp(pb->type, "patch") || !strcmp(pb->type, "l3gateway")) {
-            const char *peer_name = smap_get(&pb->options, "peer");
-            if (peer_name) {
-                const struct sbrec_port_binding *peer;
-
-                peer = lport_lookup_by_name(sbrec_port_binding_by_name,
-                                            peer_name);
-
-                if (peer && peer->datapath) {
-                    if (!strcmp(pb->type, "patch")) {
-                        /* Add the datapath to local datapath only for patch
-                         * ports. For l3gateway ports, since gateway router
-                         * resides on one chassis, we don't need to add.
-                         * Otherwise, all other chassis might create patch
-                         * ports between br-int and the provider bridge. */
-                        add_local_datapath__(sbrec_datapath_binding_by_key,
-                                             sbrec_port_binding_by_datapath,
-                                             sbrec_port_binding_by_name,
-                                             peer->datapath,
-                                             depth + 1, local_datapaths,
-                                             tracked_datapaths);
-                    }
-                    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;
-                }
-            }
-        }
-    }
-    sbrec_port_binding_index_destroy_row(target);
-}
-
-static void
-add_local_datapath(struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
-                   struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
-                   struct ovsdb_idl_index *sbrec_port_binding_by_name,
-                   const struct sbrec_datapath_binding *datapath,
-                   struct hmap *local_datapaths,
-                   struct hmap *tracked_datapaths)
-{
-    add_local_datapath__(sbrec_datapath_binding_by_key,
-                         sbrec_port_binding_by_datapath,
-                         sbrec_port_binding_by_name,
-                         datapath, 0, local_datapaths,
-                         tracked_datapaths);
-}
+                                  struct hmap *tracked_dp_bindings,
+                                  bool claimed);
 
 static void
 get_qos_params(const struct sbrec_port_binding *pb, struct hmap *queue_map)
@@ -554,7 +450,8 @@ update_related_lport(const struct sbrec_port_binding *pb,
 
         if (b_ctx->tracked_dp_bindings) {
             /* Add the 'pb' to the tracked_datapaths. */
-            tracked_binding_datapath_lport_add(pb, b_ctx->tracked_dp_bindings);
+            tracked_datapath_lport_add(pb, TRACKED_RESOURCE_NEW,
+                                       b_ctx->tracked_dp_bindings);
         }
     }
     sset_add(&b_ctx->related_lports->lport_names, pb->logical_port);
@@ -577,7 +474,8 @@ remove_related_lport(const struct sbrec_port_binding *pb,
 
         if (b_ctx->tracked_dp_bindings) {
             /* Add the 'pb' to the tracked_datapaths. */
-            tracked_binding_datapath_lport_add(pb, b_ctx->tracked_dp_bindings);
+            tracked_datapath_lport_add(pb, TRACKED_RESOURCE_REMOVED,
+                                       b_ctx->tracked_dp_bindings);
         }
     }
 }
@@ -927,74 +825,6 @@ is_lport_vif(const struct sbrec_port_binding *pb)
     return !pb->type[0];
 }
 
-static struct tracked_binding_datapath *
-tracked_binding_datapath_create(const struct sbrec_datapath_binding *dp,
-                                bool is_new,
-                                struct hmap *tracked_datapaths)
-{
-    struct tracked_binding_datapath *t_dp = xzalloc(sizeof *t_dp);
-    t_dp->dp = dp;
-    t_dp->is_new = is_new;
-    shash_init(&t_dp->lports);
-    hmap_insert(tracked_datapaths, &t_dp->node, uuid_hash(&dp->header_.uuid));
-    return t_dp;
-}
-
-static struct tracked_binding_datapath *
-tracked_binding_datapath_find(struct hmap *tracked_datapaths,
-                              const struct sbrec_datapath_binding *dp)
-{
-    struct tracked_binding_datapath *t_dp;
-    size_t hash = uuid_hash(&dp->header_.uuid);
-    HMAP_FOR_EACH_WITH_HASH (t_dp, node, hash, tracked_datapaths) {
-        if (uuid_equals(&t_dp->dp->header_.uuid, &dp->header_.uuid)) {
-            return t_dp;
-        }
-    }
-
-    return NULL;
-}
-
-static void
-tracked_binding_datapath_lport_add(const struct sbrec_port_binding *pb,
-                                   struct hmap *tracked_datapaths)
-{
-    if (!tracked_datapaths) {
-        return;
-    }
-
-    struct tracked_binding_datapath *tracked_dp =
-        tracked_binding_datapath_find(tracked_datapaths, pb->datapath);
-    if (!tracked_dp) {
-        tracked_dp = tracked_binding_datapath_create(pb->datapath, false,
-                                                     tracked_datapaths);
-    }
-
-    /* Check if the lport is already present or not.
-     * If it is already present, then just update the 'pb' field. */
-    struct tracked_binding_lport *lport =
-        shash_find_data(&tracked_dp->lports, pb->logical_port);
-
-    if (!lport) {
-        lport = xmalloc(sizeof *lport);
-        shash_add(&tracked_dp->lports, pb->logical_port, lport);
-    }
-
-    lport->pb = pb;
-}
-
-void
-binding_tracked_dp_destroy(struct hmap *tracked_datapaths)
-{
-    struct tracked_binding_datapath *t_dp;
-    HMAP_FOR_EACH_POP (t_dp, node, tracked_datapaths) {
-        shash_destroy_free_data(&t_dp->lports);
-        free(t_dp);
-    }
-
-    hmap_destroy(tracked_datapaths);
-}
-
 static enum en_lport_type
 get_lport_type(const struct sbrec_port_binding *pb)
 {
@@ -1129,7 +959,7 @@ claim_lport(const struct sbrec_port_binding *pb,
         sbrec_port_binding_set_chassis(pb, chassis_rec);
 
         if (tracked_datapaths) {
-            update_lport_tracking(pb, tracked_datapaths);
+            update_lport_tracking(pb, tracked_datapaths, true);
         }
     }
 
@@ -1178,7 +1008,7 @@ release_lport(const struct sbrec_port_binding *pb, bool sb_readonly,
         sbrec_port_binding_set_virtual_parent(pb, NULL);
     }
 
-    update_lport_tracking(pb, tracked_datapaths);
+    update_lport_tracking(pb, tracked_datapaths, false);
     if_status_mgr_release_iface(if_mgr, pb->logical_port);
     VLOG_INFO("Releasing lport %s from this chassis.", pb->logical_port);
     return true;
@@ -1264,11 +1094,10 @@ consider_vif_lport_(const struct sbrec_port_binding *pb,
                 return false;
             }
 
-            add_local_datapath(b_ctx_in->sbrec_datapath_binding_by_key,
+            local_datapath_add(b_ctx_out->local_datapaths, pb->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,
-                               pb->datapath,
-                               b_ctx_out->local_datapaths,
                                b_ctx_out->tracked_dp_bindings);
             update_related_lport(pb, b_ctx_out);
             update_local_lports(pb->logical_port, b_ctx_out);
@@ -1478,11 +1307,10 @@ consider_nonvif_lport_(const struct sbrec_port_binding *pb,
 {
     if (our_chassis) {
         update_local_lports(pb->logical_port, b_ctx_out);
-        add_local_datapath(b_ctx_in->sbrec_datapath_binding_by_key,
+        local_datapath_add(b_ctx_out->local_datapaths, pb->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,
-                           pb->datapath,
-                           b_ctx_out->local_datapaths,
                            b_ctx_out->tracked_dp_bindings);
 
         update_related_lport(pb, b_ctx_out);
@@ -1561,11 +1389,10 @@ consider_ha_lport(const struct sbrec_port_binding *pb,
          * If the chassis is active, consider_nonvif_lport_() takes care
          * of adding the datapath of this 'pb' to local datapaths.
          * */
-        add_local_datapath(b_ctx_in->sbrec_datapath_binding_by_key,
+        local_datapath_add(b_ctx_out->local_datapaths, pb->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,
-                           pb->datapath,
-                           b_ctx_out->local_datapaths,
                            b_ctx_out->tracked_dp_bindings);
         update_related_lport(pb, b_ctx_out);
     }
@@ -1827,143 +1654,6 @@ binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn,
     return !any_changes;
 }
 
-static const struct sbrec_port_binding *
-get_peer_lport__(const struct sbrec_port_binding *pb,
-                 struct binding_ctx_in *b_ctx_in)
-{
-    const char *peer_name = smap_get(&pb->options, "peer");
-
-    if (!peer_name) {
-        return NULL;
-    }
-
-    const struct sbrec_port_binding *peer;
-    peer = lport_lookup_by_name(b_ctx_in->sbrec_port_binding_by_name,
-                                peer_name);
-    return (peer && peer->datapath) ? peer : NULL;
-}
-
-static const struct sbrec_port_binding *
-get_l3gw_peer_lport(const struct sbrec_port_binding *pb,
-                    struct binding_ctx_in *b_ctx_in)
-{
-    if (strcmp(pb->type, "l3gateway")) {
-        return NULL;
-    }
-    return get_peer_lport__(pb, b_ctx_in);
-}
-
-static const struct sbrec_port_binding *
-get_peer_lport(const struct sbrec_port_binding *pb,
-               struct binding_ctx_in *b_ctx_in)
-{
-    if (strcmp(pb->type, "patch")) {
-        return NULL;
-    }
-    return get_peer_lport__(pb, b_ctx_in);
-}
-
-/* This function adds the local datapath of the 'peer' of
- * lport 'pb' to the local datapaths if it is not yet added.
- */
-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 struct sbrec_port_binding *peer;
-    peer = get_peer_lport(pb, b_ctx_in);
-
-    if (!peer) {
-        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 (!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,
-                             1, b_ctx_out->local_datapaths,
-                             b_ctx_out->tracked_dp_bindings);
-        return;
-    }
-
-    for (size_t i = 0; i < peer_ld->n_peer_ports; i++) {
-        if (peer_ld->peer_ports[i].local == peer) {
-            return;
-        }
-    }
-
-    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;
-
-    /* Possible improvement: We can shrink 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 the peer port from the peer datapath. The peer
-         * datapath also tries to remove its peer lport, but that would
-         * be no-op. */
-        remove_local_datapath_peer_port(peer, peer_ld, local_datapaths);
-    }
-}
-
 static void
 remove_pb_from_local_datapath(const struct sbrec_port_binding *pb,
                               struct binding_ctx_out *b_ctx_out,
@@ -1972,7 +1662,7 @@ remove_pb_from_local_datapath(const struct sbrec_port_binding *pb,
     remove_related_lport(pb, b_ctx_out);
     if (!strcmp(pb->type, "patch") ||
         !strcmp(pb->type, "l3gateway")) {
-        remove_local_datapath_peer_port(pb, ld, b_ctx_out->local_datapaths);
+        local_datapath_remove_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)) {
@@ -1985,13 +1675,16 @@ remove_pb_from_local_datapath(const struct sbrec_port_binding *pb,
 
 static void
 update_lport_tracking(const struct sbrec_port_binding *pb,
-                      struct hmap *tracked_dp_bindings)
+                      struct hmap *tracked_dp_bindings,
+                      bool claimed)
 {
     if (!tracked_dp_bindings) {
         return;
     }
 
-    tracked_binding_datapath_lport_add(pb, tracked_dp_bindings);
+    tracked_datapath_lport_add(
+        pb, claimed ? TRACKED_RESOURCE_NEW : TRACKED_RESOURCE_REMOVED,
+        tracked_dp_bindings);
 }
 
 /* Considers the ovs iface 'iface_rec' for claiming.
@@ -2304,7 +1997,7 @@ handle_deleted_lport(const struct sbrec_port_binding *pb,
     /* If the binding is not local, if 'pb' is a L3 gateway port, we should
      * remove its peer, if that one is local.
      */
-    pb = get_l3gw_peer_lport(pb, b_ctx_in);
+    pb = lport_get_l3gw_peer(pb, b_ctx_in->sbrec_port_binding_by_name);
     if (pb) {
         ld = get_local_datapath(b_ctx_out->local_datapaths,
                                 pb->datapath->tunnel_key);
@@ -2587,19 +2280,21 @@ delete_done:
                      * */
                     const struct sbrec_port_binding *peer;
                     struct local_datapath *peer_ld = NULL;
-                    peer = get_peer_lport(pb, b_ctx_in);
+                    peer =
+                        lport_get_peer(pb,
+                                       b_ctx_in->sbrec_port_binding_by_name);
+
                     if (peer) {
                         peer_ld =
                             get_local_datapath(b_ctx_out->local_datapaths,
                                                peer->datapath->tunnel_key);
                     }
                     if (peer_ld) {
-                        add_local_datapath(
+                        local_datapath_add(
+                            b_ctx_out->local_datapaths, pb->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,
-                            pb->datapath,
-                            b_ctx_out->local_datapaths,
                             b_ctx_out->tracked_dp_bindings);
                     }
 
@@ -2611,7 +2306,12 @@ delete_done:
                  * not present yet.
                  */
                 if (ld) {
-                    add_local_datapath_peer_port(pb, b_ctx_in, b_ctx_out, ld);
+                    local_datapath_add_peer_port(
+                        pb, b_ctx_in->sbrec_datapath_binding_by_key,
+                        b_ctx_in->sbrec_port_binding_by_datapath,
+                        b_ctx_in->sbrec_port_binding_by_name,
+                        ld, b_ctx_out->local_datapaths,
+                        b_ctx_out->tracked_dp_bindings);
                 }
             }
 
diff --git a/controller/binding.h b/controller/binding.h
index 77197e742..b1717bd2b 100644
--- a/controller/binding.h
+++ b/controller/binding.h
@@ -121,19 +121,6 @@ void local_binding_set_up(struct shash *local_bindings, const char *pb_name,
 void local_binding_set_down(struct shash *local_bindings, const char *pb_name,
                             bool sb_readonly, bool ovs_readonly);
 
-/* Represents a tracked binding logical port. */
-struct tracked_binding_lport {
-    const struct sbrec_port_binding *pb;
-};
-
-/* Represent a tracked binding datapath. */
-struct tracked_binding_datapath {
-    struct hmap_node node;
-    const struct sbrec_datapath_binding *dp;
-    bool is_new;
-    struct shash lports; /* shash of struct tracked_binding_lport. */
-};
-
 void binding_register_ovs_idl(struct ovsdb_idl *);
 void binding_run(struct binding_ctx_in *, struct binding_ctx_out *);
 bool binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn,
diff --git a/controller/lflow.c b/controller/lflow.c
index 1eed1cccc..c1d55f378 100644
--- a/controller/lflow.c
+++ b/controller/lflow.c
@@ -18,6 +18,7 @@
 #include "coverage.h"
 #include "ha-chassis.h"
 #include "lflow-cache.h"
+#include "local_data.h"
 #include "lport.h"
 #include "ofctrl.h"
 #include "openvswitch/dynamic-string.h"
diff --git a/controller/local_data.c b/controller/local_data.c
new file mode 100644
index 000000000..c6dfd0be4
--- /dev/null
+++ b/controller/local_data.c
@@ -0,0 +1,357 @@
+/* Copyright (c) 2021, Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+/* OVS includes. */
+#include "include/openvswitch/json.h"
+#include "lib/hmapx.h"
+#include "lib/util.h"
+#include "openvswitch/vlog.h"
+
+/* OVN includes. */
+#include "lib/ovn-util.h"
+#include "lib/ovn-sb-idl.h"
+#include "local_data.h"
+#include "lport.h"
+
+VLOG_DEFINE_THIS_MODULE(ldata);
+
+static struct local_datapath *local_datapath_add__(
+    struct hmap *local_datapaths,
+    const struct sbrec_datapath_binding *,
+    struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
+    struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
+    struct ovsdb_idl_index *sbrec_port_binding_by_name,
+    int depth,
+    struct hmap *tracked_datapaths);
+
+static struct tracked_datapath *tracked_datapath_create(
+    const struct sbrec_datapath_binding *dp,
+    enum en_tracked_resource_type tracked_type,
+    struct hmap *tracked_datapaths);
+
+struct local_datapath *
+get_local_datapath(const struct hmap *local_datapaths, uint32_t tunnel_key)
+{
+    struct hmap_node *node = hmap_first_with_hash(local_datapaths, tunnel_key);
+    return (node
+            ? CONTAINER_OF(node, struct local_datapath, hmap_node)
+            : NULL);
+}
+
+struct local_datapath *
+local_datapath_alloc(const struct sbrec_datapath_binding *dp)
+{
+    struct local_datapath *ld = xzalloc(sizeof *ld);
+    ld->datapath = dp;
+    ld->is_switch = datapath_is_switch(dp);
+    shash_init(&ld->external_ports);
+    return ld;
+}
+
+void
+local_datapaths_destroy(struct hmap *local_datapaths)
+{
+    struct local_datapath *ld;
+    HMAP_FOR_EACH_POP (ld, hmap_node, local_datapaths) {
+        local_datapath_destroy(ld);
+    }
+
+    hmap_destroy(local_datapaths);
+}
+
+void
+local_datapath_destroy(struct local_datapath *ld)
+{
+    free(ld->peer_ports);
+    shash_destroy(&ld->external_ports);
+    free(ld);
+}
+
+struct local_datapath *
+local_datapath_add(struct hmap *local_datapaths,
+                   const struct sbrec_datapath_binding *dp,
+                   struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
+                   struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
+                   struct ovsdb_idl_index *sbrec_port_binding_by_name,
+                   struct hmap *tracked_datapaths)
+{
+    return local_datapath_add__(local_datapaths, dp,
+                                sbrec_datapath_binding_by_key,
+                                sbrec_port_binding_by_datapath,
+                                sbrec_port_binding_by_name, 0,
+                                tracked_datapaths);
+}
+
+void
+local_datapath_add_peer_port(
+    const struct sbrec_port_binding *pb,
+    struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
+    struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
+    struct ovsdb_idl_index *sbrec_port_binding_by_name,
+    struct local_datapath *ld,
+    struct hmap *local_datapaths,
+    struct hmap *tracked_datapaths)
+{
+    const struct sbrec_port_binding *peer;
+    peer = lport_get_peer(pb, sbrec_port_binding_by_name);
+
+    if (!peer) {
+        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 (!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(local_datapaths,
+                           peer->datapath->tunnel_key);
+    if (!peer_ld) {
+        local_datapath_add__(local_datapaths, peer->datapath,
+                             sbrec_datapath_binding_by_key,
+                             sbrec_port_binding_by_datapath,
+                             sbrec_port_binding_by_name, 1,
+                             tracked_datapaths);
+        return;
+    }
+
+    for (size_t i = 0; i < peer_ld->n_peer_ports; i++) {
+        if (peer_ld->peer_ports[i].local == peer) {
+            return;
+        }
+    }
+
+    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;
+}
+
+void
+local_datapath_remove_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;
+
+    /* Possible improvement: We can shrink 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 the peer port from the peer datapath. The peer
+         * datapath also tries to remove its peer lport, but that would
+         * be no-op. */
+        local_datapath_remove_peer_port(peer, peer_ld, local_datapaths);
+    }
+}
+
+/* track datapath functions. */
+struct tracked_datapath *
+tracked_datapath_add(const struct sbrec_datapath_binding *dp,
+                     enum en_tracked_resource_type tracked_type,
+                     struct hmap *tracked_datapaths)
+{
+    struct tracked_datapath *t_dp =
+        tracked_datapath_find(tracked_datapaths, dp);
+    if (!t_dp) {
+        t_dp = tracked_datapath_create(dp, tracked_type, tracked_datapaths);
+    } else {
+        t_dp->tracked_type = tracked_type;
+    }
+
+    return t_dp;
+}
+
+struct tracked_datapath *
+tracked_datapath_find(struct hmap *tracked_datapaths,
+                      const struct sbrec_datapath_binding *dp)
+{
+    struct tracked_datapath *t_dp;
+    size_t hash = uuid_hash(&dp->header_.uuid);
+    HMAP_FOR_EACH_WITH_HASH (t_dp, node, hash, tracked_datapaths) {
+        if (uuid_equals(&t_dp->dp->header_.uuid, &dp->header_.uuid)) {
+            return t_dp;
+        }
+    }
+
+    return NULL;
+}
+
+void
+tracked_datapath_lport_add(const struct sbrec_port_binding *pb,
+                           enum en_tracked_resource_type tracked_type,
+                           struct hmap *tracked_datapaths)
+{
+    struct tracked_datapath *tracked_dp =
+        tracked_datapath_find(tracked_datapaths, pb->datapath);
+    if (!tracked_dp) {
+        tracked_dp = tracked_datapath_create(pb->datapath,
+                                             TRACKED_RESOURCE_UPDATED,
+                                             tracked_datapaths);
+    }
+
+    /* Check if the lport is already present or not.
+     * If it is already present, then just update the 'pb' field. */
+    struct tracked_lport *lport =
+        shash_find_data(&tracked_dp->lports, pb->logical_port);
+
+    if (!lport) {
+        lport = xmalloc(sizeof *lport);
+        shash_add(&tracked_dp->lports, pb->logical_port, lport);
+    }
+
+    lport->pb = pb;
+    lport->tracked_type = tracked_type;
+}
+
+void
+tracked_datapaths_destroy(struct hmap *tracked_datapaths)
+{
+    struct tracked_datapath *t_dp;
+    HMAP_FOR_EACH_POP (t_dp, node, tracked_datapaths) {
+        shash_destroy_free_data(&t_dp->lports);
+        free(t_dp);
+    }
+
+    hmap_destroy(tracked_datapaths);
+}
+
+/* static functions. */
+static struct local_datapath *
+local_datapath_add__(struct hmap *local_datapaths,
+                     const struct sbrec_datapath_binding *dp,
+                     struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
+                     struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
+                     struct ovsdb_idl_index *sbrec_port_binding_by_name,
+                     int depth, struct hmap *tracked_datapaths)
+{
+    uint32_t dp_key = dp->tunnel_key;
+    struct local_datapath *ld = get_local_datapath(local_datapaths, dp_key);
+    if (ld) {
+        return ld;
+    }
+
+    ld = local_datapath_alloc(dp);
+    hmap_insert(local_datapaths, &ld->hmap_node, dp_key);
+    ld->datapath = dp;
+
+    if (tracked_datapaths) {
+        tracked_datapath_add(ld->datapath, TRACKED_RESOURCE_NEW,
+                             tracked_datapaths);
+    }
+
+    if (depth >= 100) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+        VLOG_WARN_RL(&rl, "datapaths nested too deep");
+        return ld;
+    }
+
+    struct sbrec_port_binding *target =
+        sbrec_port_binding_index_init_row(sbrec_port_binding_by_datapath);
+    sbrec_port_binding_index_set_datapath(target, dp);
+
+    const struct sbrec_port_binding *pb;
+    SBREC_PORT_BINDING_FOR_EACH_EQUAL (pb, target,
+                                       sbrec_port_binding_by_datapath) {
+        if (!strcmp(pb->type, "patch") || !strcmp(pb->type, "l3gateway")) {
+            const char *peer_name = smap_get(&pb->options, "peer");
+            if (peer_name) {
+                const struct sbrec_port_binding *peer;
+
+                peer = lport_lookup_by_name(sbrec_port_binding_by_name,
+                                            peer_name);
+
+                if (peer && peer->datapath) {
+                    if (!strcmp(pb->type, "patch")) {
+                        /* Add the datapath to local datapath only for patch
+                         * ports. For l3gateway ports, since gateway router
+                         * resides on one chassis, we don't need to add.
+                         * Otherwise, all other chassis might create patch
+                         * ports between br-int and the provider bridge. */
+                        local_datapath_add__(local_datapaths, peer->datapath,
+                                             sbrec_datapath_binding_by_key,
+                                             sbrec_port_binding_by_datapath,
+                                             sbrec_port_binding_by_name,
+                                             depth + 1, tracked_datapaths);
+                    }
+                    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;
+                }
+            }
+        }
+    }
+    sbrec_port_binding_index_destroy_row(target);
+    return ld;
+}
+
+static struct tracked_datapath *
+tracked_datapath_create(const struct sbrec_datapath_binding *dp,
+                        enum en_tracked_resource_type tracked_type,
+                        struct hmap *tracked_datapaths)
+{
+    struct tracked_datapath *t_dp = xzalloc(sizeof *t_dp);
+    t_dp->dp = dp;
+    t_dp->tracked_type = tracked_type;
+    shash_init(&t_dp->lports);
+    hmap_insert(tracked_datapaths, &t_dp->node, uuid_hash(&dp->header_.uuid));
+    return t_dp;
+}
diff --git a/controller/local_data.h b/controller/local_data.h
new file mode 100644
index 000000000..8b80110fe
--- /dev/null
+++ b/controller/local_data.h
@@ -0,0 +1,111 @@
+/* Copyright (c) 2021, Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LOCAL_DATA_H
+#define LOCAL_DATA_H 1
+
+/* OVS includes. */
+#include "include/openvswitch/shash.h"
+#include "lib/smap.h"
+
+struct sbrec_datapath_binding;
+struct sbrec_port_binding;
+struct ovsdb_idl_index;
+
+/* A logical datapath that has some relevance to this hypervisor.  A logical
+ * datapath D is relevant to hypervisor H if:
+ *
+ *     - Some VIF or l2gateway or l3gateway port in D is located on H.
+ *
+ *     - D is reachable over a series of hops across patch ports, starting from
+ *       a datapath relevant to H.
+ *
+ * The 'hmap_node''s hash value is 'datapath->tunnel_key'. */
+struct local_datapath {
+    struct hmap_node hmap_node;
+    const struct sbrec_datapath_binding *datapath;
+    bool is_switch;
+
+    /* The localnet port in this datapath, if any (at most one is allowed). */
+    const struct sbrec_port_binding *localnet_port;
+
+    struct {
+        const struct sbrec_port_binding *local;
+        const struct sbrec_port_binding *remote;
+    } *peer_ports;
+
+    size_t n_peer_ports;
+    size_t n_allocated_peer_ports;
+
+    struct shash external_ports;
+};
+
+struct local_datapath *local_datapath_alloc(
+    const struct sbrec_datapath_binding *);
+struct local_datapath *get_local_datapath(const struct hmap *,
+                                          uint32_t tunnel_key);
+struct local_datapath * local_datapath_add(
+    struct hmap *local_datapaths, const struct sbrec_datapath_binding *,
+    struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
+    struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
+    struct ovsdb_idl_index *sbrec_port_binding_by_name,
+    struct hmap *tracked_datapaths);
+
+void local_datapaths_destroy(struct hmap *local_datapaths);
+void local_datapath_destroy(struct local_datapath *ld);
+void local_datapath_add_peer_port(
+    const struct sbrec_port_binding *pb,
+    struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
+    struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
+    struct ovsdb_idl_index *sbrec_port_binding_by_name,
+    struct local_datapath *ld,
+    struct hmap *local_datapaths,
+    struct hmap *tracked_datapaths);
+
+void local_datapath_remove_peer_port(const struct sbrec_port_binding *pb,
+                                     struct local_datapath *ld,
+                                     struct hmap *local_datapaths);
+
+enum en_tracked_resource_type {
+    TRACKED_RESOURCE_NEW,
+    TRACKED_RESOURCE_REMOVED,
+    TRACKED_RESOURCE_UPDATED
+};
+
+/* Represents a tracked logical port. */
+struct tracked_lport {
+    const struct sbrec_port_binding *pb;
+    enum en_tracked_resource_type tracked_type;
+};
+
+/* Represent a tracked datapath. */
+struct tracked_datapath {
+    struct hmap_node node;
+    const struct sbrec_datapath_binding *dp;
+    enum en_tracked_resource_type tracked_type;
+    struct shash lports; /* shash of struct tracked_binding_lport. */
+};
+
+struct tracked_datapath * tracked_datapath_add(
+    const struct sbrec_datapath_binding *, enum en_tracked_resource_type,
+    struct hmap *tracked_datapaths);
+struct tracked_datapath *tracked_datapath_find(
+    struct hmap *tracked_datapaths, const struct sbrec_datapath_binding *);
+void tracked_datapath_lport_add(const struct sbrec_port_binding *,
+                                enum en_tracked_resource_type,
+                                struct hmap *tracked_datapaths);
+void tracked_datapaths_destroy(struct hmap *tracked_datapaths);
+
+#endif /* controller/local_data.h */
diff --git a/controller/lport.c b/controller/lport.c
index 478fcfd82..25b4ef200 100644
--- a/controller/lport.c
+++ b/controller/lport.c
@@ -23,6 +23,10 @@
 #include "lib/ovn-sb-idl.h"
 VLOG_DEFINE_THIS_MODULE(lport);
 
+static const struct sbrec_port_binding *get_peer_lport(
+    const struct sbrec_port_binding *pb,
+    struct ovsdb_idl_index *sbrec_port_binding_by_name);
+
 const struct sbrec_port_binding *
 lport_lookup_by_name(struct ovsdb_idl_index *sbrec_port_binding_by_name,
                      const char *name)
@@ -84,6 +88,26 @@ lport_is_chassis_resident(struct ovsdb_idl_index *sbrec_port_binding_by_name,
     }
 }
 
+const struct sbrec_port_binding *
+lport_get_peer(const struct sbrec_port_binding *pb,
+               struct ovsdb_idl_index *sbrec_port_binding_by_name)
+{
+    if (strcmp(pb->type, "patch")) {
+        return NULL;
+    }
+    return get_peer_lport(pb, sbrec_port_binding_by_name);
+}
+
+const struct sbrec_port_binding *
+lport_get_l3gw_peer(const struct sbrec_port_binding *pb,
+                    struct ovsdb_idl_index *sbrec_port_binding_by_name)
+{
+    if (strcmp(pb->type, "l3gateway")) {
+        return NULL;
+    }
+    return get_peer_lport(pb, sbrec_port_binding_by_name);
+}
+
 const struct sbrec_datapath_binding *
 datapath_lookup_by_key(struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
                        uint64_t dp_key)
@@ -120,3 +144,19 @@ mcgroup_lookup_by_dp_name(
 
     return retval;
 }
+
+static const struct sbrec_port_binding *
+get_peer_lport(const struct sbrec_port_binding *pb,
+               struct ovsdb_idl_index *sbrec_port_binding_by_name)
+{
+    const char *peer_name = smap_get(&pb->options, "peer");
+
+    if (!peer_name) {
+        return NULL;
+    }
+
+    const struct sbrec_port_binding *peer;
+    peer = lport_lookup_by_name(sbrec_port_binding_by_name,
+                                peer_name);
+    return (peer && peer->datapath) ? peer : NULL;
+}
diff --git a/controller/lport.h b/controller/lport.h
index 345efc184..43b3d714d 100644
--- a/controller/lport.h
+++ b/controller/lport.h
@@ -54,5 +54,10 @@ lport_is_chassis_resident(struct ovsdb_idl_index *sbrec_port_binding_by_name,
                           const struct sbrec_chassis *chassis,
                           const struct sset *active_tunnels,
                           const char *port_name);
-
+const struct sbrec_port_binding *lport_get_peer(
+    const struct sbrec_port_binding *,
+    struct ovsdb_idl_index *sbrec_port_binding_by_name);
+const struct sbrec_port_binding *lport_get_l3gw_peer(
+    const struct sbrec_port_binding *,
+    struct ovsdb_idl_index *sbrec_port_binding_by_name);
 #endif /* controller/lport.h */
diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
index 55b75f7b2..c299f1a2e 100644
--- a/controller/ovn-controller.c
+++ b/controller/ovn-controller.c
@@ -39,6 +39,7 @@
 #include "lflow.h"
 #include "lflow-cache.h"
 #include "lib/vswitch-idl.h"
+#include "local_data.h"
 #include "lport.h"
 #include "memory.h"
 #include "ofctrl.h"
@@ -138,15 +139,6 @@ struct pending_pkt {
 /* Registered ofctrl seqno type for nb_cfg propagation. */
 static size_t ofctrl_seq_type_nb_cfg;
 
-struct local_datapath *
-get_local_datapath(const struct hmap *local_datapaths, uint32_t tunnel_key)
-{
-    struct hmap_node *node = hmap_first_with_hash(local_datapaths, tunnel_key);
-    return (node
-            ? CONTAINER_OF(node, struct local_datapath, hmap_node)
-            : NULL);
-}
-
 uint32_t
 get_tunnel_type(const char *name)
 {
@@ -1075,14 +1067,14 @@ struct ed_type_runtime_data {
  *
  *  ------------------------------------------------------------------------
  * |                      | This is a hmap of                               |
- * |                      | 'struct tracked_binding_datapath' defined in    |
- * |                      | binding.h. Runtime data handlers for OVS        |
+ * |                      | 'struct tracked_datapath' defined in            |
+ * |                      | ldata.h. Runtime data handlers for OVS          |
  * |                      | Interface and Port Binding changes store the    |
  * | @tracked_dp_bindings | changed datapaths (datapaths added/removed from |
  * |                      | local_datapaths) and changed port bindings      |
- * |                      | (added/updated/deleted in 'lbinding_data').    |
+ * |                      | (added/updated/deleted in 'lbinding_data').     |
  * |                      | So any changes to the runtime data -            |
- * |                      | local_datapaths and lbinding_data is captured  |
+ * |                      | local_datapaths and lbinding_data is captured   |
  * |                      | here.                                           |
  *  ------------------------------------------------------------------------
  * |                      | This is a bool which represents if the runtime  |
@@ -1109,7 +1101,7 @@ struct ed_type_runtime_data {
  *
  *  ---------------------------------------------------------------------
  * | local_datapaths  | The changes to these runtime data is captured in |
- * | lbinding_data   | the @tracked_dp_bindings indirectly and hence it |
+ * | lbinding_data   | the @tracked_dp_bindings indirectly and hence it  |
  * | local_lport_ids  | is not tracked explicitly.                       |
  *  ---------------------------------------------------------------------
  * | local_iface_ids  | This is used internally within the runtime data  |
@@ -1134,7 +1126,7 @@ en_runtime_data_clear_tracked_data(void *data_)
 {
     struct ed_type_runtime_data *data = data_;
 
-    binding_tracked_dp_destroy(&data->tracked_dp_bindings);
+    tracked_datapaths_destroy(&data->tracked_dp_bindings);
     hmap_init(&data->tracked_dp_bindings);
     data->local_lports_changed = false;
     data->tracked = false;
@@ -1172,15 +1164,7 @@ en_runtime_data_cleanup(void *data)
     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) {
-        free(cur_node->peer_ports);
-        shash_destroy(&cur_node->external_ports);
-        hmap_remove(&rt_data->local_datapaths, &cur_node->hmap_node);
-        free(cur_node);
-    }
-    hmap_destroy(&rt_data->local_datapaths);
+    local_datapaths_destroy(&rt_data->local_datapaths);
     shash_destroy_free_data(&rt_data->local_active_ports_ipv6_pd);
     shash_destroy_free_data(&rt_data->local_active_ports_ras);
     local_binding_data_destroy(&rt_data->lbinding_data);
@@ -1292,14 +1276,7 @@ en_runtime_data_run(struct engine_node *node, void *data)
         /* don't cleanup since there is no data yet */
         first_run = false;
     } else {
-        struct local_datapath *cur_node, *next_node;
-        HMAP_FOR_EACH_SAFE (cur_node, next_node, hmap_node, local_datapaths) {
-            free(cur_node->peer_ports);
-            shash_destroy(&cur_node->external_ports);
-            hmap_remove(local_datapaths, &cur_node->hmap_node);
-            free(cur_node);
-        }
-        hmap_clear(local_datapaths);
+        local_datapaths_destroy(local_datapaths);
         shash_clear_free_data(local_active_ipv6_pd);
         shash_clear_free_data(local_active_ras);
         local_binding_data_destroy(&rt_data->lbinding_data);
@@ -1308,6 +1285,7 @@ en_runtime_data_run(struct engine_node *node, void *data)
         sset_destroy(active_tunnels);
         sset_destroy(&rt_data->egress_ifaces);
         smap_destroy(&rt_data->local_iface_ids);
+        hmap_init(local_datapaths);
         sset_init(local_lports);
         related_lports_init(&rt_data->related_lports);
         sset_init(active_tunnels);
@@ -1749,12 +1727,12 @@ port_groups_runtime_data_handler(struct engine_node *node, void *data)
                                                  pg_sb->name);
         ovs_assert(pg_lports);
 
-        struct tracked_binding_datapath *tdp;
+        struct tracked_datapath *tdp;
         bool need_update = false;
         HMAP_FOR_EACH (tdp, node, &rt_data->tracked_dp_bindings) {
             struct shash_node *shash_node;
             SHASH_FOR_EACH (shash_node, &tdp->lports) {
-                struct tracked_binding_lport *lport = shash_node->data;
+                struct tracked_lport *lport = shash_node->data;
                 if (sset_contains(pg_lports, lport->pb->logical_port)) {
                     /* At least one local port-binding change is related to the
                      * port_group, so the port_group_cs_local needs update. */
@@ -1907,9 +1885,9 @@ ct_zones_runtime_data_handler(struct engine_node *node, void *data OVS_UNUSED)
     }
 
     struct hmap *tracked_dp_bindings = &rt_data->tracked_dp_bindings;
-    struct tracked_binding_datapath *tdp;
+    struct tracked_datapath *tdp;
     HMAP_FOR_EACH (tdp, node, tracked_dp_bindings) {
-        if (tdp->is_new) {
+        if (tdp->tracked_type == TRACKED_RESOURCE_NEW) {
             /* A new datapath has been added. Fall back to full recompute. */
             return false;
         }
@@ -2531,9 +2509,9 @@ lflow_output_runtime_data_handler(struct engine_node *node,
     struct hmap *lbs = NULL;
     init_lflow_ctx(node, rt_data, fo, &l_ctx_in, &l_ctx_out);
 
-    struct tracked_binding_datapath *tdp;
+    struct tracked_datapath *tdp;
     HMAP_FOR_EACH (tdp, node, tracked_dp_bindings) {
-        if (tdp->is_new) {
+        if (tdp->tracked_type == TRACKED_RESOURCE_NEW) {
             if (!lbs) {
                 lbs = load_balancers_by_dp_init(&rt_data->local_datapaths,
                                                 l_ctx_in.lb_table);
@@ -2550,7 +2528,7 @@ lflow_output_runtime_data_handler(struct engine_node *node,
         } else {
             struct shash_node *shash_node;
             SHASH_FOR_EACH (shash_node, &tdp->lports) {
-                struct tracked_binding_lport *lport = shash_node->data;
+                struct tracked_lport *lport = shash_node->data;
                 if (!lflow_handle_flows_for_lport(lport->pb, &l_ctx_in,
                                                   &l_ctx_out)) {
                     return false;
diff --git a/controller/ovn-controller.h b/controller/ovn-controller.h
index a3aa6ebd3..578588305 100644
--- a/controller/ovn-controller.h
+++ b/controller/ovn-controller.h
@@ -40,36 +40,6 @@ struct ct_zone_pending_entry {
     enum ct_zone_pending_state state;
 };
 
-/* A logical datapath that has some relevance to this hypervisor.  A logical
- * datapath D is relevant to hypervisor H if:
- *
- *     - Some VIF or l2gateway or l3gateway port in D is located on H.
- *
- *     - D is reachable over a series of hops across patch ports, starting from
- *       a datapath relevant to H.
- *
- * The 'hmap_node''s hash value is 'datapath->tunnel_key'. */
-struct local_datapath {
-    struct hmap_node hmap_node;
-    const struct sbrec_datapath_binding *datapath;
-
-    /* The localnet port in this datapath, if any (at most one is allowed). */
-    const struct sbrec_port_binding *localnet_port;
-
-    struct {
-        const struct sbrec_port_binding *local;
-        const struct sbrec_port_binding *remote;
-    } *peer_ports;
-
-    size_t n_peer_ports;
-    size_t n_allocated_peer_ports;
-
-    struct shash external_ports;
-};
-
-struct local_datapath *get_local_datapath(const struct hmap *,
-                                          uint32_t tunnel_key);
-
 const struct ovsrec_bridge *get_bridge(const struct ovsrec_bridge_table *,
                                        const char *br_name);
 
diff --git a/controller/patch.c b/controller/patch.c
index e54b56354..a661025da 100644
--- a/controller/patch.c
+++ b/controller/patch.c
@@ -20,6 +20,7 @@
 #include "hash.h"
 #include "lflow.h"
 #include "lib/vswitch-idl.h"
+#include "local_data.h"
 #include "lport.h"
 #include "openvswitch/hmap.h"
 #include "openvswitch/vlog.h"
diff --git a/controller/physical.c b/controller/physical.c
index 00cf2ca35..188606927 100644
--- a/controller/physical.c
+++ b/controller/physical.c
@@ -21,6 +21,7 @@
 #include "flow.h"
 #include "ha-chassis.h"
 #include "lflow.h"
+#include "local_data.h"
 #include "lport.h"
 #include "chassis.h"
 #include "lib/bundle.h"
diff --git a/controller/pinctrl.c b/controller/pinctrl.c
index d7166b0a9..d417b858d 100644
--- a/controller/pinctrl.c
+++ b/controller/pinctrl.c
@@ -25,6 +25,7 @@
 #include "encaps.h"
 #include "flow.h"
 #include "ha-chassis.h"
+#include "local_data.h"
 #include "lport.h"
 #include "mac-learn.h"
 #include "nx-match.h"
