ovn: Change type of 'chassis' column in 'Binding' table.
[cascardo/ovs.git] / ovn / northd / ovn-northd.c
index ef17ceb..39df3b5 100644 (file)
 #include "hmap.h"
 #include "json.h"
 #include "ovn/lib/lex.h"
-#include "ovn/ovn-nb-idl.h"
-#include "ovn/ovn-sb-idl.h"
+#include "ovn/lib/ovn-nb-idl.h"
+#include "ovn/lib/ovn-sb-idl.h"
 #include "poll-loop.h"
 #include "stream.h"
 #include "stream-ssl.h"
+#include "unixctl.h"
 #include "util.h"
 #include "uuid.h"
 #include "openvswitch/vlog.h"
 
 VLOG_DEFINE_THIS_MODULE(ovn_northd);
 
+static unixctl_cb_func ovn_northd_exit;
+
 struct northd_context {
     struct ovsdb_idl *ovnnb_idl;
     struct ovsdb_idl *ovnsb_idl;
@@ -202,66 +205,10 @@ pipeline_add(struct pipeline_ctx *ctx,
     sbrec_pipeline_set_actions(pipeline, actions);
 }
 
-/* A single port security constraint.  This is a parsed version of a single
- * member of the port_security column in the OVN_NB Logical_Port table.
- *
- * Each token has type LEX_T_END if that field is missing, otherwise
- * LEX_T_INTEGER or LEX_T_MASKED_INTEGER. */
-struct ps_constraint {
-    struct lex_token eth;
-    struct lex_token ip4;
-    struct lex_token ip6;
-};
-
-/* Parses a member of the port_security column 'ps' into 'c'.  Returns true if
- * successful, false on syntax error. */
-static bool
-parse_port_security(const char *ps, struct ps_constraint *c)
-{
-    c->eth.type = LEX_T_END;
-    c->ip4.type = LEX_T_END;
-    c->ip6.type = LEX_T_END;
-
-    struct lexer lexer;
-    lexer_init(&lexer, ps);
-    do {
-        if (lexer.token.type == LEX_T_INTEGER ||
-            lexer.token.type == LEX_T_MASKED_INTEGER) {
-            struct lex_token *t;
-
-            t = (lexer.token.format == LEX_F_IPV4 ? &c->ip4
-                 : lexer.token.format == LEX_F_IPV6 ? &c->ip6
-                 : lexer.token.format == LEX_F_ETHERNET ? &c->eth
-                 : NULL);
-            if (t) {
-                if (t->type == LEX_T_END) {
-                    *t = lexer.token;
-                } else {
-                    VLOG_INFO("%s: port_security has duplicate %s address",
-                              ps, lex_format_to_string(lexer.token.format));
-                }
-                lexer_get(&lexer);
-                lexer_match(&lexer, LEX_T_COMMA);
-                continue;
-            }
-        }
-
-        VLOG_INFO("%s: syntax error in port_security", ps);
-        lexer_destroy(&lexer);
-        return false;
-    } while (lexer.token.type != LEX_T_END);
-    lexer_destroy(&lexer);
-
-    return true;
-}
-
 /* Appends port security constraints on L2 address field 'eth_addr_field'
  * (e.g. "eth.src" or "eth.dst") to 'match'.  'port_security', with
  * 'n_port_security' elements, is the collection of port_security constraints
- * from an OVN_NB Logical_Port row.
- *
- * (This is naive; it's not yet possible to express complete L2 and L3 port
- * security constraints as a single Boolean expression.) */
+ * from an OVN_NB Logical_Port row. */
 static void
 build_port_security(const char *eth_addr_field,
                     char **port_security, size_t n_port_security,
@@ -272,14 +219,15 @@ build_port_security(const char *eth_addr_field,
 
     size_t n = 0;
     for (size_t i = 0; i < n_port_security; i++) {
-        struct ps_constraint c;
-        if (parse_port_security(port_security[i], &c)
-            && c.eth.type != LEX_T_END) {
-            lex_token_format(&c.eth, match);
+        uint8_t ea[ETH_ADDR_LEN];
+
+        if (eth_addr_from_string(port_security[i], ea)) {
+            ds_put_format(match, ETH_ADDR_FMT, ETH_ADDR_ARGS(ea));
             ds_put_char(match, ' ');
             n++;
         }
     }
+    ds_chomp(match, ' ');
     ds_put_cstr(match, "}");
 
     if (!n) {
@@ -314,16 +262,16 @@ build_pipeline(struct northd_context *ctx)
     const struct nbrec_logical_switch *lswitch;
     NBREC_LOGICAL_SWITCH_FOR_EACH (lswitch, ctx->ovnnb_idl) {
         /* Logical VLANs not supported. */
-        pipeline_add(&pc, lswitch, 0, 100, "vlan.present", "drop");
+        pipeline_add(&pc, lswitch, 0, 100, "vlan.present", "drop;");
 
         /* Broadcast/multicast source address is invalid. */
-        pipeline_add(&pc, lswitch, 0, 100, "eth.src[40]", "drop");
+        pipeline_add(&pc, lswitch, 0, 100, "eth.src[40]", "drop;");
 
-        /* Port security flows have priority 50 (see below) and will resubmit
-         * if packet source is acceptable. */
+        /* Port security flows have priority 50 (see below) and will continue
+         * to the next table if packet source is acceptable. */
 
         /* Otherwise drop the packet. */
-        pipeline_add(&pc, lswitch, 0, 0, "1", "drop");
+        pipeline_add(&pc, lswitch, 0, 0, "1", "drop;");
     }
 
     /* Table 0: Ingress port security. */
@@ -335,7 +283,7 @@ build_pipeline(struct northd_context *ctx)
         build_port_security("eth.src",
                             lport->port_security, lport->n_port_security,
                             &match);
-        pipeline_add(&pc, lport->lswitch, 0, 50, ds_cstr(&match), "resubmit");
+        pipeline_add(&pc, lport->lswitch, 0, 50, ds_cstr(&match), "next;");
         ds_destroy(&match);
     }
 
@@ -349,7 +297,7 @@ build_pipeline(struct northd_context *ctx)
             if (lport->lswitch == lswitch) {
                 ds_put_cstr(&actions, "outport = ");
                 json_string_escape(lport->name, &actions);
-                ds_put_cstr(&actions, "; resubmit; ");
+                ds_put_cstr(&actions, "; next; ");
             }
         }
         ds_chomp(&actions, ' ');
@@ -359,8 +307,14 @@ build_pipeline(struct northd_context *ctx)
     }
 
     /* Table 1: Destination lookup, unicast handling (priority 50),  */
-    struct ds unknown_actions = DS_EMPTY_INITIALIZER;
+    struct unknown_actions {
+        struct hmap_node hmap_node;
+        const struct nbrec_logical_switch *ls;
+        struct ds actions;
+    };
+    struct hmap unknown_actions = HMAP_INITIALIZER(&unknown_actions);
     NBREC_LOGICAL_PORT_FOR_EACH (lport, ctx->ovnnb_idl) {
+        lswitch = lport->lswitch;
         for (size_t i = 0; i < lport->n_macs; i++) {
             uint8_t mac[ETH_ADDR_LEN];
 
@@ -373,15 +327,35 @@ build_pipeline(struct northd_context *ctx)
                 ds_init(&actions);
                 ds_put_cstr(&actions, "outport = ");
                 json_string_escape(lport->name, &actions);
-                ds_put_cstr(&actions, "; resubmit;");
-                pipeline_add(&pc, lport->lswitch, 1, 50,
+                ds_put_cstr(&actions, "; next;");
+                pipeline_add(&pc, lswitch, 1, 50,
                              ds_cstr(&match), ds_cstr(&actions));
                 ds_destroy(&actions);
                 ds_destroy(&match);
             } else if (!strcmp(lport->macs[i], "unknown")) {
-                ds_put_cstr(&unknown_actions, "outport = ");
-                json_string_escape(lport->name, &unknown_actions);
-                ds_put_cstr(&unknown_actions, "; resubmit; ");
+                const struct uuid *uuid = &lswitch->header_.uuid;
+                struct unknown_actions *ua = NULL;
+                struct unknown_actions *iter;
+                HMAP_FOR_EACH_WITH_HASH (iter, hmap_node, uuid_hash(uuid),
+                                         &unknown_actions) {
+                    if (uuid_equals(&iter->ls->header_.uuid, uuid)) {
+                        ua = iter;
+                        break;
+                    }
+                }
+                if (!ua) {
+                    ua = xmalloc(sizeof *ua);
+                    hmap_insert(&unknown_actions, &ua->hmap_node,
+                                uuid_hash(uuid));
+                    ua->ls = lswitch;
+                    ds_init(&ua->actions);
+                } else {
+                    ds_put_char(&ua->actions, ' ');
+                }
+
+                ds_put_cstr(&ua->actions, "outport = ");
+                json_string_escape(lport->name, &ua->actions);
+                ds_put_cstr(&ua->actions, "; next;");
             } else {
                 static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
 
@@ -392,12 +366,14 @@ build_pipeline(struct northd_context *ctx)
     }
 
     /* Table 1: Destination lookup for unknown MACs (priority 0). */
-    if (unknown_actions.length) {
-        ds_chomp(&unknown_actions, ' ');
-        pipeline_add(&pc, lport->lswitch, 1, 0, "1",
-                     ds_cstr(&unknown_actions));
+    struct unknown_actions *ua, *next_ua;
+    HMAP_FOR_EACH_SAFE (ua, next_ua, hmap_node, &unknown_actions) {
+        pipeline_add(&pc, ua->ls, 1, 0, "1", ds_cstr(&ua->actions));
+        hmap_remove(&unknown_actions, &ua->hmap_node);
+        ds_destroy(&ua->actions);
+        free(ua);
     }
-    ds_destroy(&unknown_actions);
+    hmap_destroy(&unknown_actions);
 
     /* Table 2: ACLs. */
     const struct nbrec_acl *acl;
@@ -405,16 +381,20 @@ build_pipeline(struct northd_context *ctx)
         const char *action;
 
         action = (!strcmp(acl->action, "allow") ||
-                  !strcmp(acl->action, "allow-related")) ? "resubmit" : "drop";
+                  !strcmp(acl->action, "allow-related"))
+                      ? "next;" : "drop;";
         pipeline_add(&pc, acl->lswitch, 2, acl->priority, acl->match, action);
     }
     NBREC_LOGICAL_SWITCH_FOR_EACH (lswitch, ctx->ovnnb_idl) {
-        pipeline_add(&pc, lswitch, 2, 0, "1", "resubmit");
+        pipeline_add(&pc, lswitch, 2, 0, "1", "next;");
     }
 
     /* Table 3: Egress port security. */
+    NBREC_LOGICAL_SWITCH_FOR_EACH (lswitch, ctx->ovnnb_idl) {
+        pipeline_add(&pc, lswitch, 3, 100, "eth.dst[40]", "output;");
+    }
     NBREC_LOGICAL_PORT_FOR_EACH (lport, ctx->ovnnb_idl) {
-        struct ds match, actions;
+        struct ds match;
 
         ds_init(&match);
         ds_put_cstr(&match, "outport == ");
@@ -423,15 +403,8 @@ build_pipeline(struct northd_context *ctx)
                             lport->port_security, lport->n_port_security,
                             &match);
 
-        ds_init(&actions);
-        ds_put_cstr(&actions, "output(");
-        json_string_escape(lport->name, &actions);
-        ds_put_char(&actions, ')');
-
-        pipeline_add(&pc, lport->lswitch, 3, 50,
-                     ds_cstr(&match), ds_cstr(&actions));
+        pipeline_add(&pc, lport->lswitch, 3, 50, ds_cstr(&match), "output;");
 
-        ds_destroy(&actions);
         ds_destroy(&match);
     }
 
@@ -446,7 +419,7 @@ build_pipeline(struct northd_context *ctx)
 }
 \f
 static bool
-parents_equal(const struct sbrec_bindings *binding,
+parents_equal(const struct sbrec_binding *binding,
               const struct nbrec_logical_port *lport)
 {
     if (!!binding->parent_port != !!lport->parent_name) {
@@ -464,7 +437,7 @@ parents_equal(const struct sbrec_bindings *binding,
 }
 
 static bool
-tags_equal(const struct sbrec_bindings *binding,
+tags_equal(const struct sbrec_binding *binding,
            const struct nbrec_logical_port *lport)
 {
     if (binding->n_tag != lport->n_tag) {
@@ -474,92 +447,166 @@ tags_equal(const struct sbrec_bindings *binding,
     return binding->n_tag ? (binding->tag[0] == lport->tag[0]) : true;
 }
 
+struct binding_hash_node {
+    struct hmap_node lp_node; /* In 'lp_map', by binding->logical_port. */
+    struct hmap_node tk_node; /* In 'tk_map', by binding->tunnel_key. */
+    const struct sbrec_binding *binding;
+};
+
+static bool
+tunnel_key_in_use(const struct hmap *tk_hmap, uint16_t tunnel_key)
+{
+    const struct binding_hash_node *hash_node;
+
+    HMAP_FOR_EACH_IN_BUCKET (hash_node, tk_node, hash_int(tunnel_key, 0),
+                             tk_hmap) {
+        if (hash_node->binding->tunnel_key == tunnel_key) {
+            return true;
+        }
+    }
+    return false;
+}
+
+/* Chooses and returns a positive tunnel key that is not already in use in
+ * 'tk_hmap'.  Returns 0 if all tunnel keys are in use. */
+static uint16_t
+choose_tunnel_key(const struct hmap *tk_hmap)
+{
+    static uint16_t prev;
+
+    for (uint16_t key = prev + 1; key != prev; key++) {
+        if (!tunnel_key_in_use(tk_hmap, key)) {
+            prev = key;
+            return key;
+        }
+    }
+
+    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+    VLOG_WARN_RL(&rl, "all tunnel keys exhausted");
+    return 0;
+}
+
 /*
  * When a change has occurred in the OVN_Northbound database, we go through and
- * make sure that the contents of the Bindings table in the OVN_Southbound
+ * make sure that the contents of the Binding table in the OVN_Southbound
  * database are up to date with the logical ports defined in the
  * OVN_Northbound database.
  */
 static void
 set_bindings(struct northd_context *ctx)
 {
-    struct hmap bindings_hmap;
-    const struct sbrec_bindings *binding;
+    const struct sbrec_binding *binding;
     const struct nbrec_logical_port *lport;
 
-    struct binding_hash_node {
-        struct hmap_node node;
-        const struct sbrec_bindings *binding;
-    } *hash_node, *hash_node_next;
-
     /*
      * We will need to look up a binding for every logical port.  We don't want
      * to have to do an O(n) search for every binding, so start out by hashing
      * them on the logical port.
      *
      * As we go through every logical port, we will update the binding if it
-     * exists or create one otherwise.  When the update is done, we'll remove it
-     * from the hashmap.  At the end, any bindings left in the hashmap are for
-     * logical ports that have been deleted.
+     * exists or create one otherwise.  When the update is done, we'll remove
+     * it from the hashmap.  At the end, any bindings left in the hashmap are
+     * for logical ports that have been deleted.
+     *
+     * We index the logical_port column because that's the shared key between
+     * the OVN_NB and OVN_SB databases.  We index the tunnel_key column to
+     * allow us to choose a unique tunnel key for any Binding rows we have to
+     * add.
      */
-    hmap_init(&bindings_hmap);
+    struct hmap lp_hmap = HMAP_INITIALIZER(&lp_hmap);
+    struct hmap tk_hmap = HMAP_INITIALIZER(&tk_hmap);
 
-    SBREC_BINDINGS_FOR_EACH(binding, ctx->ovnsb_idl) {
-        hash_node = xzalloc(sizeof *hash_node);
+    SBREC_BINDING_FOR_EACH(binding, ctx->ovnsb_idl) {
+        struct binding_hash_node *hash_node = xzalloc(sizeof *hash_node);
         hash_node->binding = binding;
-        hmap_insert(&bindings_hmap, &hash_node->node,
-                hash_string(binding->logical_port, 0));
+        hmap_insert(&lp_hmap, &hash_node->lp_node,
+                    hash_string(binding->logical_port, 0));
+        hmap_insert(&tk_hmap, &hash_node->tk_node,
+                    hash_int(binding->tunnel_key, 0));
     }
 
     NBREC_LOGICAL_PORT_FOR_EACH(lport, ctx->ovnnb_idl) {
+        struct binding_hash_node *hash_node;
         binding = NULL;
-        HMAP_FOR_EACH_WITH_HASH(hash_node, node,
-                hash_string(lport->name, 0), &bindings_hmap) {
+        HMAP_FOR_EACH_WITH_HASH(hash_node, lp_node,
+                                hash_string(lport->name, 0), &lp_hmap) {
             if (!strcmp(lport->name, hash_node->binding->logical_port)) {
                 binding = hash_node->binding;
                 break;
             }
         }
 
+        struct uuid logical_datapath;
+        if (lport->lswitch) {
+            logical_datapath = lport->lswitch->header_.uuid;
+        } else {
+            uuid_zero(&logical_datapath);
+        }
+
         if (binding) {
             /* We found an existing binding for this logical port.  Update its
              * contents. */
 
-            hmap_remove(&bindings_hmap, &hash_node->node);
-            free(hash_node);
-            hash_node = NULL;
+            hmap_remove(&lp_hmap, &hash_node->lp_node);
 
             if (!macs_equal(binding->mac, binding->n_mac,
                         lport->macs, lport->n_macs)) {
-                sbrec_bindings_set_mac(binding,
+                sbrec_binding_set_mac(binding,
                         (const char **) lport->macs, lport->n_macs);
             }
             if (!parents_equal(binding, lport)) {
-                sbrec_bindings_set_parent_port(binding, lport->parent_name);
+                sbrec_binding_set_parent_port(binding, lport->parent_name);
             }
             if (!tags_equal(binding, lport)) {
-                sbrec_bindings_set_tag(binding, lport->tag, lport->n_tag);
+                sbrec_binding_set_tag(binding, lport->tag, lport->n_tag);
+            }
+            if (!uuid_equals(&binding->logical_datapath, &logical_datapath)) {
+                sbrec_binding_set_logical_datapath(binding,
+                                                    logical_datapath);
             }
         } else {
             /* There is no binding for this logical port, so create one. */
 
-            binding = sbrec_bindings_insert(ctx->ovnsb_txn);
-            sbrec_bindings_set_logical_port(binding, lport->name);
-            sbrec_bindings_set_mac(binding,
+            uint16_t tunnel_key = choose_tunnel_key(&tk_hmap);
+            if (!tunnel_key) {
+                continue;
+            }
+
+            binding = sbrec_binding_insert(ctx->ovnsb_txn);
+            sbrec_binding_set_logical_port(binding, lport->name);
+            sbrec_binding_set_mac(binding,
                     (const char **) lport->macs, lport->n_macs);
             if (lport->parent_name && lport->n_tag > 0) {
-                sbrec_bindings_set_parent_port(binding, lport->parent_name);
-                sbrec_bindings_set_tag(binding, lport->tag, lport->n_tag);
+                sbrec_binding_set_parent_port(binding, lport->parent_name);
+                sbrec_binding_set_tag(binding, lport->tag, lport->n_tag);
             }
+
+            sbrec_binding_set_tunnel_key(binding, tunnel_key);
+            sbrec_binding_set_logical_datapath(binding, logical_datapath);
+
+            /* Add the tunnel key to the tk_hmap so that we don't try to use it
+             * for another port.  (We don't want it in the lp_hmap because that
+             * would just get the Binding record deleted later.) */
+            struct binding_hash_node *hash_node = xzalloc(sizeof *hash_node);
+            hash_node->binding = binding;
+            hmap_insert(&tk_hmap, &hash_node->tk_node,
+                        hash_int(binding->tunnel_key, 0));
         }
     }
 
-    HMAP_FOR_EACH_SAFE(hash_node, hash_node_next, node, &bindings_hmap) {
-        hmap_remove(&bindings_hmap, &hash_node->node);
-        sbrec_bindings_delete(hash_node->binding);
+    struct binding_hash_node *hash_node;
+    HMAP_FOR_EACH (hash_node, lp_node, &lp_hmap) {
+        hmap_remove(&lp_hmap, &hash_node->lp_node);
+        sbrec_binding_delete(hash_node->binding);
+    }
+    hmap_destroy(&lp_hmap);
+
+    struct binding_hash_node *hash_node_next;
+    HMAP_FOR_EACH_SAFE (hash_node, hash_node_next, tk_node, &tk_hmap) {
+        hmap_remove(&tk_hmap, &hash_node->tk_node);
         free(hash_node);
     }
-    hmap_destroy(&bindings_hmap);
+    hmap_destroy(&tk_hmap);
 }
 
 static void
@@ -573,14 +620,14 @@ ovnnb_db_changed(struct northd_context *ctx)
 
 /*
  * The only change we get notified about is if the 'chassis' column of the
- * 'Bindings' table changes.  When this column is not empty, it means we need to
+ * 'Binding' table changes.  When this column is not empty, it means we need to
  * set the corresponding logical port as 'up' in the northbound DB.
  */
 static void
 ovnsb_db_changed(struct northd_context *ctx)
 {
     struct hmap lports_hmap;
-    const struct sbrec_bindings *binding;
+    const struct sbrec_binding *binding;
     const struct nbrec_logical_port *lport;
 
     struct lport_hash_node {
@@ -599,7 +646,7 @@ ovnsb_db_changed(struct northd_context *ctx)
                 hash_string(lport->name, 0));
     }
 
-    SBREC_BINDINGS_FOR_EACH(binding, ctx->ovnsb_idl) {
+    SBREC_BINDING_FOR_EACH(binding, ctx->ovnsb_idl) {
         lport = NULL;
         HMAP_FOR_EACH_WITH_HASH(hash_node, node,
                 hash_string(binding->logical_port, 0), &lports_hmap) {
@@ -616,10 +663,10 @@ ovnsb_db_changed(struct northd_context *ctx)
             continue;
         }
 
-        if (*binding->chassis && (!lport->up || !*lport->up)) {
+        if (binding->chassis && (!lport->up || !*lport->up)) {
             bool up = true;
             nbrec_logical_port_set_up(lport, &up, 1);
-        } else if (!*binding->chassis && (!lport->up || *lport->up)) {
+        } else if (!binding->chassis && (!lport->up || *lport->up)) {
             bool up = false;
             nbrec_logical_port_set_up(lport, &up, 1);
         }
@@ -723,6 +770,9 @@ main(int argc, char *argv[])
     };
     bool ovnnb_changes_pending = false;
     bool ovn_changes_pending = false;
+    struct unixctl_server *unixctl;
+    int retval;
+    bool exiting;
 
     fatal_ignore_sigpipe();
     set_program_name(argv[0]);
@@ -730,7 +780,15 @@ main(int argc, char *argv[])
     vlog_set_levels(&VLM_reconnect, VLF_ANY_DESTINATION, VLL_WARN);
     parse_options(argc, argv);
 
-    daemonize();
+    daemonize_start();
+
+    retval = unixctl_server_create(NULL, &unixctl);
+    if (retval) {
+        exit(EXIT_FAILURE);
+    }
+    unixctl_command_register("exit", "", 0, 0, ovn_northd_exit, &exiting);
+
+    daemonize_complete();
 
     nbrec_init();
     sbrec_init();
@@ -743,12 +801,14 @@ main(int argc, char *argv[])
      * has to care about, so we'll enable monitoring those directly. */
     ctx.ovnsb_idl = ovnsb_idl = ovsdb_idl_create(ovnsb_db,
             &sbrec_idl_class, false, true);
-    ovsdb_idl_add_table(ovnsb_idl, &sbrec_table_bindings);
-    ovsdb_idl_add_column(ovnsb_idl, &sbrec_bindings_col_logical_port);
-    ovsdb_idl_add_column(ovnsb_idl, &sbrec_bindings_col_chassis);
-    ovsdb_idl_add_column(ovnsb_idl, &sbrec_bindings_col_mac);
-    ovsdb_idl_add_column(ovnsb_idl, &sbrec_bindings_col_tag);
-    ovsdb_idl_add_column(ovnsb_idl, &sbrec_bindings_col_parent_port);
+    ovsdb_idl_add_table(ovnsb_idl, &sbrec_table_binding);
+    ovsdb_idl_add_column(ovnsb_idl, &sbrec_binding_col_logical_port);
+    ovsdb_idl_add_column(ovnsb_idl, &sbrec_binding_col_chassis);
+    ovsdb_idl_add_column(ovnsb_idl, &sbrec_binding_col_mac);
+    ovsdb_idl_add_column(ovnsb_idl, &sbrec_binding_col_tag);
+    ovsdb_idl_add_column(ovnsb_idl, &sbrec_binding_col_parent_port);
+    ovsdb_idl_add_column(ovnsb_idl, &sbrec_binding_col_logical_datapath);
+    ovsdb_idl_add_column(ovnsb_idl, &sbrec_binding_col_tunnel_key);
     ovsdb_idl_add_column(ovnsb_idl, &sbrec_pipeline_col_logical_datapath);
     ovsdb_idl_omit_alert(ovnsb_idl, &sbrec_pipeline_col_logical_datapath);
     ovsdb_idl_add_column(ovnsb_idl, &sbrec_pipeline_col_table_id);
@@ -774,9 +834,11 @@ main(int argc, char *argv[])
 
     ovnnb_seqno = ovsdb_idl_get_seqno(ovnnb_idl);
     ovn_seqno = ovsdb_idl_get_seqno(ovnsb_idl);
-    for (;;) {
+    exiting = false;
+    while (!exiting) {
         ovsdb_idl_run(ovnnb_idl);
         ovsdb_idl_run(ovnsb_idl);
+        unixctl_server_run(unixctl);
 
         if (!ovsdb_idl_is_alive(ovnnb_idl)) {
             int retval = ovsdb_idl_get_last_error(ovnnb_idl);
@@ -889,12 +951,27 @@ main(int argc, char *argv[])
             if (ctx.ovnsb_txn) {
                 ovsdb_idl_txn_wait(ctx.ovnsb_txn);
             }
+            unixctl_server_wait(unixctl);
+            if (exiting) {
+                poll_immediate_wake();
+            }
             poll_block();
         }
     }
 
+    unixctl_server_destroy(unixctl);
     ovsdb_idl_destroy(ovnsb_idl);
     ovsdb_idl_destroy(ovnnb_idl);
 
     exit(res);
 }
+
+static void
+ovn_northd_exit(struct unixctl_conn *conn, int argc OVS_UNUSED,
+                const char *argv[] OVS_UNUSED, void *exiting_)
+{
+    bool *exiting = exiting_;
+    *exiting = true;
+
+    unixctl_command_reply(conn, NULL);
+}