[ovs-dev,RFC,v3,06/10] ovn-controller: Incremental logical flow processing

Message ID 1523909665-27961-7-git-send-email-hzhou8@ebay.com
State New
Headers show
Series
  • ovn-controller Incremental Processing
Related show

Commit Message

Han Zhou April 16, 2018, 8:14 p.m.
Persistents flow-table and implements change handler of flow_output
for SB lflow changes.

Signed-off-by: Han Zhou <hzhou8@ebay.com>
---
 include/ovn/actions.h           |   3 +
 ovn/controller/lflow.c          | 178 +++++++++++++++++++++++------
 ovn/controller/lflow.h          |  20 +++-
 ovn/controller/ofctrl.c         | 241 ++++++++++++++++++++++++++++------------
 ovn/controller/ofctrl.h         |  29 ++++-
 ovn/controller/ovn-controller.c | 116 +++++++++++++------
 ovn/controller/physical.c       |  86 +++++++-------
 ovn/controller/physical.h       |   5 +-
 ovn/lib/actions.c               |   6 +-
 ovn/lib/extend-table.c          |  60 +++++++---
 ovn/lib/extend-table.h          |  16 ++-
 11 files changed, 550 insertions(+), 210 deletions(-)

Patch

diff --git a/include/ovn/actions.h b/include/ovn/actions.h
index fb8f515..b6014a2 100644
--- a/include/ovn/actions.h
+++ b/include/ovn/actions.h
@@ -513,6 +513,9 @@  struct ovnact_encode_params {
     /* A struct to figure out the meter_id for meter actions. */
     struct ovn_extend_table *meter_table;
 
+    /* The logical flow uuid that drove this action. */
+    struct uuid lflow_uuid;
+
     /* OVN maps each logical flow table (ltable), one-to-one, onto a physical
      * OpenFlow flow table (ptable).  A number of parameters describe this
      * mapping and data related to flow tables:
diff --git a/ovn/controller/lflow.c b/ovn/controller/lflow.c
index f022aa3..d75e03f 100644
--- a/ovn/controller/lflow.c
+++ b/ovn/controller/lflow.c
@@ -58,7 +58,8 @@  struct condition_aux {
     const struct chassis_index *chassis_index;
 };
 
-static void consider_logical_flow(struct controller_ctx *ctx,
+static bool consider_logical_flow(struct ovn_desired_flow_table *flow_table,
+                                  struct controller_ctx *ctx,
                                   const struct chassis_index *chassis_index,
                                   const struct sbrec_logical_flow *lflow,
                                   const struct hmap *local_datapaths,
@@ -71,7 +72,6 @@  static void consider_logical_flow(struct controller_ctx *ctx,
                                   uint32_t *conj_id_ofs,
                                   const struct shash *addr_sets,
                                   const struct shash *port_groups,
-                                  struct hmap *flow_table,
                                   struct sset *active_tunnels,
                                   struct sset *local_lport_ids);
 
@@ -134,7 +134,8 @@  is_switch(const struct sbrec_datapath_binding *ldp)
 
 /* Adds the logical flows from the Logical_Flow table to flow tables. */
 static void
-add_logical_flows(struct controller_ctx *ctx,
+add_logical_flows(struct ovn_desired_flow_table *flow_table,
+                  struct controller_ctx *ctx,
                   const struct chassis_index *chassis_index,
                   const struct hmap *local_datapaths,
                   struct ovn_extend_table *group_table,
@@ -142,11 +143,10 @@  add_logical_flows(struct controller_ctx *ctx,
                   const struct sbrec_chassis *chassis,
                   const struct shash *addr_sets,
                   const struct shash *port_groups,
-                  struct hmap *flow_table,
                   struct sset *active_tunnels,
-                  struct sset *local_lport_ids)
+                  struct sset *local_lport_ids,
+                  uint32_t *conj_id_ofs)
 {
-    uint32_t conj_id_ofs = 1;
     const struct sbrec_logical_flow *lflow;
 
     struct hmap dhcp_opts = HMAP_INITIALIZER(&dhcp_opts);
@@ -168,12 +168,16 @@  add_logical_flows(struct controller_ctx *ctx,
     nd_ra_opts_init(&nd_ra_opts);
 
     SBREC_LOGICAL_FLOW_FOR_EACH (lflow, ctx->ovnsb_idl) {
-        consider_logical_flow(ctx, chassis_index,
-                              lflow, local_datapaths,
-                              group_table, meter_table, chassis,
-                              &dhcp_opts, &dhcpv6_opts, &nd_ra_opts,
-                              &conj_id_ofs, addr_sets, port_groups,
-                              flow_table, active_tunnels, local_lport_ids);
+        if (!consider_logical_flow(flow_table, ctx, chassis_index,
+                                   lflow, local_datapaths,
+                                   group_table, meter_table, chassis,
+                                   &dhcp_opts, &dhcpv6_opts, &nd_ra_opts,
+                                   conj_id_ofs, addr_sets, port_groups,
+                                   active_tunnels, local_lport_ids)) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5);
+            VLOG_ERR_RL(&rl, "Conjunction id overflow when processing lflow "
+                        UUID_FMT, UUID_ARGS(&lflow->header_.uuid));
+        }
     }
 
     dhcp_opts_destroy(&dhcp_opts);
@@ -181,8 +185,95 @@  add_logical_flows(struct controller_ctx *ctx,
     nd_ra_opts_destroy(&nd_ra_opts);
 }
 
-static void
-consider_logical_flow(struct controller_ctx *ctx,
+bool
+lflow_handle_changed_flows(struct ovn_desired_flow_table *flow_table,
+                           struct controller_ctx *ctx,
+                           const struct sbrec_chassis *chassis,
+                           const struct chassis_index *chassis_index,
+                           const struct hmap *local_datapaths,
+                           struct ovn_extend_table *group_table,
+                           struct ovn_extend_table *meter_table,
+                           const struct shash *addr_sets,
+                           const struct shash *port_groups,
+                           struct sset *active_tunnels,
+                           struct sset *local_lport_ids,
+                           uint32_t *conj_id_ofs)
+{
+    bool ret = true;
+    const struct sbrec_logical_flow *lflow;
+
+    struct hmap dhcp_opts = HMAP_INITIALIZER(&dhcp_opts);
+    struct hmap dhcpv6_opts = HMAP_INITIALIZER(&dhcpv6_opts);
+    const struct sbrec_dhcp_options *dhcp_opt_row;
+    SBREC_DHCP_OPTIONS_FOR_EACH(dhcp_opt_row, ctx->ovnsb_idl) {
+        dhcp_opt_add(&dhcp_opts, dhcp_opt_row->name, dhcp_opt_row->code,
+                     dhcp_opt_row->type);
+    }
+
+
+    const struct sbrec_dhcpv6_options *dhcpv6_opt_row;
+    SBREC_DHCPV6_OPTIONS_FOR_EACH(dhcpv6_opt_row, ctx->ovnsb_idl) {
+       dhcp_opt_add(&dhcpv6_opts, dhcpv6_opt_row->name, dhcpv6_opt_row->code,
+                    dhcpv6_opt_row->type);
+    }
+
+    struct hmap nd_ra_opts = HMAP_INITIALIZER(&nd_ra_opts);
+    nd_ra_opts_init(&nd_ra_opts);
+
+    /* Handle removed flows first, and then other flows, so that when
+     * the flows being added and removed have same match conditions
+     * can be processed in the proper order */
+    SBREC_LOGICAL_FLOW_FOR_EACH_TRACKED (lflow, ctx->ovnsb_idl) {
+        /* Remove any flows that should be removed. */
+        if (sbrec_logical_flow_is_deleted(lflow)) {
+            VLOG_DBG("handle deleted lflow "UUID_FMT,
+                     UUID_ARGS(&lflow->header_.uuid));
+            ofctrl_remove_flows(flow_table, &lflow->header_.uuid);
+        }
+    }
+    SBREC_LOGICAL_FLOW_FOR_EACH_TRACKED (lflow, ctx->ovnsb_idl) {
+        if (!sbrec_logical_flow_is_deleted(lflow)) {
+            /* Now, add/modify existing flows. If the logical
+             * flow is a modification, just remove the flows
+             * for this row, and then add new flows. */
+            if (!sbrec_logical_flow_is_new(lflow)) {
+                VLOG_DBG("handle updated lflow "UUID_FMT,
+                         UUID_ARGS(&lflow->header_.uuid));
+                ofctrl_remove_flows(flow_table, &lflow->header_.uuid);
+            }
+            VLOG_DBG("handle new lflow "UUID_FMT,
+                     UUID_ARGS(&lflow->header_.uuid));
+            if (!consider_logical_flow(flow_table, ctx, chassis_index,
+                                       lflow, local_datapaths,
+                                       group_table, meter_table, chassis,
+                                       &dhcp_opts, &dhcpv6_opts, &nd_ra_opts,
+                                       conj_id_ofs, addr_sets, port_groups,
+                                       active_tunnels, local_lport_ids)) {
+                ret = false;
+                break;
+            }
+        }
+    }
+    dhcp_opts_destroy(&dhcp_opts);
+    dhcp_opts_destroy(&dhcpv6_opts);
+    nd_ra_opts_destroy(&nd_ra_opts);
+    return ret;
+}
+
+static bool
+update_conj_id_ofs(uint32_t *conj_id_ofs, uint32_t n_conjs)
+{
+    if (*conj_id_ofs + n_conjs < *conj_id_ofs) {
+        /* overflow */
+        return false;
+    }
+    *conj_id_ofs += n_conjs;
+    return true;
+}
+
+static bool
+consider_logical_flow(struct ovn_desired_flow_table *flow_table,
+                      struct controller_ctx *ctx,
                       const struct chassis_index *chassis_index,
                       const struct sbrec_logical_flow *lflow,
                       const struct hmap *local_datapaths,
@@ -195,7 +286,6 @@  consider_logical_flow(struct controller_ctx *ctx,
                       uint32_t *conj_id_ofs,
                       const struct shash *addr_sets,
                       const struct shash *port_groups,
-                      struct hmap *flow_table,
                       struct sset *active_tunnels,
                       struct sset *local_lport_ids)
 {
@@ -204,10 +294,14 @@  consider_logical_flow(struct controller_ctx *ctx,
 
     const struct sbrec_datapath_binding *ldp = lflow->logical_datapath;
     if (!ldp) {
-        return;
+        VLOG_DBG("lflow "UUID_FMT" has no datapath binding, skip",
+                 UUID_ARGS(&lflow->header_.uuid));
+        return true;
     }
     if (!get_local_datapath(local_datapaths, ldp->tunnel_key)) {
-        return;
+        VLOG_DBG("lflow "UUID_FMT" is not for local datapath, skip",
+                 UUID_ARGS(&lflow->header_.uuid));
+        return true;
     }
 
     /* Determine translation of logical table IDs to physical table IDs. */
@@ -245,7 +339,7 @@  consider_logical_flow(struct controller_ctx *ctx,
         free(error);
         ovnacts_free(ovnacts.data, ovnacts.size);
         ofpbuf_uninit(&ovnacts);
-        return;
+        return true;
     }
 
     /* Translate OVN match into table of OpenFlow matches. */
@@ -269,7 +363,7 @@  consider_logical_flow(struct controller_ctx *ctx,
         free(error);
         ovnacts_free(ovnacts.data, ovnacts.size);
         ofpbuf_uninit(&ovnacts);
-        return;
+        return true;
     }
 
     struct lookup_port_aux aux = {
@@ -285,10 +379,12 @@  consider_logical_flow(struct controller_ctx *ctx,
     expr_destroy(expr);
 
     if (hmap_is_empty(&matches)) {
+        VLOG_DBG("lflow "UUID_FMT" matches are empty, skip",
+                 UUID_ARGS(&lflow->header_.uuid));
         ovnacts_free(ovnacts.data, ovnacts.size);
         ofpbuf_uninit(&ovnacts);
         expr_matches_destroy(&matches);
-        return;
+        return true;
     }
 
     /* Encode OVN logical actions into OpenFlow. */
@@ -300,6 +396,7 @@  consider_logical_flow(struct controller_ctx *ctx,
         .is_switch = is_switch(ldp),
         .group_table = group_table,
         .meter_table = meter_table,
+        .lflow_uuid = lflow->header_.uuid,
 
         .pipeline = ingress ? OVNACT_P_INGRESS : OVNACT_P_EGRESS,
         .ingress_ptable = OFTABLE_LOG_INGRESS_PIPELINE,
@@ -328,13 +425,18 @@  consider_logical_flow(struct controller_ctx *ctx,
                 char buf[16];
                 snprintf(buf, sizeof(buf), "%"PRId64"_%"PRId64, dp_id, port_id);
                 if (!sset_contains(local_lport_ids, buf)) {
+                    VLOG_DBG("lflow "UUID_FMT
+                             " port %s in match is not local, skip",
+                             UUID_ARGS(&lflow->header_.uuid),
+                             buf);
                     continue;
                 }
             }
         }
         if (!m->n) {
             ofctrl_add_flow(flow_table, ptable, lflow->priority,
-                            lflow->header_.uuid.parts[0], &m->match, &ofpacts);
+                            lflow->header_.uuid.parts[0], &m->match, &ofpacts,
+                            &lflow->header_.uuid);
         } else {
             uint64_t conj_stubs[64 / 8];
             struct ofpbuf conj;
@@ -350,7 +452,7 @@  consider_logical_flow(struct controller_ctx *ctx,
                 dst->n_clauses = src->n_clauses;
             }
             ofctrl_add_flow(flow_table, ptable, lflow->priority, 0, &m->match,
-                            &conj);
+                            &conj, &lflow->header_.uuid);
             ofpbuf_uninit(&conj);
         }
     }
@@ -358,7 +460,7 @@  consider_logical_flow(struct controller_ctx *ctx,
     /* Clean up. */
     expr_matches_destroy(&matches);
     ofpbuf_uninit(&ofpacts);
-    *conj_id_ofs += n_conjs;
+    return update_conj_id_ofs(conj_id_ofs, n_conjs);
 }
 
 static void
@@ -374,9 +476,9 @@  put_load(const uint8_t *data, size_t len,
 }
 
 static void
-consider_neighbor_flow(struct controller_ctx *ctx,
-                       const struct sbrec_mac_binding *b,
-                       struct hmap *flow_table)
+consider_neighbor_flow(struct ovn_desired_flow_table *flow_table,
+                       struct controller_ctx *ctx,
+                       const struct sbrec_mac_binding *b)
 {
     const struct sbrec_port_binding *pb
         = lport_lookup_by_name(ctx->ovnsb_idl, b->logical_port);
@@ -418,26 +520,28 @@  consider_neighbor_flow(struct controller_ctx *ctx,
     uint64_t stub[1024 / 8];
     struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(stub);
     put_load(mac.ea, sizeof mac.ea, MFF_ETH_DST, 0, 48, &ofpacts);
-    ofctrl_add_flow(flow_table, OFTABLE_MAC_BINDING, 100, 0, &match, &ofpacts);
+    ofctrl_add_flow(flow_table, OFTABLE_MAC_BINDING, 100, 0, &match, &ofpacts,
+                    &b->header_.uuid);
     ofpbuf_uninit(&ofpacts);
 }
 
 /* Adds an OpenFlow flow to flow tables for each MAC binding in the OVN
  * southbound database. */
 static void
-add_neighbor_flows(struct controller_ctx *ctx,
-                   struct hmap *flow_table)
+add_neighbor_flows(struct ovn_desired_flow_table *flow_table,
+                   struct controller_ctx *ctx)
 {
     const struct sbrec_mac_binding *b;
     SBREC_MAC_BINDING_FOR_EACH (b, ctx->ovnsb_idl) {
-        consider_neighbor_flow(ctx, b, flow_table);
+        consider_neighbor_flow(flow_table, ctx, b);
     }
 }
 
 /* Translates logical flows in the Logical_Flow table in the OVN_SB database
  * into OpenFlow flows.  See ovn-architecture(7) for more information. */
 void
-lflow_run(struct controller_ctx *ctx,
+lflow_run(struct ovn_desired_flow_table *flow_table,
+          struct controller_ctx *ctx,
           const struct sbrec_chassis *chassis,
           const struct chassis_index *chassis_index,
           const struct hmap *local_datapaths,
@@ -445,15 +549,15 @@  lflow_run(struct controller_ctx *ctx,
           struct ovn_extend_table *meter_table,
           const struct shash *addr_sets,
           const struct shash *port_groups,
-          struct hmap *flow_table,
           struct sset *active_tunnels,
-          struct sset *local_lport_ids)
+          struct sset *local_lport_ids,
+          uint32_t *conj_id_ofs)
 {
-    add_logical_flows(ctx, chassis_index, local_datapaths,
+    add_logical_flows(flow_table, ctx, chassis_index, local_datapaths,
                       group_table, meter_table, chassis, addr_sets,
-                      port_groups, flow_table, active_tunnels,
-                      local_lport_ids);
-    add_neighbor_flows(ctx, flow_table);
+                      port_groups, active_tunnels, local_lport_ids,
+                      conj_id_ofs);
+    add_neighbor_flows(flow_table, ctx);
 }
 
 void
diff --git a/ovn/controller/lflow.h b/ovn/controller/lflow.h
index dcf2fe7..4e96d50 100644
--- a/ovn/controller/lflow.h
+++ b/ovn/controller/lflow.h
@@ -38,6 +38,7 @@ 
 struct chassis_index;
 struct controller_ctx;
 struct ovn_extend_table;
+struct ovn_desired_flow_table;
 struct hmap;
 struct sbrec_chassis;
 struct simap;
@@ -62,7 +63,8 @@  struct uuid;
 #define LOG_PIPELINE_LEN 24
 
 void lflow_init(void);
-void lflow_run(struct controller_ctx *,
+void lflow_run(struct ovn_desired_flow_table *flow_table,
+               struct controller_ctx *,
                const struct sbrec_chassis *chassis,
                const struct chassis_index *,
                const struct hmap *local_datapaths,
@@ -70,9 +72,21 @@  void lflow_run(struct controller_ctx *,
                struct ovn_extend_table *meter_table,
                const struct shash *addr_sets,
                const struct shash *port_groups,
-               struct hmap *flow_table,
                struct sset *active_tunnels,
-               struct sset *local_lport_ids);
+               struct sset *local_lport_ids,
+               uint32_t *conj_id_ofs);
+bool lflow_handle_changed_flows(struct ovn_desired_flow_table *flow_table,
+                                struct controller_ctx *ctx,
+                                const struct sbrec_chassis *chassis,
+                                const struct chassis_index *chassis_index,
+                                const struct hmap *local_datapaths,
+                                struct ovn_extend_table *group_table,
+                                struct ovn_extend_table *extend_table,
+                                const struct shash *addr_sets,
+                                const struct shash *port_groups,
+                                struct sset *active_tunnels,
+                                struct sset *local_lport_ids,
+                                uint32_t *conj_id_ofs);
 void lflow_destroy(void);
 
 #endif /* ovn/lflow.h */
diff --git a/ovn/controller/ofctrl.c b/ovn/controller/ofctrl.c
index 134f0e5..f251cd6 100644
--- a/ovn/controller/ofctrl.c
+++ b/ovn/controller/ofctrl.c
@@ -20,6 +20,7 @@ 
 #include "dp-packet.h"
 #include "flow.h"
 #include "hash.h"
+#include "hindex.h"
 #include "lflow.h"
 #include "ofctrl.h"
 #include "openflow/openflow.h"
@@ -52,7 +53,8 @@  VLOG_DEFINE_THIS_MODULE(ofctrl);
 
 /* An OpenFlow flow. */
 struct ovn_flow {
-    struct hmap_node hmap_node; /* For match based hashing. */
+    struct hmap_node match_hmap_node; /* For match based hashing. */
+    struct hindex_node uuid_hindex_node; /* For uuid based hashing. */
     struct ovs_list list_node; /* For handling lists of flows. */
 
     /* Key. */
@@ -61,14 +63,16 @@  struct ovn_flow {
     struct minimatch match;
 
     /* Data. */
+    struct uuid sb_uuid;
     struct ofpact *ofpacts;
     size_t ofpacts_len;
     uint64_t cookie;
 };
 
-static uint32_t ovn_flow_hash(const struct ovn_flow *);
+static uint32_t ovn_flow_match_hash(const struct ovn_flow *);
 static struct ovn_flow *ovn_flow_lookup(struct hmap *flow_table,
-                                        const struct ovn_flow *target);
+                                        const struct ovn_flow *target,
+                                        bool cmp_sb_uuid);
 static char *ovn_flow_to_string(const struct ovn_flow *);
 static void ovn_flow_log(const struct ovn_flow *, const char *action);
 static void ovn_flow_destroy(struct ovn_flow *);
@@ -146,6 +150,10 @@  static struct ovn_extend_table *meters;
  * S_CLEAR_FLOWS or S_UPDATE_FLOWS, this is really the option we have. */
 static enum mf_field_id mff_ovn_geneve;
 
+/* indicates if flows need to be reinstalled for scenarios when ovs
+ * is restarted, even if there is no change in the desired flow table */
+static bool need_reinstall_flows;
+
 static ovs_be32 queue_msg(struct ofpbuf *);
 
 static struct ofpbuf *encode_flow_mod(struct ofputil_flow_mod *);
@@ -154,8 +162,8 @@  static struct ofpbuf *encode_group_mod(const struct ofputil_group_mod *);
 
 static struct ofpbuf *encode_meter_mod(const struct ofputil_meter_mod *);
 
-static void ovn_flow_table_clear(struct hmap *flow_table);
-static void ovn_flow_table_destroy(struct hmap *flow_table);
+static void ovn_installed_flow_table_clear(void);
+static void ovn_installed_flow_table_destroy(void);
 
 static void ofctrl_recv(const struct ofp_header *, enum ofptype);
 
@@ -375,6 +383,7 @@  run_S_CLEAR_FLOWS(void)
 {
     VLOG_DBG("clearing all flows");
 
+    need_reinstall_flows = true;
     /* Send a flow_mod to delete all flows. */
     struct ofputil_flow_mod fm = {
         .table_id = OFPTT_ALL,
@@ -384,9 +393,6 @@  run_S_CLEAR_FLOWS(void)
     queue_msg(encode_flow_mod(&fm));
     minimatch_destroy(&fm.match);
 
-    /* Clear installed_flows, to match the state of the switch. */
-    ovn_flow_table_clear(&installed_flows);
-
     /* Send a group_mod to delete all groups. */
     struct ofputil_group_mod gm;
     memset(&gm, 0, sizeof gm);
@@ -397,6 +403,9 @@  run_S_CLEAR_FLOWS(void)
     queue_msg(encode_group_mod(&gm));
     ofputil_uninit_group_mod(&gm);
 
+    /* Clear installed_flows, to match the state of the switch. */
+    ovn_installed_flow_table_clear();
+
     /* Clear existing groups, to match the state of the switch. */
     if (groups) {
         ovn_extend_table_clear(groups, true);
@@ -580,7 +589,7 @@  void
 ofctrl_destroy(void)
 {
     rconn_destroy(swconn);
-    ovn_flow_table_destroy(&installed_flows);
+    ovn_installed_flow_table_destroy();
     rconn_packet_counter_destroy(tx_counter);
     expr_symtab_destroy(&symtab);
     shash_destroy(&symtab);
@@ -632,14 +641,17 @@  ofctrl_recv(const struct ofp_header *oh, enum ofptype type)
  * the OpenFlow table numbered 'table_id' with the given 'priority' and
  * OpenFlow 'cookie'.  The caller retains ownership of 'match' and 'actions'.
  *
+ * The flow is also added to a hash index based on sb_uuid.
+ *
  * This just assembles the desired flow table in memory.  Nothing is actually
  * sent to the switch until a later call to ofctrl_put().
  *
  * The caller should initialize its own hmap to hold the flows. */
 void
-ofctrl_add_flow(struct hmap *desired_flows,
+ofctrl_add_flow(struct ovn_desired_flow_table *flow_table,
                 uint8_t table_id, uint16_t priority, uint64_t cookie,
-                const struct match *match, const struct ofpbuf *actions)
+                const struct match *match, const struct ofpbuf *actions,
+                const struct uuid *sb_uuid)
 {
     struct ovn_flow *f = xmalloc(sizeof *f);
     f->table_id = table_id;
@@ -647,10 +659,14 @@  ofctrl_add_flow(struct hmap *desired_flows,
     minimatch_init(&f->match, match);
     f->ofpacts = xmemdup(actions->data, actions->size);
     f->ofpacts_len = actions->size;
-    f->hmap_node.hash = ovn_flow_hash(f);
+    f->sb_uuid = *sb_uuid;
+    f->match_hmap_node.hash = ovn_flow_match_hash(f);
+    f->uuid_hindex_node.hash = uuid_hash(&f->sb_uuid);
     f->cookie = cookie;
 
-    if (ovn_flow_lookup(desired_flows, f)) {
+    ovn_flow_log(f, "ofctrl_add_flow");
+
+    if (ovn_flow_lookup(&flow_table->match_flow_table, f, true)) {
         static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5);
         if (!VLOG_DROP_DBG(&rl)) {
             char *s = ovn_flow_to_string(f);
@@ -662,19 +678,44 @@  ofctrl_add_flow(struct hmap *desired_flows,
         return;
     }
 
-    hmap_insert(desired_flows, &f->hmap_node, f->hmap_node.hash);
+    hmap_insert(&flow_table->match_flow_table, &f->match_hmap_node,
+                f->match_hmap_node.hash);
+    hindex_insert(&flow_table->uuid_flow_table, &f->uuid_hindex_node,
+                  f->uuid_hindex_node.hash);
+}
+
+/* Removes a bundles of flows from the flow table. */
+void
+ofctrl_remove_flows(struct ovn_desired_flow_table *flow_table,
+                    const struct uuid *sb_uuid)
+{
+    struct ovn_flow *f, *next;
+    HINDEX_FOR_EACH_WITH_HASH_SAFE (f, next, uuid_hindex_node,
+                                    uuid_hash(sb_uuid),
+                                    &flow_table->uuid_flow_table) {
+        if (uuid_equals(&f->sb_uuid, sb_uuid)) {
+            ovn_flow_log(f, "ofctrl_remove_flow");
+            hmap_remove(&flow_table->match_flow_table,
+                        &f->match_hmap_node);
+            hindex_remove(&flow_table->uuid_flow_table, &f->uuid_hindex_node);
+            ovn_flow_destroy(f);
+        }
+    }
+
+    /* remove any related group and meter info */
+    ovn_extend_table_remove_desired(groups, sb_uuid);
+    ovn_extend_table_remove_desired(meters, sb_uuid);
 }
 
 
 /* ovn_flow. */
 
-/* Returns a hash of the key in 'f'. */
+/* Returns a hash of the match key in 'f'. */
 static uint32_t
-ovn_flow_hash(const struct ovn_flow *f)
+ovn_flow_match_hash(const struct ovn_flow *f)
 {
     return hash_2words((f->table_id << 16) | f->priority,
                        minimatch_hash(&f->match, 0));
-
 }
 
 /* Duplicate an ovn_flow structure. */
@@ -687,23 +728,28 @@  ofctrl_dup_flow(struct ovn_flow *src)
     minimatch_clone(&dst->match, &src->match);
     dst->ofpacts = xmemdup(src->ofpacts, src->ofpacts_len);
     dst->ofpacts_len = src->ofpacts_len;
-    dst->hmap_node.hash = ovn_flow_hash(dst);
+    dst->sb_uuid = src->sb_uuid;
+    dst->match_hmap_node.hash = ovn_flow_match_hash(dst);
+    dst->uuid_hindex_node.hash = uuid_hash(&src->sb_uuid);
     return dst;
 }
 
 /* Finds and returns an ovn_flow in 'flow_table' whose key is identical to
  * 'target''s key, or NULL if there is none. */
 static struct ovn_flow *
-ovn_flow_lookup(struct hmap *flow_table, const struct ovn_flow *target)
+ovn_flow_lookup(struct hmap *flow_table, const struct ovn_flow *target,
+                bool cmp_sb_uuid)
 {
     struct ovn_flow *f;
 
-    HMAP_FOR_EACH_WITH_HASH (f, hmap_node, target->hmap_node.hash,
+    HMAP_FOR_EACH_WITH_HASH (f, match_hmap_node, target->match_hmap_node.hash,
                              flow_table) {
         if (f->table_id == target->table_id
             && f->priority == target->priority
             && minimatch_equal(&f->match, &target->match)) {
-            return f;
+            if (!cmp_sb_uuid || uuid_equals(&target->sb_uuid, &f->sb_uuid)) {
+                return f;
+            }
         }
     }
     return NULL;
@@ -713,6 +759,7 @@  static char *
 ovn_flow_to_string(const struct ovn_flow *f)
 {
     struct ds s = DS_EMPTY_INITIALIZER;
+    ds_put_format(&s, "sb_uuid="UUID_FMT", ", UUID_ARGS(&f->sb_uuid));
     ds_put_format(&s, "table_id=%"PRIu8", ", f->table_id);
     ds_put_format(&s, "priority=%"PRIu16", ", f->priority);
     minimatch_format(&f->match, NULL, NULL, &s, OFP_DEFAULT_PRIORITY);
@@ -743,22 +790,48 @@  ovn_flow_destroy(struct ovn_flow *f)
 }
 
 /* Flow tables of struct ovn_flow. */
+void
+ovn_desired_flow_table_init(struct ovn_desired_flow_table *flow_table)
+{
+    hmap_init(&flow_table->match_flow_table);
+    hindex_init(&flow_table->uuid_flow_table);
+}
+
+void
+ovn_desired_flow_table_clear(struct ovn_desired_flow_table *flow_table)
+{
+    struct ovn_flow *f, *next;
+    HMAP_FOR_EACH_SAFE (f, next, match_hmap_node,
+                        &flow_table->match_flow_table) {
+        hmap_remove(&flow_table->match_flow_table, &f->match_hmap_node);
+        hindex_remove(&flow_table->uuid_flow_table, &f->uuid_hindex_node);
+        ovn_flow_destroy(f);
+    }
+}
+
+void
+ovn_desired_flow_table_destroy(struct ovn_desired_flow_table *flow_table)
+{
+    ovn_desired_flow_table_clear(flow_table);
+    hmap_destroy(&flow_table->match_flow_table);
+    hindex_destroy(&flow_table->uuid_flow_table);
+}
 
 static void
-ovn_flow_table_clear(struct hmap *flow_table)
+ovn_installed_flow_table_clear(void)
 {
     struct ovn_flow *f, *next;
-    HMAP_FOR_EACH_SAFE (f, next, hmap_node, flow_table) {
-        hmap_remove(flow_table, &f->hmap_node);
+    HMAP_FOR_EACH_SAFE (f, next, match_hmap_node, &installed_flows) {
+        hmap_remove(&installed_flows, &f->match_hmap_node);
         ovn_flow_destroy(f);
     }
 }
 
 static void
-ovn_flow_table_destroy(struct hmap *flow_table)
+ovn_installed_flow_table_destroy(void)
 {
-    ovn_flow_table_clear(flow_table);
-    hmap_destroy(flow_table);
+    ovn_installed_flow_table_clear();
+    hmap_destroy(&installed_flows);
 }
 
 /* Flow table update. */
@@ -823,7 +896,7 @@  add_ct_flush_zone(uint16_t zone_id, struct ovs_list *msgs)
  * in the correct state and not backlogged with existing flow_mods.  (Our
  * criteria for being backlogged appear very conservative, but the socket
  * between ovn-controller and OVS provides some buffering.) */
-bool
+static bool
 ofctrl_can_put(void)
 {
     if (state != S_UPDATE_FLOWS
@@ -838,9 +911,7 @@  ofctrl_can_put(void)
  * with ofctrl_add_flow().
  *
  * Replaces the group table and meter table on the switch, if possible, by the
- *  contents of 'groups->desired'.  Regardless of whether the group table
- * is updated, this deletes all the groups from the 'groups->desired' and frees
- * them. (The hmap itself isn't destroyed.)
+ * contents of 'desired'.
  *
  * Sends conntrack flush messages to each zone in 'pending_ct_zones' that
  * is in the CT_ZONE_OF_QUEUED state and then moves the zone into the
@@ -848,16 +919,42 @@  ofctrl_can_put(void)
  *
  * This should be called after ofctrl_run() within the main loop. */
 void
-ofctrl_put(struct hmap *flow_table, struct shash *pending_ct_zones,
-           int64_t nb_cfg)
+ofctrl_put(struct ovn_desired_flow_table *flow_table,
+           struct shash *pending_ct_zones,
+           int64_t nb_cfg,
+           bool flow_changed)
 {
+    static bool skipped_last_time = false;
+    static int64_t old_nb_cfg = 0;
+    bool need_put = false;
+    if (flow_changed || skipped_last_time || need_reinstall_flows) {
+        need_put = true;
+    } else if (nb_cfg != old_nb_cfg) {
+        /* nb_cfg changed since last ofctrl_put() call */
+        if (cur_cfg == old_nb_cfg) {
+            /* we were up-to-date already, so just update with the
+             * new nb_cfg */
+            cur_cfg = nb_cfg;
+        } else {
+            need_put = true;
+        }
+    }
+
+    old_nb_cfg = nb_cfg;
+
+    if (!need_put) {
+        VLOG_DBG("ofctrl_put not needed");
+        return;
+    }
     if (!ofctrl_can_put()) {
-        ovn_flow_table_clear(flow_table);
-        ovn_extend_table_clear(groups, false);
-        ovn_extend_table_clear(meters, false);
+        VLOG_DBG("ofctrl_put can't be performed");
+        skipped_last_time = true;
         return;
     }
 
+    skipped_last_time = false;
+    need_reinstall_flows = false;
+
     /* OpenFlow messages to send to the switch to bring it up-to-date. */
     struct ovs_list msgs = OVS_LIST_INITIALIZER(&msgs);
 
@@ -921,8 +1018,9 @@  ofctrl_put(struct hmap *flow_table, struct shash *pending_ct_zones,
      * longer desired, delete them; if any of them should have different
      * actions, update them. */
     struct ovn_flow *i, *next;
-    HMAP_FOR_EACH_SAFE (i, next, hmap_node, &installed_flows) {
-        struct ovn_flow *d = ovn_flow_lookup(flow_table, i);
+    HMAP_FOR_EACH_SAFE (i, next, match_hmap_node, &installed_flows) {
+        struct ovn_flow *d = ovn_flow_lookup(&flow_table->match_flow_table,
+                                             i, false);
         if (!d) {
             /* Installed flow is no longer desirable.  Delete it from the
              * switch and from installed_flows. */
@@ -935,9 +1033,13 @@  ofctrl_put(struct hmap *flow_table, struct shash *pending_ct_zones,
             add_flow_mod(&fm, &msgs);
             ovn_flow_log(i, "removing installed");
 
-            hmap_remove(&installed_flows, &i->hmap_node);
+            hmap_remove(&installed_flows, &i->match_hmap_node);
             ovn_flow_destroy(i);
         } else {
+            if (!uuid_equals(&i->sb_uuid, &d->sb_uuid)) {
+                /* Update installed flow's UUID. */
+                i->sb_uuid = d->sb_uuid;
+            }
             if (!ofpacts_equal(i->ofpacts, i->ofpacts_len,
                                d->ofpacts, d->ofpacts_len)) {
                 /* Update actions in installed flow. */
@@ -954,38 +1056,37 @@  ofctrl_put(struct hmap *flow_table, struct shash *pending_ct_zones,
 
                 /* Replace 'i''s actions by 'd''s. */
                 free(i->ofpacts);
-                i->ofpacts = d->ofpacts;
+                i->ofpacts = xmemdup(d->ofpacts, d->ofpacts_len);
                 i->ofpacts_len = d->ofpacts_len;
-                d->ofpacts = NULL;
-                d->ofpacts_len = 0;
             }
 
-            hmap_remove(flow_table, &d->hmap_node);
-            ovn_flow_destroy(d);
         }
     }
 
-    /* The previous loop removed from 'flow_table' all of the flows that are
-     * already installed.  Thus, any flows remaining in 'flow_table' need to
-     * be added to the flow table. */
+    /* Iterate through the desired flows and add those that aren't found
+     * in the installed flow table. */
     struct ovn_flow *d;
-    HMAP_FOR_EACH_SAFE (d, next, hmap_node, flow_table) {
-        /* Send flow_mod to add flow. */
-        struct ofputil_flow_mod fm = {
-            .match = d->match,
-            .priority = d->priority,
-            .table_id = d->table_id,
-            .ofpacts = d->ofpacts,
-            .ofpacts_len = d->ofpacts_len,
-            .new_cookie = htonll(d->cookie),
-            .command = OFPFC_ADD,
-        };
-        add_flow_mod(&fm, &msgs);
-        ovn_flow_log(d, "adding installed");
-
-        /* Move 'd' from 'flow_table' to installed_flows. */
-        hmap_remove(flow_table, &d->hmap_node);
-        hmap_insert(&installed_flows, &d->hmap_node, d->hmap_node.hash);
+    HMAP_FOR_EACH (d, match_hmap_node, &flow_table->match_flow_table) {
+        i = ovn_flow_lookup(&installed_flows, d, false);
+        if (!i) {
+            /* Send flow_mod to add flow. */
+            struct ofputil_flow_mod fm = {
+                .match = d->match,
+                .priority = d->priority,
+                .table_id = d->table_id,
+                .ofpacts = d->ofpacts,
+                .ofpacts_len = d->ofpacts_len,
+                .new_cookie = htonll(d->cookie),
+                .command = OFPFC_ADD,
+            };
+            add_flow_mod(&fm, &msgs);
+            ovn_flow_log(d, "adding installed");
+
+            /* Copy 'd' from 'flow_table' to installed_flows. */
+            struct ovn_flow *new_node = ofctrl_dup_flow(d);
+            hmap_insert(&installed_flows, &new_node->match_hmap_node,
+                        new_node->match_hmap_node.hash);
+        }
     }
 
     /* Iterate through the installed groups from previous runs. If they
@@ -1010,11 +1111,11 @@  ofctrl_put(struct hmap *flow_table, struct shash *pending_ct_zones,
         }
         free(group_string);
         ofputil_uninit_group_mod(&gm);
-        ovn_extend_table_remove(groups, installed);
+        ovn_extend_table_remove_existing(groups, installed);
     }
 
-    /* Move the contents of groups->desired to groups->existing. */
-    ovn_extend_table_move(groups);
+    /* Sync the contents of groups->desired to groups->existing. */
+    ovn_extend_table_sync(groups);
 
     /* Iterate through the installed meters from previous runs. If they
      * are not needed delete them. */
@@ -1037,11 +1138,11 @@  ofctrl_put(struct hmap *flow_table, struct shash *pending_ct_zones,
             free(error);
         }
         free(meter_string);
-        ovn_extend_table_remove(meters, m_installed);
+        ovn_extend_table_remove_existing(meters, m_installed);
     }
 
-    /* Move the contents of meters->desired to meters->existing. */
-    ovn_extend_table_move(meters);
+    /* Sync the contents of meters->desired to meters->existing. */
+    ovn_extend_table_sync(meters);
 
     if (!ovs_list_is_empty(&msgs)) {
         /* Add a barrier to the list of messages. */
diff --git a/ovn/controller/ofctrl.h b/ovn/controller/ofctrl.h
index 45081e5..f05f8e6 100644
--- a/ovn/controller/ofctrl.h
+++ b/ovn/controller/ofctrl.h
@@ -21,6 +21,7 @@ 
 
 #include "openvswitch/meta-flow.h"
 #include "ovsdb-idl.h"
+#include "hindex.h"
 
 struct controller_ctx;
 struct ovn_extend_table;
@@ -30,15 +31,24 @@  struct ofpbuf;
 struct ovsrec_bridge;
 struct shash;
 
+struct ovn_desired_flow_table {
+    /* hash map flow table using flow match conditions as hash key */
+    struct hmap match_flow_table;
+
+    /* SB uuid index for the nodes in match_flow_table */
+    struct hindex uuid_flow_table;
+};
+
 /* Interface for OVN main loop. */
 void ofctrl_init(struct ovn_extend_table *group_table,
                  struct ovn_extend_table *meter_table);
 void ofctrl_run(const struct ovsrec_bridge *br_int,
                 struct shash *pending_ct_zones);
 enum mf_field_id ofctrl_get_mf_field_id(void);
-bool ofctrl_can_put(void);
-void ofctrl_put(struct hmap *flow_table, struct shash *pending_ct_zones,
-                int64_t nb_cfg);
+void ofctrl_put(struct ovn_desired_flow_table *flow_table,
+                struct shash *pending_ct_zones,
+                int64_t nb_cfg,
+                bool flow_changed);
 void ofctrl_wait(void);
 void ofctrl_destroy(void);
 int64_t ofctrl_get_cur_cfg(void);
@@ -52,8 +62,17 @@  char *ofctrl_inject_pkt(const struct ovsrec_bridge *br_int,
                         const struct shash *port_groups);
 
 /* Flow table interfaces to the rest of ovn-controller. */
-void ofctrl_add_flow(struct hmap *desired_flows, uint8_t table_id,
+void ofctrl_add_flow(struct ovn_desired_flow_table *flow_table,
+                     uint8_t table_id,
                      uint16_t priority, uint64_t cookie,
-                     const struct match *, const struct ofpbuf *ofpacts);
+                     const struct match *, const struct ofpbuf *ofpacts,
+                     const struct uuid *lflow_uuid);
+
+void ofctrl_remove_flows(struct ovn_desired_flow_table *flow_table,
+                         const struct uuid *lflow_uuid);
+
+void ovn_desired_flow_table_init(struct ovn_desired_flow_table *flow_table);
+void ovn_desired_flow_table_clear(struct ovn_desired_flow_table *flow_table);
+void ovn_desired_flow_table_destroy(struct ovn_desired_flow_table *flow_table);
 
 #endif /* ovn/ofctrl.h */
diff --git a/ovn/controller/ovn-controller.c b/ovn/controller/ovn-controller.c
index 1f8ae05..240a2da 100644
--- a/ovn/controller/ovn-controller.c
+++ b/ovn/controller/ovn-controller.c
@@ -747,11 +747,13 @@  runtime_data_run(struct engine_node *node)
 
 struct ed_type_flow_output {
     /* desired flows */
-    struct hmap flow_table;
+    struct ovn_desired_flow_table flow_table;
     /* group ids for load balancing */
     struct ovn_extend_table group_table;
     /* meter ids for QoS */
     struct ovn_extend_table meter_table;
+    /* conjunction id offset */
+    uint32_t conj_id_ofs;
 };
 
 static void
@@ -759,9 +761,10 @@  flow_output_init(struct engine_node *node)
 {
     struct ed_type_flow_output *data =
         (struct ed_type_flow_output *)node->data;
-    hmap_init(&data->flow_table);
+    ovn_desired_flow_table_init(&data->flow_table);
     ovn_extend_table_init(&data->group_table);
     ovn_extend_table_init(&data->meter_table);
+    data->conj_id_ofs = 1;
 }
 
 static void
@@ -769,7 +772,7 @@  flow_output_cleanup(struct engine_node *node)
 {
     struct ed_type_flow_output *data =
         (struct ed_type_flow_output *)node->data;
-    hmap_destroy(&data->flow_table);
+    ovn_desired_flow_table_destroy(&data->flow_table);
     ovn_extend_table_destroy(&data->group_table);
     ovn_extend_table_destroy(&data->meter_table);
 }
@@ -803,29 +806,75 @@  flow_output_run(struct engine_node *node)
 
     struct ed_type_flow_output *fod =
         (struct ed_type_flow_output *)node->data;
-    struct hmap *flow_table = &fod->flow_table;
+    struct ovn_desired_flow_table *flow_table = &fod->flow_table;
     struct ovn_extend_table *group_table = &fod->group_table;
     struct ovn_extend_table *meter_table = &fod->meter_table;
+    uint32_t *conj_id_ofs = &fod->conj_id_ofs;
 
     static bool first_run = true;
     if (first_run) {
         first_run = false;
     } else {
-        hmap_clear(flow_table);
+        ovn_desired_flow_table_clear(&fod->flow_table);
+        ovn_extend_table_clear(&fod->group_table, false /* desired */);
+        ovn_extend_table_clear(&fod->meter_table, false /* desired */);
     }
 
-    lflow_run(ctx, chassis,
+    *conj_id_ofs = 1;
+    lflow_run(flow_table, ctx, chassis,
               chassis_index, local_datapaths, group_table,
-              meter_table, addr_sets, port_groups, flow_table,
-              active_tunnels, local_lport_ids);
+              meter_table, addr_sets, port_groups, active_tunnels,
+              local_lport_ids, conj_id_ofs);
 
     enum mf_field_id mff_ovn_geneve = ofctrl_get_mf_field_id();
 
-    physical_run(ctx, mff_ovn_geneve,
+    physical_run(flow_table, ctx, mff_ovn_geneve,
                  br_int, chassis, ct_zones,
-                 flow_table, local_datapaths, local_lports,
+                 local_datapaths, local_lports,
                  chassis_index, active_tunnels);
+
+    node->changed = true;
+}
+
+static bool
+flow_output_sb_logical_flow_handler(struct engine_node *node)
+{
+    struct controller_ctx *ctx = (struct controller_ctx *)node->context;
+    struct ed_type_runtime_data *data =
+        (struct ed_type_runtime_data *)engine_get_input(
+                "runtime_data", node)->data;
+    struct hmap *local_datapaths = &data->local_datapaths;
+    struct sset *local_lport_ids = &data->local_lport_ids;
+    struct sset *active_tunnels = &data->active_tunnels;
+    struct chassis_index *chassis_index = &data->chassis_index;
+    struct shash *addr_sets = &data->addr_sets;
+    struct shash *port_groups = &data->port_groups;
+    const struct ovsrec_bridge *br_int = get_br_int(ctx);
+
+    const char *chassis_id = get_chassis_id(ctx->ovs_idl);
+
+
+    const struct sbrec_chassis *chassis = NULL;
+    if (chassis_id) {
+        chassis = get_chassis(ctx->ovnsb_idl, chassis_id);
+    }
+
+    ovs_assert(br_int && chassis);
+
+    struct ed_type_flow_output *fod =
+        (struct ed_type_flow_output *)node->data;
+    struct ovn_desired_flow_table *flow_table = &fod->flow_table;
+    struct ovn_extend_table *group_table = &fod->group_table;
+    struct ovn_extend_table *meter_table = &fod->meter_table;
+    uint32_t *conj_id_ofs = &fod->conj_id_ofs;
+
+    bool handled = lflow_handle_changed_flows(flow_table, ctx, chassis,
+              chassis_index, local_datapaths, group_table, meter_table,
+              addr_sets, port_groups, active_tunnels, local_lport_ids,
+              conj_id_ofs);
+
     node->changed = true;
+    return handled;
 }
 
 int
@@ -910,13 +959,11 @@  main(int argc, char *argv[])
 
     engine_add_input(&en_flow_output, &en_sb_chassis, NULL);
     engine_add_input(&en_flow_output, &en_sb_encap, NULL);
-    engine_add_input(&en_flow_output, &en_sb_address_set, NULL);
-    engine_add_input(&en_flow_output, &en_sb_port_group, NULL);
     engine_add_input(&en_flow_output, &en_sb_multicast_group, NULL);
     engine_add_input(&en_flow_output, &en_sb_datapath_binding, NULL);
     engine_add_input(&en_flow_output, &en_sb_port_binding, NULL);
     engine_add_input(&en_flow_output, &en_sb_mac_binding, NULL);
-    engine_add_input(&en_flow_output, &en_sb_logical_flow, NULL);
+    engine_add_input(&en_flow_output, &en_sb_logical_flow, flow_output_sb_logical_flow_handler);
     engine_add_input(&en_flow_output, &en_sb_dhcp_options, NULL);
     engine_add_input(&en_flow_output, &en_sb_dhcpv6_options, NULL);
     engine_add_input(&en_flow_output, &en_sb_dns, NULL);
@@ -927,6 +974,7 @@  main(int argc, char *argv[])
 
     engine_add_input(&en_runtime_data, &en_sb_chassis, NULL);
     engine_add_input(&en_runtime_data, &en_sb_address_set, NULL);
+    engine_add_input(&en_runtime_data, &en_sb_port_group, NULL);
     engine_add_input(&en_runtime_data, &en_sb_datapath_binding, NULL);
     engine_add_input(&en_runtime_data, &en_sb_port_binding, NULL);
     engine_add_input(&en_runtime_data, &en_sb_gateway_chassis, NULL);
@@ -944,6 +992,7 @@  main(int argc, char *argv[])
 
     uint64_t engine_run_id = 0;
     uint64_t old_engine_run_id = 0;
+
     /* Main loop. */
     exiting = false;
     while (!exiting) {
@@ -980,33 +1029,30 @@  main(int argc, char *argv[])
             patch_run(&ctx, br_int, chassis);
             encaps_run(&ctx, br_int, chassis_id);
 
-            if (ofctrl_can_put()) {
-                stopwatch_start(CONTROLLER_LOOP_STOPWATCH_NAME,
-                                time_msec());
-                engine_run(&en_flow_output, ++engine_run_id);
-                stopwatch_stop(CONTROLLER_LOOP_STOPWATCH_NAME,
-                               time_msec());
-
-                if (ctx.ovs_idl_txn) {
-                    commit_ct_zones(br_int, &ed_runtime_data.pending_ct_zones);
-                    bfd_run(&ctx, br_int, chassis,
-                            &ed_runtime_data.local_datapaths,
-                            &ed_runtime_data.chassis_index);
-                }
-                if (en_flow_output.changed) {
-                    ofctrl_put(&ed_flow_output.flow_table,
-                               &ed_runtime_data.pending_ct_zones,
-                               get_nb_cfg(ctx.ovnsb_idl));
-                }
+            stopwatch_start(CONTROLLER_LOOP_STOPWATCH_NAME,
+                            time_msec());
+            engine_run(&en_flow_output, ++engine_run_id);
+            stopwatch_stop(CONTROLLER_LOOP_STOPWATCH_NAME,
+                           time_msec());
+            if (ctx.ovs_idl_txn) {
+                commit_ct_zones(br_int, &ed_runtime_data.pending_ct_zones);
+                bfd_run(&ctx, br_int, chassis,
+                        &ed_runtime_data.local_datapaths,
+                        &ed_runtime_data.chassis_index);
             }
-
+            ofctrl_put(&ed_flow_output.flow_table,
+                       &ed_runtime_data.pending_ct_zones,
+                       get_nb_cfg(ctx.ovnsb_idl),
+                       en_flow_output.changed);
             pinctrl_run(&ctx, br_int, chassis, &ed_runtime_data.chassis_index,
                         &ed_runtime_data.local_datapaths,
                         &ed_runtime_data.active_tunnels);
 
-            update_sb_monitors(ctx.ovnsb_idl, chassis,
-                               &ed_runtime_data.local_lports,
-                               &ed_runtime_data.local_datapaths);
+            if (en_runtime_data.changed) {
+                update_sb_monitors(ctx.ovnsb_idl, chassis,
+                                   &ed_runtime_data.local_lports,
+                                   &ed_runtime_data.local_datapaths);
+            }
 
         }
         if (old_engine_run_id == engine_run_id ||
diff --git a/ovn/controller/physical.c b/ovn/controller/physical.c
index fc418e7..513d986 100644
--- a/ovn/controller/physical.c
+++ b/ovn/controller/physical.c
@@ -44,6 +44,9 @@ 
 
 VLOG_DEFINE_THIS_MODULE(physical);
 
+/* UUID to identify OF flows not associated with ovsdb rows. */
+static struct uuid *hc_uuid = NULL;
+
 void
 physical_register_ovs_idl(struct ovsdb_idl *ovs_idl)
 {
@@ -188,9 +191,10 @@  get_zone_ids(const struct sbrec_port_binding *binding,
 }
 
 static void
-put_local_common_flows(uint32_t dp_key, uint32_t port_key,
+put_local_common_flows(struct ovn_desired_flow_table *flow_table,
+                       uint32_t dp_key, uint32_t port_key,
                        bool nested_container, const struct zone_ids *zone_ids,
-                       struct ofpbuf *ofpacts_p, struct hmap *flow_table)
+                       struct ofpbuf *ofpacts_p)
 {
     struct match match;
 
@@ -224,7 +228,7 @@  put_local_common_flows(uint32_t dp_key, uint32_t port_key,
     /* Resubmit to table 34. */
     put_resubmit(OFTABLE_CHECK_LOOPBACK, ofpacts_p);
     ofctrl_add_flow(flow_table, OFTABLE_LOCAL_OUTPUT, 100, 0,
-                    &match, ofpacts_p);
+                    &match, ofpacts_p, hc_uuid);
 
     /* Table 34, Priority 100.
      * =======================
@@ -239,7 +243,7 @@  put_local_common_flows(uint32_t dp_key, uint32_t port_key,
     match_set_reg(&match, MFF_LOG_INPORT - MFF_REG0, port_key);
     match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0, port_key);
     ofctrl_add_flow(flow_table, OFTABLE_CHECK_LOOPBACK, 100, 0,
-                    &match, ofpacts_p);
+                    &match, ofpacts_p, hc_uuid);
 
     /* Table 64, Priority 100.
      * =======================
@@ -263,7 +267,7 @@  put_local_common_flows(uint32_t dp_key, uint32_t port_key,
     put_resubmit(OFTABLE_LOG_TO_PHY, ofpacts_p);
     put_stack(MFF_IN_PORT, ofpact_put_STACK_POP(ofpacts_p));
     ofctrl_add_flow(flow_table, OFTABLE_SAVE_INPORT, 100, 0,
-                    &match, ofpacts_p);
+                    &match, ofpacts_p, hc_uuid);
 }
 
 static void
@@ -291,7 +295,8 @@  load_logical_ingress_metadata(const struct sbrec_port_binding *binding,
 }
 
 static void
-consider_port_binding(struct controller_ctx *ctx,
+consider_port_binding(struct ovn_desired_flow_table *flow_table,
+                      struct controller_ctx *ctx,
                       enum mf_field_id mff_ovn_geneve,
                       const struct simap *ct_zones,
                       const struct chassis_index *chassis_index,
@@ -299,8 +304,7 @@  consider_port_binding(struct controller_ctx *ctx,
                       struct hmap *local_datapaths,
                       const struct sbrec_port_binding *binding,
                       const struct sbrec_chassis *chassis,
-                      struct ofpbuf *ofpacts_p,
-                      struct hmap *flow_table)
+                      struct ofpbuf *ofpacts_p)
 {
     uint32_t dp_key = binding->datapath->tunnel_key;
     uint32_t port_key = binding->tunnel_key;
@@ -328,8 +332,8 @@  consider_port_binding(struct controller_ctx *ctx,
         }
 
         struct zone_ids binding_zones = get_zone_ids(binding, ct_zones);
-        put_local_common_flows(dp_key, port_key, false, &binding_zones,
-                               ofpacts_p, flow_table);
+        put_local_common_flows(flow_table, dp_key, port_key, false,
+                               &binding_zones, ofpacts_p);
 
         match_init_catchall(&match);
         ofpbuf_clear(ofpacts_p);
@@ -356,7 +360,7 @@  consider_port_binding(struct controller_ctx *ctx,
         ofpact_finish_CLONE(ofpacts_p, &clone);
 
         ofctrl_add_flow(flow_table, OFTABLE_LOG_TO_PHY, 100, 0,
-                        &match, ofpacts_p);
+                        &match, ofpacts_p, hc_uuid);
         return;
     }
 
@@ -424,7 +428,7 @@  consider_port_binding(struct controller_ctx *ctx,
         }
 
         ofctrl_add_flow(flow_table, OFTABLE_LOCAL_OUTPUT, 100, 0,
-                        &match, ofpacts_p);
+                        &match, ofpacts_p, hc_uuid);
 
         goto out;
     }
@@ -522,8 +526,8 @@  consider_port_binding(struct controller_ctx *ctx,
          */
 
         struct zone_ids zone_ids = get_zone_ids(binding, ct_zones);
-        put_local_common_flows(dp_key, port_key, nested_container, &zone_ids,
-                               ofpacts_p, flow_table);
+        put_local_common_flows(flow_table, dp_key, port_key, nested_container,
+                               &zone_ids, ofpacts_p);
 
         /* Table 0, Priority 150 and 100.
          * ==============================
@@ -567,7 +571,7 @@  consider_port_binding(struct controller_ctx *ctx,
         /* Resubmit to first logical ingress pipeline table. */
         put_resubmit(OFTABLE_LOG_INGRESS_PIPELINE, ofpacts_p);
         ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG,
-                        tag ? 150 : 100, 0, &match, ofpacts_p);
+                        tag ? 150 : 100, 0, &match, ofpacts_p, hc_uuid);
 
         if (!tag && (!strcmp(binding->type, "localnet")
                      || !strcmp(binding->type, "l2gateway"))) {
@@ -577,7 +581,7 @@  consider_port_binding(struct controller_ctx *ctx,
              * action. */
             ofpbuf_pull(ofpacts_p, ofpacts_orig_size);
             match_set_dl_tci_masked(&match, 0, htons(VLAN_CFI));
-            ofctrl_add_flow(flow_table, 0, 100, 0, &match, ofpacts_p);
+            ofctrl_add_flow(flow_table, 0, 100, 0, &match, ofpacts_p, hc_uuid);
         }
 
         /* Table 65, Priority 100.
@@ -605,7 +609,7 @@  consider_port_binding(struct controller_ctx *ctx,
             ofpact_put_STRIP_VLAN(ofpacts_p);
         }
         ofctrl_add_flow(flow_table, OFTABLE_LOG_TO_PHY, 100, 0,
-                        &match, ofpacts_p);
+                        &match, ofpacts_p, hc_uuid);
     } else if (!tun && !is_ha_remote) {
         /* Remote port connected by localnet port */
         /* Table 33, priority 100.
@@ -628,7 +632,7 @@  consider_port_binding(struct controller_ctx *ctx,
         /* Resubmit to table 33. */
         put_resubmit(OFTABLE_LOCAL_OUTPUT, ofpacts_p);
         ofctrl_add_flow(flow_table, OFTABLE_LOCAL_OUTPUT, 100, 0,
-                        &match, ofpacts_p);
+                        &match, ofpacts_p, hc_uuid);
     } else {
         /* Remote port connected by tunnel */
 
@@ -719,7 +723,7 @@  consider_port_binding(struct controller_ctx *ctx,
             ofpact_finish_BUNDLE(ofpacts_p, &bundle);
         }
         ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 100, 0,
-                        &match, ofpacts_p);
+                        &match, ofpacts_p, hc_uuid);
     }
 out:
     if (gateway_chassis) {
@@ -728,14 +732,14 @@  out:
 }
 
 static void
-consider_mc_group(enum mf_field_id mff_ovn_geneve,
+consider_mc_group(struct ovn_desired_flow_table *flow_table,
+                  enum mf_field_id mff_ovn_geneve,
                   const struct simap *ct_zones,
                   struct hmap *local_datapaths,
                   const struct sbrec_chassis *chassis,
                   const struct sbrec_multicast_group *mc,
                   struct ofpbuf *ofpacts_p,
-                  struct ofpbuf *remote_ofpacts_p,
-                  struct hmap *flow_table)
+                  struct ofpbuf *remote_ofpacts_p)
 {
     uint32_t dp_key = mc->datapath->tunnel_key;
     if (!get_local_datapath(local_datapaths, dp_key)) {
@@ -811,7 +815,7 @@  consider_mc_group(enum mf_field_id mff_ovn_geneve,
         put_load(mc->tunnel_key, MFF_LOG_OUTPORT, 0, 32, ofpacts_p);
 
         ofctrl_add_flow(flow_table, OFTABLE_LOCAL_OUTPUT, 100, 0,
-                        &match, ofpacts_p);
+                        &match, ofpacts_p, hc_uuid);
     }
 
     /* Table 32, priority 100.
@@ -849,7 +853,7 @@  consider_mc_group(enum mf_field_id mff_ovn_geneve,
                 put_resubmit(OFTABLE_LOCAL_OUTPUT, remote_ofpacts_p);
             }
             ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 100, 0,
-                            &match, remote_ofpacts_p);
+                            &match, remote_ofpacts_p, hc_uuid);
         }
     }
     sset_destroy(&remote_chassis);
@@ -867,16 +871,22 @@  update_ofports(struct simap *old, struct simap *new)
 }
 
 void
-physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
+physical_run(struct ovn_desired_flow_table *flow_table,
+             struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
              const struct ovsrec_bridge *br_int,
              const struct sbrec_chassis *chassis,
              const struct simap *ct_zones,
-             struct hmap *flow_table, struct hmap *local_datapaths,
+             struct hmap *local_datapaths,
              const struct sset *local_lports,
              struct chassis_index *chassis_index,
              struct sset *active_tunnels)
 {
 
+    if (!hc_uuid) {
+        hc_uuid = xmalloc(sizeof(struct uuid));
+        uuid_generate(hc_uuid);
+    }
+
     /* This bool tracks physical mapping changes. */
     bool physical_map_changed = false;
 
@@ -995,10 +1005,10 @@  physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
      * 64 for logical-to-physical translation. */
     const struct sbrec_port_binding *binding;
     SBREC_PORT_BINDING_FOR_EACH (binding, ctx->ovnsb_idl) {
-        consider_port_binding(ctx, mff_ovn_geneve, ct_zones,
+        consider_port_binding(flow_table, ctx, mff_ovn_geneve, ct_zones,
                               chassis_index, active_tunnels,
                               local_datapaths, binding, chassis,
-                              &ofpacts, flow_table);
+                              &ofpacts);
     }
 
     /* Handle output to multicast groups, in tables 32 and 33. */
@@ -1019,10 +1029,10 @@  physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
         ofpbuf_clear(&ofpacts);
         put_resubmit(OFTABLE_LOCAL_OUTPUT, &ofpacts);
         ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 150, 0, &match,
-                        &ofpacts);
+                        &ofpacts, hc_uuid);
 
-        consider_mc_group(mff_ovn_geneve, ct_zones, local_datapaths, chassis,
-                          mc, &ofpacts, &remote_ofpacts, flow_table);
+        consider_mc_group(flow_table, mff_ovn_geneve, ct_zones, local_datapaths, chassis,
+                          mc, &ofpacts, &remote_ofpacts);
     }
 
     ofpbuf_uninit(&remote_ofpacts);
@@ -1063,7 +1073,7 @@  physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
         put_resubmit(OFTABLE_LOCAL_OUTPUT, &ofpacts);
 
         ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG, 100, 0, &match,
-                        &ofpacts);
+                        &ofpacts, hc_uuid);
     }
 
     /* Add flows for VXLAN encapsulations.  Due to the limited amount of
@@ -1096,7 +1106,7 @@  physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
             put_resubmit(OFTABLE_LOG_INGRESS_PIPELINE, &ofpacts);
 
             ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG, 100, 0, &match,
-                            &ofpacts);
+                            &ofpacts, hc_uuid);
         }
     }
 
@@ -1117,7 +1127,7 @@  physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
     ofpbuf_clear(&ofpacts);
     put_resubmit(OFTABLE_LOCAL_OUTPUT, &ofpacts);
     ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 150, 0,
-                    &match, &ofpacts);
+                    &match, &ofpacts, hc_uuid);
 
     /* Table 32, priority 150.
      * =======================
@@ -1140,7 +1150,7 @@  physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
             match_set_reg(&match, MFF_LOG_INPORT - MFF_REG0, pb->tunnel_key);
             match_set_metadata(&match, htonll(pb->datapath->tunnel_key));
             ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 150, 0,
-                            &match, &ofpacts);
+                            &match, &ofpacts, hc_uuid);
         }
     }
 
@@ -1152,7 +1162,7 @@  physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
     match_init_catchall(&match);
     ofpbuf_clear(&ofpacts);
     put_resubmit(OFTABLE_LOCAL_OUTPUT, &ofpacts);
-    ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 0, 0, &match, &ofpacts);
+    ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 0, 0, &match, &ofpacts, hc_uuid);
 
     /* Table 34, Priority 0.
      * =======================
@@ -1167,7 +1177,7 @@  physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
     }
     put_resubmit(OFTABLE_LOG_EGRESS_PIPELINE, &ofpacts);
     ofctrl_add_flow(flow_table, OFTABLE_CHECK_LOOPBACK, 0, 0, &match,
-                    &ofpacts);
+                    &ofpacts, hc_uuid);
 
     /* Table 64, Priority 0.
      * =======================
@@ -1177,7 +1187,7 @@  physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
     match_init_catchall(&match);
     ofpbuf_clear(&ofpacts);
     put_resubmit(OFTABLE_LOG_TO_PHY, &ofpacts);
-    ofctrl_add_flow(flow_table, OFTABLE_SAVE_INPORT, 0, 0, &match, &ofpacts);
+    ofctrl_add_flow(flow_table, OFTABLE_SAVE_INPORT, 0, 0, &match, &ofpacts, hc_uuid);
 
     ofpbuf_uninit(&ofpacts);
 
diff --git a/ovn/controller/physical.h b/ovn/controller/physical.h
index f8dbb49..ac2da2a 100644
--- a/ovn/controller/physical.h
+++ b/ovn/controller/physical.h
@@ -43,11 +43,12 @@  struct chassis_index;
 #define OVN_GENEVE_LEN 4
 
 void physical_register_ovs_idl(struct ovsdb_idl *);
-void physical_run(struct controller_ctx *, enum mf_field_id mff_ovn_geneve,
+void physical_run(struct ovn_desired_flow_table *flow_table,
+                  struct controller_ctx *, enum mf_field_id mff_ovn_geneve,
                   const struct ovsrec_bridge *br_int,
                   const struct sbrec_chassis *chassis,
                   const struct simap *ct_zones,
-                  struct hmap *flow_table, struct hmap *local_datapaths,
+                  struct hmap *local_datapaths,
                   const struct sset *local_lports,
                   struct chassis_index *chassis_index,
                   struct sset *active_tunnels);
diff --git a/ovn/lib/actions.c b/ovn/lib/actions.c
index a694581..655085d 100644
--- a/ovn/lib/actions.c
+++ b/ovn/lib/actions.c
@@ -1049,7 +1049,8 @@  encode_CT_LB(const struct ovnact_ct_lb *cl,
                       recirc_table, zone_reg);
     }
 
-    table_id = ovn_extend_table_assign_id(ep->group_table, &ds);
+    table_id = ovn_extend_table_assign_id(ep->group_table, &ds,
+                                          ep->lflow_uuid);
     ds_destroy(&ds);
     if (table_id == EXT_TABLE_ID_INVALID) {
         return;
@@ -2208,7 +2209,8 @@  encode_SET_METER(const struct ovnact_set_meter *cl,
                       cl->rate);
     }
 
-    table_id = ovn_extend_table_assign_id(ep->meter_table, &ds);
+    table_id = ovn_extend_table_assign_id(ep->meter_table, &ds,
+                                          ep->lflow_uuid);
     if (table_id == EXT_TABLE_ID_INVALID) {
         ds_destroy(&ds);
         return;
diff --git a/ovn/lib/extend-table.c b/ovn/lib/extend-table.c
index e18713b..e4dd52a 100644
--- a/ovn/lib/extend-table.c
+++ b/ovn/lib/extend-table.c
@@ -17,6 +17,7 @@ 
 #include <config.h>
 #include "bitmap.h"
 #include "hash.h"
+#include "lib/uuid.h"
 #include "openvswitch/vlog.h"
 #include "ovn/lib/extend-table.h"
 
@@ -89,9 +90,10 @@  ovn_extend_table_clear(struct ovn_extend_table *table, bool existing)
     }
 }
 
+/* Remove an entry from existing table */
 void
-ovn_extend_table_remove(struct ovn_extend_table *table,
-                        struct ovn_extend_table_info *existing)
+ovn_extend_table_remove_existing(struct ovn_extend_table *table,
+                                 struct ovn_extend_table_info *existing)
 {
     /* Remove 'existing' from 'groups->existing' */
     hmap_remove(&table->existing, &existing->hmap_node);
@@ -102,21 +104,50 @@  ovn_extend_table_remove(struct ovn_extend_table *table,
     free(existing);
 }
 
+/* Remove entries in desired table that are created by the lflow_uuid */
 void
-ovn_extend_table_move(struct ovn_extend_table *table)
+ovn_extend_table_remove_desired(struct ovn_extend_table *table,
+                                const struct uuid *lflow_uuid)
+{
+    struct ovn_extend_table_info *e, *next_e;
+    HMAP_FOR_EACH_SAFE (e, next_e, hmap_node, &table->desired) {
+        if (uuid_equals(&e->lflow_uuid, lflow_uuid)) {
+            hmap_remove(&table->desired, &e->hmap_node);
+            ds_destroy(&e->info);
+            if (e->new_table_id) {
+                bitmap_set0(table->table_ids, e->table_id);
+            }
+            free(e);
+        }
+    }
+
+}
+
+static struct ovn_extend_table_info*
+ovn_extend_info_clone(struct ovn_extend_table_info *source)
+{
+    struct ovn_extend_table_info *clone = xmalloc(sizeof *clone);
+    ds_clone(&clone->info, &source->info);
+    clone->table_id = source->table_id;
+    clone->new_table_id = source->new_table_id;
+    clone->hmap_node.hash = source->hmap_node.hash;
+    clone->lflow_uuid = source->lflow_uuid;
+    return clone;
+}
+
+void
+ovn_extend_table_sync(struct ovn_extend_table *table)
 {
     struct ovn_extend_table_info *desired, *next;
 
-    /* Move the contents of desired to existing. */
+    /* Copy the contents of desired to existing. */
     HMAP_FOR_EACH_SAFE (desired, next, hmap_node, &table->desired) {
-        hmap_remove(&table->desired, &desired->hmap_node);
-
         if (!ovn_extend_table_lookup(&table->existing, desired)) {
-            hmap_insert(&table->existing, &desired->hmap_node,
-                        desired->hmap_node.hash);
-        } else {
-           ds_destroy(&desired->info);
-           free(desired);
+            desired->new_table_id = false;
+            struct ovn_extend_table_info *clone =
+                ovn_extend_info_clone(desired);
+            hmap_insert(&table->existing, &clone->hmap_node,
+                        clone->hmap_node.hash);
         }
     }
 }
@@ -124,7 +155,8 @@  ovn_extend_table_move(struct ovn_extend_table *table)
 /* Assign a new table ID for the table information from the bitmap.
  * If it already exists, return the old ID. */
 uint32_t
-ovn_extend_table_assign_id(struct ovn_extend_table *table, struct ds *ds)
+ovn_extend_table_assign_id(struct ovn_extend_table *table, struct ds *ds,
+                           struct uuid lflow_uuid)
 {
     uint32_t table_id = 0, hash;
     struct ovn_extend_table_info *table_info;
@@ -133,7 +165,8 @@  ovn_extend_table_assign_id(struct ovn_extend_table *table, struct ds *ds)
 
     /* Check whether we have non installed but allocated group_id. */
     HMAP_FOR_EACH_WITH_HASH (table_info, hmap_node, hash, &table->desired) {
-        if (!strcmp(ds_cstr(&table_info->info), ds_cstr(ds))) {
+        if (!strcmp(ds_cstr(&table_info->info), ds_cstr(ds)) &&
+            table_info->new_table_id) {
             return table_info->table_id;
         }
     }
@@ -165,6 +198,7 @@  ovn_extend_table_assign_id(struct ovn_extend_table *table, struct ds *ds)
     table_info->table_id = table_id;
     table_info->hmap_node.hash = hash;
     table_info->new_table_id = new_table_id;
+    table_info->lflow_uuid = lflow_uuid;
 
     hmap_insert(&table->desired,
                 &table_info->hmap_node, table_info->hmap_node.hash);
diff --git a/ovn/lib/extend-table.h b/ovn/lib/extend-table.h
index d9ae549..f0b8fd9 100644
--- a/ovn/lib/extend-table.h
+++ b/ovn/lib/extend-table.h
@@ -23,6 +23,7 @@ 
 #include "openvswitch/dynamic-string.h"
 #include "openvswitch/hmap.h"
 #include "openvswitch/list.h"
+#include "openvswitch/uuid.h"
 
 /* Used to manage expansion tables associated with Flow table,
  * such as the Group Table or Meter Table. */
@@ -37,6 +38,7 @@  struct ovn_extend_table {
 struct ovn_extend_table_info {
     struct hmap_node hmap_node;
     struct ds info;     /* Details string for the table entity. */
+    struct uuid lflow_uuid;
     uint32_t table_id;
     bool new_table_id;  /* 'True' if 'table_id' was reserved from
                          * ovn_extend_table's 'table_ids' bitmap. */
@@ -51,14 +53,18 @@  struct ovn_extend_table_info *ovn_extend_table_lookup(
 
 void ovn_extend_table_clear(struct ovn_extend_table *, bool);
 
-void ovn_extend_table_remove(struct ovn_extend_table *,
-                             struct ovn_extend_table_info *);
+void ovn_extend_table_remove_existing(struct ovn_extend_table *,
+                                      struct ovn_extend_table_info *);
 
-/* Move the contents of desired to existing. */
-void ovn_extend_table_move(struct ovn_extend_table *);
+void ovn_extend_table_remove_desired(struct ovn_extend_table *table,
+                                     const struct uuid *lflow_uuid);
+
+/* Copy the contents of desired to existing. */
+void ovn_extend_table_sync(struct ovn_extend_table *);
 
 uint32_t ovn_extend_table_assign_id(struct ovn_extend_table *,
-                                    struct ds *);
+                                    struct ds *,
+                                    struct uuid lflow_uuid);
 
 /* Iterates 'DESIRED' through all of the 'ovn_extend_table_info's in
  * 'TABLE'->desired that are not in 'TABLE'->existing.  (The loop body