diff mbox

[ovs-dev,v2,13/14] ovn-nbctl: Enable database commands using db-ctl-base infrastructure.

Message ID 1441237451-17940-13-git-send-email-blp@nicira.com
State Superseded
Headers show

Commit Message

Ben Pfaff Sept. 2, 2015, 11:44 p.m. UTC
This makes ovn-nbctl into a pretty slavish imitation of ovn-nbctl, using
almost the same code.  It has two immediate benefits.  First, multiple
commands can now be chained together into a single ovn-nbctl invocation.
Second, the database commands such as "create", "set", and so on allow
queries and modifications that don't have specific commands already.
In the following commit, this allows testing the OVN ACL feature.

Signed-off-by: Ben Pfaff <blp@nicira.com>
---
 ovn/utilities/ovn-nbctl.c | 998 +++++++++++++++++++++++++++-------------------
 1 file changed, 590 insertions(+), 408 deletions(-)
diff mbox

Patch

diff --git a/ovn/utilities/ovn-nbctl.c b/ovn/utilities/ovn-nbctl.c
index e33dd37..b74fbaf 100644
--- a/ovn/utilities/ovn-nbctl.c
+++ b/ovn/utilities/ovn-nbctl.c
@@ -20,26 +20,262 @@ 
 #include <stdio.h>
 
 #include "command-line.h"
+#include "db-ctl-base.h"
 #include "dirs.h"
 #include "fatal-signal.h"
+#include "json.h"
 #include "ovn/lib/ovn-nb-idl.h"
 #include "poll-loop.h"
 #include "process.h"
 #include "stream.h"
 #include "stream-ssl.h"
+#include "table.h"
+#include "timeval.h"
 #include "util.h"
 #include "openvswitch/vlog.h"
 
-VLOG_DEFINE_THIS_MODULE(ovn_nbctl);
+VLOG_DEFINE_THIS_MODULE(nbctl);
 
-struct nbctl_context {
+/* --db: The database server to contact. */
+static const char *db;
+
+/* --oneline: Write each command's output as a single line? */
+static bool oneline;
+
+/* --dry-run: Do not commit any changes. */
+static bool dry_run;
+
+/* --timeout: Time to wait for a connection to 'db'. */
+static int timeout;
+
+/* Format for table output. */
+static struct table_style table_style = TABLE_STYLE_DEFAULT;
+
+/* The IDL we're using and the current transaction, if any.
+ * This is for use by nbctl_exit() only, to allow it to clean up.
+ * Other code should use its context arguments. */
+static struct ovsdb_idl *the_idl;
+static struct ovsdb_idl_txn *the_idl_txn;
+OVS_NO_RETURN static void nbctl_exit(int status);
+
+static void nbctl_cmd_init(void);
+OVS_NO_RETURN static void usage(void);
+static void parse_options(int argc, char *argv[], struct shash *local_options);
+static const char *nbctl_default_db(void);
+static void run_prerequisites(struct ctl_command[], size_t n_commands,
+                              struct ovsdb_idl *);
+static void do_nbctl(const char *args, struct ctl_command *, size_t n,
+                     struct ovsdb_idl *);
+
+int
+main(int argc, char *argv[])
+{
+    extern struct vlog_module VLM_reconnect;
     struct ovsdb_idl *idl;
-    struct ovsdb_idl_txn *txn;
-};
+    struct ctl_command *commands;
+    struct shash local_options;
+    unsigned int seqno;
+    size_t n_commands;
+    char *args;
 
-static const char *db;
+    set_program_name(argv[0]);
+    fatal_ignore_sigpipe();
+    vlog_set_levels(NULL, VLF_CONSOLE, VLL_WARN);
+    vlog_set_levels(&VLM_reconnect, VLF_ANY_DESTINATION, VLL_WARN);
+    nbrec_init();
+
+    nbctl_cmd_init();
+
+    /* Log our arguments.  This is often valuable for debugging systems. */
+    args = process_escape_args(argv);
+    VLOG(ctl_might_write_to_db(argv) ? VLL_INFO : VLL_DBG,
+         "Called as %s", args);
+
+    /* Parse command line. */
+    shash_init(&local_options);
+    parse_options(argc, argv, &local_options);
+    commands = ctl_parse_commands(argc - optind, argv + optind, &local_options,
+                                  &n_commands);
+
+    if (timeout) {
+        time_alarm(timeout);
+    }
+
+    /* Initialize IDL. */
+    idl = the_idl = ovsdb_idl_create(db, &nbrec_idl_class, true, false);
+    run_prerequisites(commands, n_commands, idl);
+
+    /* Execute the commands.
+     *
+     * 'seqno' is the database sequence number for which we last tried to
+     * execute our transaction.  There's no point in trying to commit more than
+     * once for any given sequence number, because if the transaction fails
+     * it's because the database changed and we need to obtain an up-to-date
+     * view of the database before we try the transaction again. */
+    seqno = ovsdb_idl_get_seqno(idl);
+    for (;;) {
+        ovsdb_idl_run(idl);
+        if (!ovsdb_idl_is_alive(idl)) {
+            int retval = ovsdb_idl_get_last_error(idl);
+            ctl_fatal("%s: database connection failed (%s)",
+                        db, ovs_retval_to_string(retval));
+        }
+
+        if (seqno != ovsdb_idl_get_seqno(idl)) {
+            seqno = ovsdb_idl_get_seqno(idl);
+            do_nbctl(args, commands, n_commands, idl);
+        }
+
+        if (seqno == ovsdb_idl_get_seqno(idl)) {
+            ovsdb_idl_wait(idl);
+            poll_block();
+        }
+    }
+}
+
+static const char *
+nbctl_default_db(void)
+{
+    static char *def;
+    if (!def) {
+        def = getenv("OVN_NB_DB");
+        if (!def) {
+            def = xasprintf("unix:%s/db.sock", ovs_rundir());
+        }
+    }
+    return def;
+}
+
+static void
+parse_options(int argc, char *argv[], struct shash *local_options)
+{
+    enum {
+        OPT_DB = UCHAR_MAX + 1,
+        OPT_NO_SYSLOG,
+        OPT_DRY_RUN,
+        OPT_ONELINE,
+        OPT_LOCAL,
+        OPT_COMMANDS,
+        OPT_OPTIONS,
+        VLOG_OPTION_ENUMS,
+        TABLE_OPTION_ENUMS
+    };
+    static const struct option global_long_options[] = {
+        {"db", required_argument, NULL, OPT_DB},
+        {"no-syslog", no_argument, NULL, OPT_NO_SYSLOG},
+        {"dry-run", no_argument, NULL, OPT_DRY_RUN},
+        {"oneline", no_argument, NULL, OPT_ONELINE},
+        {"timeout", required_argument, NULL, 't'},
+        {"help", no_argument, NULL, 'h'},
+        {"commands", no_argument, NULL, OPT_COMMANDS},
+        {"options", no_argument, NULL, OPT_OPTIONS},
+        {"version", no_argument, NULL, 'V'},
+        VLOG_LONG_OPTIONS,
+        STREAM_SSL_LONG_OPTIONS,
+        TABLE_LONG_OPTIONS,
+        {NULL, 0, NULL, 0},
+    };
+    const int n_global_long_options = ARRAY_SIZE(global_long_options) - 1;
+    char *tmp, *short_options;
+
+    struct option *options;
+    size_t allocated_options;
+    size_t n_options;
+    size_t i;
+
+    tmp = ovs_cmdl_long_options_to_short_options(global_long_options);
+    short_options = xasprintf("+%s", tmp);
+    free(tmp);
+
+    /* We want to parse both global and command-specific options here, but
+     * getopt_long() isn't too convenient for the job.  We copy our global
+     * options into a dynamic array, then append all of the command-specific
+     * options. */
+    options = xmemdup(global_long_options, sizeof global_long_options);
+    allocated_options = ARRAY_SIZE(global_long_options);
+    n_options = n_global_long_options;
+    ctl_add_cmd_options(&options, &n_options, &allocated_options, OPT_LOCAL);
+    table_style.format = TF_LIST;
+
+    for (;;) {
+        int idx;
+        int c;
+
+        c = getopt_long(argc, argv, short_options, options, &idx);
+        if (c == -1) {
+            break;
+        }
+
+        switch (c) {
+        case OPT_DB:
+            db = optarg;
+            break;
+
+        case OPT_ONELINE:
+            oneline = true;
+            break;
+
+        case OPT_NO_SYSLOG:
+            vlog_set_levels(&VLM_nbctl, VLF_SYSLOG, VLL_WARN);
+            break;
+
+        case OPT_DRY_RUN:
+            dry_run = true;
+            break;
+
+        case OPT_LOCAL:
+            if (shash_find(local_options, options[idx].name)) {
+                ctl_fatal("'%s' option specified multiple times",
+                            options[idx].name);
+            }
+            shash_add_nocopy(local_options,
+                             xasprintf("--%s", options[idx].name),
+                             optarg ? xstrdup(optarg) : NULL);
+            break;
+
+        case 'h':
+            usage();
+            exit(EXIT_SUCCESS);
+
+        case OPT_COMMANDS:
+            ctl_print_commands();
+
+        case OPT_OPTIONS:
+            ctl_print_options(global_long_options);
+
+        case 'V':
+            ovs_print_version(0, 0);
+            printf("DB Schema %s\n", nbrec_get_db_version());
+            exit(EXIT_SUCCESS);
+
+        case 't':
+            timeout = strtoul(optarg, NULL, 10);
+            if (timeout < 0) {
+                ctl_fatal("value %s on -t or --timeout is invalid", optarg);
+            }
+            break;
+
+        VLOG_OPTION_HANDLERS
+        TABLE_OPTION_HANDLERS(&table_style)
+        STREAM_SSL_OPTION_HANDLERS
+
+        case '?':
+            exit(EXIT_FAILURE);
+
+        default:
+            abort();
+        }
+    }
 
-static const char *default_db(void);
+    if (!db) {
+        db = nbctl_default_db();
+    }
+
+    for (i = n_global_long_options; options[i].name; i++) {
+        free(CONST_CAST(char *, options[i].name));
+    }
+    free(options);
+}
 
 static void
 usage(void)
@@ -93,18 +329,24 @@  Logical port commands:\n\
   lport-get-options LPORT   Get the type specific options for LPORT\n\
 \n\
 Options:\n\
-  --db=DATABASE             connect to DATABASE\n\
-                            (default: %s)\n\
-  -h, --help                display this help message\n\
-  -o, --options             list available options\n\
-  -V, --version             display version information\n\
-", program_name, program_name, default_db());
+  --db=DATABASE               connect to DATABASE\n\
+                              (default: %s)\n\
+  -t, --timeout=SECS          wait at most SECS seconds\n\
+  --dry-run                   do not commit changes to database\n\
+  --oneline                   print exactly one line of output per command\n",
+           program_name, program_name, nbctl_default_db());
     vlog_usage();
-    stream_usage("database", true, true, false);
+    printf("\
+  --no-syslog             equivalent to --verbose=nbctl:syslog:warn\n");
+    printf("\n\
+Other options:\n\
+  -h, --help                  display this help message\n\
+  -V, --version               display version information\n");
+    exit(EXIT_SUCCESS);
 }
 
 static const struct nbrec_logical_switch *
-lswitch_by_name_or_uuid(struct nbctl_context *nb_ctx, const char *id)
+lswitch_by_name_or_uuid(struct ctl_context *ctx, const char *id)
 {
     const struct nbrec_logical_switch *lswitch = NULL;
     bool is_uuid = false;
@@ -113,14 +355,14 @@  lswitch_by_name_or_uuid(struct nbctl_context *nb_ctx, const char *id)
 
     if (uuid_from_string(&lswitch_uuid, id)) {
         is_uuid = true;
-        lswitch = nbrec_logical_switch_get_for_uuid(nb_ctx->idl,
+        lswitch = nbrec_logical_switch_get_for_uuid(ctx->idl,
                                                     &lswitch_uuid);
     }
 
     if (!lswitch) {
         const struct nbrec_logical_switch *iter;
 
-        NBREC_LOGICAL_SWITCH_FOR_EACH(iter, nb_ctx->idl) {
+        NBREC_LOGICAL_SWITCH_FOR_EACH(iter, ctx->idl) {
             if (strcmp(iter->name, id)) {
                 continue;
             }
@@ -144,67 +386,64 @@  lswitch_by_name_or_uuid(struct nbctl_context *nb_ctx, const char *id)
 }
 
 static void
-print_lswitch(const struct nbrec_logical_switch *lswitch)
+print_lswitch(const struct nbrec_logical_switch *lswitch, struct ds *s)
 {
-    printf("    lswitch "UUID_FMT" (%s)\n",
-           UUID_ARGS(&lswitch->header_.uuid), lswitch->name);
+    ds_put_format(s, "    lswitch "UUID_FMT" (%s)\n",
+                  UUID_ARGS(&lswitch->header_.uuid), lswitch->name);
 
     for (size_t i = 0; i < lswitch->n_ports; i++) {
         const struct nbrec_logical_port *lport = lswitch->ports[i];
 
-        printf("        lport %s\n", lport->name);
+        ds_put_format(s, "        lport %s\n", lport->name);
         if (lport->parent_name && lport->n_tag) {
-            printf("            parent: %s, tag:%"PRIu64"\n",
-                   lport->parent_name, lport->tag[0]);
+            ds_put_format(s, "            parent: %s, tag:%"PRIu64"\n",
+                          lport->parent_name, lport->tag[0]);
         }
         if (lport->n_macs) {
-            printf("            macs:");
+            ds_put_cstr(s, "            macs:");
             for (size_t j = 0; j < lport->n_macs; j++) {
-                printf(" %s", lport->macs[j]);
+                ds_put_format(s, " %s", lport->macs[j]);
             }
-            printf("\n");
+            ds_put_char(s, '\n');
         }
     }
 }
 
 static void
-nbctl_show(struct ovs_cmdl_context *ctx)
+nbctl_show(struct ctl_context *ctx)
 {
-    struct nbctl_context *nb_ctx = ctx->pvt;
     const struct nbrec_logical_switch *lswitch;
 
     if (ctx->argc == 2) {
-        lswitch = lswitch_by_name_or_uuid(nb_ctx, ctx->argv[1]);
+        lswitch = lswitch_by_name_or_uuid(ctx, ctx->argv[1]);
         if (lswitch) {
-            print_lswitch(lswitch);
+            print_lswitch(lswitch, &ctx->output);
         }
     } else {
-        NBREC_LOGICAL_SWITCH_FOR_EACH(lswitch, nb_ctx->idl) {
-            print_lswitch(lswitch);
+        NBREC_LOGICAL_SWITCH_FOR_EACH(lswitch, ctx->idl) {
+            print_lswitch(lswitch, &ctx->output);
         }
     }
 }
 
 static void
-nbctl_lswitch_add(struct ovs_cmdl_context *ctx)
+nbctl_lswitch_add(struct ctl_context *ctx)
 {
-    struct nbctl_context *nb_ctx = ctx->pvt;
     struct nbrec_logical_switch *lswitch;
 
-    lswitch = nbrec_logical_switch_insert(nb_ctx->txn);
+    lswitch = nbrec_logical_switch_insert(ctx->txn);
     if (ctx->argc == 2) {
         nbrec_logical_switch_set_name(lswitch, ctx->argv[1]);
     }
 }
 
 static void
-nbctl_lswitch_del(struct ovs_cmdl_context *ctx)
+nbctl_lswitch_del(struct ctl_context *ctx)
 {
-    struct nbctl_context *nb_ctx = ctx->pvt;
     const char *id = ctx->argv[1];
     const struct nbrec_logical_switch *lswitch;
 
-    lswitch = lswitch_by_name_or_uuid(nb_ctx, id);
+    lswitch = lswitch_by_name_or_uuid(ctx, id);
     if (!lswitch) {
         return;
     }
@@ -213,26 +452,24 @@  nbctl_lswitch_del(struct ovs_cmdl_context *ctx)
 }
 
 static void
-nbctl_lswitch_list(struct ovs_cmdl_context *ctx)
+nbctl_lswitch_list(struct ctl_context *ctx)
 {
-    struct nbctl_context *nb_ctx = ctx->pvt;
     const struct nbrec_logical_switch *lswitch;
 
-    NBREC_LOGICAL_SWITCH_FOR_EACH(lswitch, nb_ctx->idl) {
-        printf(UUID_FMT " (%s)\n",
-               UUID_ARGS(&lswitch->header_.uuid), lswitch->name);
+    NBREC_LOGICAL_SWITCH_FOR_EACH(lswitch, ctx->idl) {
+        ds_put_format(&ctx->output, UUID_FMT " (%s)\n",
+                      UUID_ARGS(&lswitch->header_.uuid), lswitch->name);
     }
 }
 
 static void
-nbctl_lswitch_set_external_id(struct ovs_cmdl_context *ctx)
+nbctl_lswitch_set_external_id(struct ctl_context *ctx)
 {
-    struct nbctl_context *nb_ctx = ctx->pvt;
     const char *id = ctx->argv[1];
     const struct nbrec_logical_switch *lswitch;
     struct smap new_external_ids;
 
-    lswitch = lswitch_by_name_or_uuid(nb_ctx, id);
+    lswitch = lswitch_by_name_or_uuid(ctx, id);
     if (!lswitch) {
         return;
     }
@@ -249,13 +486,12 @@  nbctl_lswitch_set_external_id(struct ovs_cmdl_context *ctx)
 }
 
 static void
-nbctl_lswitch_get_external_id(struct ovs_cmdl_context *ctx)
+nbctl_lswitch_get_external_id(struct ctl_context *ctx)
 {
-    struct nbctl_context *nb_ctx = ctx->pvt;
     const char *id = ctx->argv[1];
     const struct nbrec_logical_switch *lswitch;
 
-    lswitch = lswitch_by_name_or_uuid(nb_ctx, id);
+    lswitch = lswitch_by_name_or_uuid(ctx, id);
     if (!lswitch) {
         return;
     }
@@ -268,7 +504,7 @@  nbctl_lswitch_get_external_id(struct ovs_cmdl_context *ctx)
 
         value = smap_get(&lswitch->external_ids, key);
         if (value) {
-            printf("%s\n", value);
+            ds_put_format(&ctx->output, "%s\n", value);
         }
     } else {
         struct smap_node *node;
@@ -276,13 +512,13 @@  nbctl_lswitch_get_external_id(struct ovs_cmdl_context *ctx)
         /* List all external IDs */
 
         SMAP_FOR_EACH(node, &lswitch->external_ids) {
-            printf("%s=%s\n", node->key, node->value);
+            ds_put_format(&ctx->output, "%s=%s\n", node->key, node->value);
         }
     }
 }
 
 static const struct nbrec_logical_port *
-lport_by_name_or_uuid(struct nbctl_context *nb_ctx, const char *id)
+lport_by_name_or_uuid(struct ctl_context *ctx, const char *id)
 {
     const struct nbrec_logical_port *lport = NULL;
     bool is_uuid = false;
@@ -290,11 +526,11 @@  lport_by_name_or_uuid(struct nbctl_context *nb_ctx, const char *id)
 
     if (uuid_from_string(&lport_uuid, id)) {
         is_uuid = true;
-        lport = nbrec_logical_port_get_for_uuid(nb_ctx->idl, &lport_uuid);
+        lport = nbrec_logical_port_get_for_uuid(ctx->idl, &lport_uuid);
     }
 
     if (!lport) {
-        NBREC_LOGICAL_PORT_FOR_EACH(lport, nb_ctx->idl) {
+        NBREC_LOGICAL_PORT_FOR_EACH(lport, ctx->idl) {
             if (!strcmp(lport->name, id)) {
                 break;
             }
@@ -310,14 +546,13 @@  lport_by_name_or_uuid(struct nbctl_context *nb_ctx, const char *id)
 }
 
 static void
-nbctl_lport_add(struct ovs_cmdl_context *ctx)
+nbctl_lport_add(struct ctl_context *ctx)
 {
-    struct nbctl_context *nb_ctx = ctx->pvt;
     struct nbrec_logical_port *lport;
     const struct nbrec_logical_switch *lswitch;
     int64_t tag;
 
-    lswitch = lswitch_by_name_or_uuid(nb_ctx, ctx->argv[1]);
+    lswitch = lswitch_by_name_or_uuid(ctx, ctx->argv[1]);
     if (!lswitch) {
         return;
     }
@@ -337,7 +572,7 @@  nbctl_lport_add(struct ovs_cmdl_context *ctx)
     }
 
     /* Create the logical port. */
-    lport = nbrec_logical_port_insert(nb_ctx->txn);
+    lport = nbrec_logical_port_insert(ctx->txn);
     nbrec_logical_port_set_name(lport, ctx->argv[2]);
     if (ctx->argc == 5) {
         nbrec_logical_port_set_parent_name(lport, ctx->argv[3]);
@@ -377,19 +612,18 @@  remove_lport(const struct nbrec_logical_switch *lswitch, size_t idx)
 }
 
 static void
-nbctl_lport_del(struct ovs_cmdl_context *ctx)
+nbctl_lport_del(struct ctl_context *ctx)
 {
-    struct nbctl_context *nb_ctx = ctx->pvt;
     const struct nbrec_logical_port *lport;
 
-    lport = lport_by_name_or_uuid(nb_ctx, ctx->argv[1]);
+    lport = lport_by_name_or_uuid(ctx, ctx->argv[1]);
     if (!lport) {
         return;
     }
 
     /* Find the switch that contains 'lport', then delete it. */
     const struct nbrec_logical_switch *lswitch;
-    NBREC_LOGICAL_SWITCH_FOR_EACH (lswitch, nb_ctx->idl) {
+    NBREC_LOGICAL_SWITCH_FOR_EACH (lswitch, ctx->idl) {
         for (size_t i = 0; i < lswitch->n_ports; i++) {
             if (lswitch->ports[i] == lport) {
                 remove_lport(lswitch, i);
@@ -403,65 +637,61 @@  nbctl_lport_del(struct ovs_cmdl_context *ctx)
 }
 
 static void
-nbctl_lport_list(struct ovs_cmdl_context *ctx)
+nbctl_lport_list(struct ctl_context *ctx)
 {
-    struct nbctl_context *nb_ctx = ctx->pvt;
     const char *id = ctx->argv[1];
     const struct nbrec_logical_switch *lswitch;
 
-    lswitch = lswitch_by_name_or_uuid(nb_ctx, id);
+    lswitch = lswitch_by_name_or_uuid(ctx, id);
     if (!lswitch) {
         return;
     }
 
     for (size_t i = 0; i < lswitch->n_ports; i++) {
         const struct nbrec_logical_port *lport = lswitch->ports[i];
-        printf(UUID_FMT " (%s)\n",
-               UUID_ARGS(&lport->header_.uuid), lport->name);
+        ds_put_format(&ctx->output, UUID_FMT " (%s)\n",
+                      UUID_ARGS(&lport->header_.uuid), lport->name);
     }
 }
 
 static void
-nbctl_lport_get_parent(struct ovs_cmdl_context *ctx)
+nbctl_lport_get_parent(struct ctl_context *ctx)
 {
-    struct nbctl_context *nb_ctx = ctx->pvt;
     const struct nbrec_logical_port *lport;
 
-    lport = lport_by_name_or_uuid(nb_ctx, ctx->argv[1]);
+    lport = lport_by_name_or_uuid(ctx, ctx->argv[1]);
     if (!lport) {
         return;
     }
 
     if (lport->parent_name) {
-        printf("%s\n", lport->parent_name);
+        ds_put_format(&ctx->output, "%s\n", lport->parent_name);
     }
 }
 
 static void
-nbctl_lport_get_tag(struct ovs_cmdl_context *ctx)
+nbctl_lport_get_tag(struct ctl_context *ctx)
 {
-    struct nbctl_context *nb_ctx = ctx->pvt;
     const struct nbrec_logical_port *lport;
 
-    lport = lport_by_name_or_uuid(nb_ctx, ctx->argv[1]);
+    lport = lport_by_name_or_uuid(ctx, ctx->argv[1]);
     if (!lport) {
         return;
     }
 
     if (lport->n_tag > 0) {
-        printf("%"PRId64"\n", lport->tag[0]);
+        ds_put_format(&ctx->output, "%"PRId64"\n", lport->tag[0]);
     }
 }
 
 static void
-nbctl_lport_set_external_id(struct ovs_cmdl_context *ctx)
+nbctl_lport_set_external_id(struct ctl_context *ctx)
 {
-    struct nbctl_context *nb_ctx = ctx->pvt;
     const char *id = ctx->argv[1];
     const struct nbrec_logical_port *lport;
     struct smap new_external_ids;
 
-    lport = lport_by_name_or_uuid(nb_ctx, id);
+    lport = lport_by_name_or_uuid(ctx, id);
     if (!lport) {
         return;
     }
@@ -478,13 +708,12 @@  nbctl_lport_set_external_id(struct ovs_cmdl_context *ctx)
 }
 
 static void
-nbctl_lport_get_external_id(struct ovs_cmdl_context *ctx)
+nbctl_lport_get_external_id(struct ctl_context *ctx)
 {
-    struct nbctl_context *nb_ctx = ctx->pvt;
     const char *id = ctx->argv[1];
     const struct nbrec_logical_port *lport;
 
-    lport = lport_by_name_or_uuid(nb_ctx, id);
+    lport = lport_by_name_or_uuid(ctx, id);
     if (!lport) {
         return;
     }
@@ -497,7 +726,7 @@  nbctl_lport_get_external_id(struct ovs_cmdl_context *ctx)
 
         value = smap_get(&lport->external_ids, key);
         if (value) {
-            printf("%s\n", value);
+            ds_put_format(&ctx->output, "%s\n", value);
         }
     } else {
         struct smap_node *node;
@@ -505,19 +734,18 @@  nbctl_lport_get_external_id(struct ovs_cmdl_context *ctx)
         /* List all external IDs */
 
         SMAP_FOR_EACH(node, &lport->external_ids) {
-            printf("%s=%s\n", node->key, node->value);
+            ds_put_format(&ctx->output, "%s=%s\n", node->key, node->value);
         }
     }
 }
 
 static void
-nbctl_lport_set_macs(struct ovs_cmdl_context *ctx)
+nbctl_lport_set_macs(struct ctl_context *ctx)
 {
-    struct nbctl_context *nb_ctx = ctx->pvt;
     const char *id = ctx->argv[1];
     const struct nbrec_logical_port *lport;
 
-    lport = lport_by_name_or_uuid(nb_ctx, id);
+    lport = lport_by_name_or_uuid(ctx, id);
     if (!lport) {
         return;
     }
@@ -527,31 +755,29 @@  nbctl_lport_set_macs(struct ovs_cmdl_context *ctx)
 }
 
 static void
-nbctl_lport_get_macs(struct ovs_cmdl_context *ctx)
+nbctl_lport_get_macs(struct ctl_context *ctx)
 {
-    struct nbctl_context *nb_ctx = ctx->pvt;
     const char *id = ctx->argv[1];
     const struct nbrec_logical_port *lport;
     size_t i;
 
-    lport = lport_by_name_or_uuid(nb_ctx, id);
+    lport = lport_by_name_or_uuid(ctx, id);
     if (!lport) {
         return;
     }
 
     for (i = 0; i < lport->n_macs; i++) {
-        printf("%s\n", lport->macs[i]);
+        ds_put_format(&ctx->output, "%s\n", lport->macs[i]);
     }
 }
 
 static void
-nbctl_lport_set_port_security(struct ovs_cmdl_context *ctx)
+nbctl_lport_set_port_security(struct ctl_context *ctx)
 {
-    struct nbctl_context *nb_ctx = ctx->pvt;
     const char *id = ctx->argv[1];
     const struct nbrec_logical_port *lport;
 
-    lport = lport_by_name_or_uuid(nb_ctx, id);
+    lport = lport_by_name_or_uuid(ctx, id);
     if (!lport) {
         return;
     }
@@ -561,47 +787,45 @@  nbctl_lport_set_port_security(struct ovs_cmdl_context *ctx)
 }
 
 static void
-nbctl_lport_get_port_security(struct ovs_cmdl_context *ctx)
+nbctl_lport_get_port_security(struct ctl_context *ctx)
 {
-    struct nbctl_context *nb_ctx = ctx->pvt;
     const char *id = ctx->argv[1];
     const struct nbrec_logical_port *lport;
     size_t i;
 
-    lport = lport_by_name_or_uuid(nb_ctx, id);
+    lport = lport_by_name_or_uuid(ctx, id);
     if (!lport) {
         return;
     }
 
     for (i = 0; i < lport->n_port_security; i++) {
-        printf("%s\n", lport->port_security[i]);
+        ds_put_format(&ctx->output, "%s\n", lport->port_security[i]);
     }
 }
 
 static void
-nbctl_lport_get_up(struct ovs_cmdl_context *ctx)
+nbctl_lport_get_up(struct ctl_context *ctx)
 {
-    struct nbctl_context *nb_ctx = ctx->pvt;
     const char *id = ctx->argv[1];
     const struct nbrec_logical_port *lport;
 
-    lport = lport_by_name_or_uuid(nb_ctx, id);
+    lport = lport_by_name_or_uuid(ctx, id);
     if (!lport) {
         return;
     }
 
-    printf("%s\n", (lport->up && *lport->up) ? "up" : "down");
+    ds_put_format(&ctx->output,
+                  "%s\n", (lport->up && *lport->up) ? "up" : "down");
 }
 
 static void
-nbctl_lport_set_enabled(struct ovs_cmdl_context *ctx)
+nbctl_lport_set_enabled(struct ctl_context *ctx)
 {
-    struct nbctl_context *nb_ctx = ctx->pvt;
     const char *id = ctx->argv[1];
     const char *state = ctx->argv[2];
     const struct nbrec_logical_port *lport;
 
-    lport = lport_by_name_or_uuid(nb_ctx, id);
+    lport = lport_by_name_or_uuid(ctx, id);
     if (!lport) {
         return;
     }
@@ -618,30 +842,28 @@  nbctl_lport_set_enabled(struct ovs_cmdl_context *ctx)
 }
 
 static void
-nbctl_lport_get_enabled(struct ovs_cmdl_context *ctx)
+nbctl_lport_get_enabled(struct ctl_context *ctx)
 {
-    struct nbctl_context *nb_ctx = ctx->pvt;
     const char *id = ctx->argv[1];
     const struct nbrec_logical_port *lport;
 
-    lport = lport_by_name_or_uuid(nb_ctx, id);
+    lport = lport_by_name_or_uuid(ctx, id);
     if (!lport) {
         return;
     }
 
-    printf("%s\n",
-           (!lport->enabled || *lport->enabled) ? "enabled" : "disabled");
+    ds_put_format(&ctx->output, "%s\n",
+                  !lport->enabled || *lport->enabled ? "enabled" : "disabled");
 }
 
 static void
-nbctl_lport_set_type(struct ovs_cmdl_context *ctx)
+nbctl_lport_set_type(struct ctl_context *ctx)
 {
-    struct nbctl_context *nb_ctx = ctx->pvt;
     const char *id = ctx->argv[1];
     const char *type = ctx->argv[2];
     const struct nbrec_logical_port *lport;
 
-    lport = lport_by_name_or_uuid(nb_ctx, id);
+    lport = lport_by_name_or_uuid(ctx, id);
     if (!lport) {
         return;
     }
@@ -650,30 +872,28 @@  nbctl_lport_set_type(struct ovs_cmdl_context *ctx)
 }
 
 static void
-nbctl_lport_get_type(struct ovs_cmdl_context *ctx)
+nbctl_lport_get_type(struct ctl_context *ctx)
 {
-    struct nbctl_context *nb_ctx = ctx->pvt;
     const char *id = ctx->argv[1];
     const struct nbrec_logical_port *lport;
 
-    lport = lport_by_name_or_uuid(nb_ctx, id);
+    lport = lport_by_name_or_uuid(ctx, id);
     if (!lport) {
         return;
     }
 
-    printf("%s\n", lport->type);
+    ds_put_format(&ctx->output, "%s\n", lport->type);
 }
 
 static void
-nbctl_lport_set_options(struct ovs_cmdl_context *ctx)
+nbctl_lport_set_options(struct ctl_context *ctx)
 {
-    struct nbctl_context *nb_ctx = ctx->pvt;
     const char *id = ctx->argv[1];
     const struct nbrec_logical_port *lport;
     size_t i;
     struct smap options = SMAP_INITIALIZER(&options);
 
-    lport = lport_by_name_or_uuid(nb_ctx, id);
+    lport = lport_by_name_or_uuid(ctx, id);
     if (!lport) {
         return;
     }
@@ -694,339 +914,301 @@  nbctl_lport_set_options(struct ovs_cmdl_context *ctx)
 }
 
 static void
-nbctl_lport_get_options(struct ovs_cmdl_context *ctx)
+nbctl_lport_get_options(struct ctl_context *ctx)
 {
-    struct nbctl_context *nb_ctx = ctx->pvt;
     const char *id = ctx->argv[1];
     const struct nbrec_logical_port *lport;
     struct smap_node *node;
 
-    lport = lport_by_name_or_uuid(nb_ctx, id);
+    lport = lport_by_name_or_uuid(ctx, id);
     if (!lport) {
         return;
     }
 
     SMAP_FOR_EACH(node, &lport->options) {
-        printf("%s=%s\n", node->key, node->value);
+        ds_put_format(&ctx->output, "%s=%s\n", node->key, node->value);
     }
 }
 
-static void
-parse_options(int argc, char *argv[])
-{
-    enum {
-        VLOG_OPTION_ENUMS,
-    };
-    static const struct option long_options[] = {
-        {"db", required_argument, NULL, 'd'},
-        {"help", no_argument, NULL, 'h'},
-        {"options", no_argument, NULL, 'o'},
-        {"version", no_argument, NULL, 'V'},
-        VLOG_LONG_OPTIONS,
-        STREAM_SSL_LONG_OPTIONS,
-        {NULL, 0, NULL, 0},
-    };
-    char *short_options = ovs_cmdl_long_options_to_short_options(long_options);
+static const struct ctl_table_class tables[] = {
+    {&nbrec_table_logical_switch,
+     {{&nbrec_table_logical_switch, &nbrec_logical_switch_col_name, NULL},
+      {NULL, NULL, NULL}}},
 
-    for (;;) {
-        int c;
+    {&nbrec_table_logical_port,
+     {{&nbrec_table_logical_port, &nbrec_logical_port_col_name, NULL},
+      {NULL, NULL, NULL}}},
 
-        c = getopt_long(argc, argv, short_options, long_options, NULL);
-        if (c == -1) {
-            break;
-        }
+    {&nbrec_table_acl,
+     {{NULL, NULL, NULL},
+      {NULL, NULL, NULL}}},
 
-        switch (c) {
-        VLOG_OPTION_HANDLERS;
-        STREAM_SSL_OPTION_HANDLERS;
+    {&nbrec_table_logical_router,
+     {{&nbrec_table_logical_router, &nbrec_logical_router_col_name, NULL},
+      {NULL, NULL, NULL}}},
 
-        case 'd':
-            db = optarg;
-            break;
+    {&nbrec_table_logical_router_port,
+     {{&nbrec_table_logical_router_port, &nbrec_logical_router_port_col_name,
+       NULL},
+      {NULL, NULL, NULL}}},
 
-        case 'h':
-            usage();
-            exit(EXIT_SUCCESS);
+    {NULL, {{NULL, NULL, NULL}, {NULL, NULL, NULL}}}
+};
+
+static void
+run_prerequisites(struct ctl_command *commands, size_t n_commands,
+                  struct ovsdb_idl *idl)
+{
+    struct ctl_command *c;
 
-        case 'o':
-            ovs_cmdl_print_options(long_options);
-            exit(EXIT_SUCCESS);
+    for (c = commands; c < &commands[n_commands]; c++) {
+        if (c->syntax->prerequisites) {
+            struct ctl_context ctx;
 
-        case 'V':
-            ovs_print_version(0, 0);
-            exit(EXIT_SUCCESS);
+            ds_init(&c->output);
+            c->table = NULL;
 
-        default:
-            break;
+            ctl_context_init(&ctx, c, idl, NULL, NULL, NULL);
+            (c->syntax->prerequisites)(&ctx);
+            ctl_context_done(&ctx, c);
+
+            ovs_assert(!c->output.string);
+            ovs_assert(!c->table);
         }
     }
+}
 
-    if (!db) {
-        db = default_db();
+static void
+do_nbctl(const char *args, struct ctl_command *commands, size_t n_commands,
+         struct ovsdb_idl *idl)
+{
+    struct ovsdb_idl_txn *txn;
+    enum ovsdb_idl_txn_status status;
+    struct ovsdb_symbol_table *symtab;
+    struct ctl_context ctx;
+    struct ctl_command *c;
+    struct shash_node *node;
+    char *error = NULL;
+
+    txn = the_idl_txn = ovsdb_idl_txn_create(idl);
+    if (dry_run) {
+        ovsdb_idl_txn_set_dry_run(txn);
     }
 
-    free(short_options);
-}
+    ovsdb_idl_txn_add_comment(txn, "ovs-nbctl: %s", args);
 
-static const struct ovs_cmdl_command all_commands[] = {
-    {
-        .name = "show",
-        .usage = "[LSWITCH]",
-        .min_args = 0,
-        .max_args = 1,
-        .handler = nbctl_show,
-    },
-    {
-        .name = "lswitch-add",
-        .usage = "[LSWITCH]",
-        .min_args = 0,
-        .max_args = 1,
-        .handler = nbctl_lswitch_add,
-    },
-    {
-        .name = "lswitch-del",
-        .usage = "LSWITCH",
-        .min_args = 1,
-        .max_args = 1,
-        .handler = nbctl_lswitch_del,
-    },
-    {
-        .name = "lswitch-list",
-        .usage = "",
-        .min_args = 0,
-        .max_args = 0,
-        .handler = nbctl_lswitch_list,
-    },
-    {
-        .name = "lswitch-set-external-id",
-        .usage = "LSWITCH KEY [VALUE]",
-        .min_args = 2,
-        .max_args = 3,
-        .handler = nbctl_lswitch_set_external_id,
-    },
-    {
-        .name = "lswitch-get-external-id",
-        .usage = "LSWITCH [KEY]",
-        .min_args = 1,
-        .max_args = 2,
-        .handler = nbctl_lswitch_get_external_id,
-    },
-    {
-        .name = "lport-add",
-        .usage = "LSWITCH LPORT [PARENT] [TAG]",
-        .min_args = 2,
-        .max_args = 4,
-        .handler = nbctl_lport_add,
-    },
-    {
-        .name = "lport-del",
-        .usage = "LPORT",
-        .min_args = 1,
-        .max_args = 1,
-        .handler = nbctl_lport_del,
-    },
-    {
-        .name = "lport-list",
-        .usage = "LSWITCH",
-        .min_args = 1,
-        .max_args = 1,
-        .handler = nbctl_lport_list,
-    },
-    {
-        .name = "lport-get-parent",
-        .usage = "LPORT",
-        .min_args = 1,
-        .max_args = 1,
-        .handler = nbctl_lport_get_parent,
-    },
-    {
-        .name = "lport-get-tag",
-        .usage = "LPORT",
-        .min_args = 1,
-        .max_args = 1,
-        .handler = nbctl_lport_get_tag,
-    },
-    {
-        .name = "lport-set-external-id",
-        .usage = "LPORT KEY [VALUE]",
-        .min_args = 2,
-        .max_args = 3,
-        .handler = nbctl_lport_set_external_id,
-    },
-    {
-        .name = "lport-get-external-id",
-        .usage = "LPORT [KEY]",
-        .min_args = 1,
-        .max_args = 2,
-        .handler = nbctl_lport_get_external_id,
-    },
-    {
-        .name = "lport-set-macs",
-        .usage = "LPORT [MAC]...",
-        .min_args = 1,
-        /* Accept however many arguments the system will allow. */
-        .max_args = INT_MAX,
-        .handler = nbctl_lport_set_macs,
-    },
-    {
-        .name = "lport-get-macs",
-        .usage = "LPORT",
-        .min_args = 1,
-        .max_args = 1,
-        .handler = nbctl_lport_get_macs,
-    },
-    {
-        .name = "lport-set-port-security",
-        .usage = "LPORT [ADDRS]...",
-        .min_args = 0,
-        /* Accept however many arguments the system will allow. */
-        .max_args = INT_MAX,
-        .handler = nbctl_lport_set_port_security,
-    },
-    {
-        .name = "lport-get-port-security",
-        .usage = "LPORT",
-        .min_args = 1,
-        .max_args = 1,
-        .handler = nbctl_lport_get_port_security,
-    },
-    {
-        .name = "lport-get-up",
-        .usage = "LPORT",
-        .min_args = 1,
-        .max_args = 1,
-        .handler = nbctl_lport_get_up,
-    },
-    {
-        .name = "lport-set-enabled",
-        .usage = "LPORT STATE",
-        .min_args = 2,
-        .max_args = 2,
-        .handler = nbctl_lport_set_enabled,
-    },
-    {
-        .name = "lport-get-enabled",
-        .usage = "LPORT",
-        .min_args = 1,
-        .max_args = 1,
-        .handler = nbctl_lport_get_enabled,
-    },
-    {
-        .name = "lport-set-type",
-        .usage = "LPORT TYPE",
-        .min_args = 2,
-        .max_args = 2,
-        .handler = nbctl_lport_set_type,
-    },
-    {
-        .name = "lport-get-type",
-        .usage = "LPORT",
-        .min_args = 1,
-        .max_args = 1,
-        .handler = nbctl_lport_get_type,
-    },
-    {
-        .name = "lport-set-options",
-        .usage = "LPORT KEY=VALUE [KEY=VALUE]...",
-        .min_args = 1,
-        .max_args = INT_MAX,
-        .handler = nbctl_lport_set_options
-    },
-    {
-        .name = "lport-get-options",
-        .usage = "LPORT",
-        .min_args = 1,
-        .max_args = 1,
-        .handler = nbctl_lport_get_options,
-    },
-
-    {
-        /* sentinel */
-        .name = NULL,
-    },
-};
+    symtab = ovsdb_symbol_table_create();
+    for (c = commands; c < &commands[n_commands]; c++) {
+        ds_init(&c->output);
+        c->table = NULL;
+    }
+    ctl_context_init(&ctx, NULL, idl, txn, symtab, NULL);
+    for (c = commands; c < &commands[n_commands]; c++) {
+        ctl_context_init_command(&ctx, c);
+        if (c->syntax->run) {
+            (c->syntax->run)(&ctx);
+        }
+        ctl_context_done_command(&ctx, c);
 
-static const struct ovs_cmdl_command *
-get_all_commands(void)
-{
-    return all_commands;
-}
+        if (ctx.try_again) {
+            ctl_context_done(&ctx, NULL);
+            goto try_again;
+        }
+    }
+    ctl_context_done(&ctx, NULL);
+
+    SHASH_FOR_EACH (node, &symtab->sh) {
+        struct ovsdb_symbol *symbol = node->data;
+        if (!symbol->created) {
+            ctl_fatal("row id \"%s\" is referenced but never created (e.g. "
+                      "with \"-- --id=%s create ...\")",
+                      node->name, node->name);
+        }
+        if (!symbol->strong_ref) {
+            if (!symbol->weak_ref) {
+                VLOG_WARN("row id \"%s\" was created but no reference to it "
+                          "was inserted, so it will not actually appear in "
+                          "the database", node->name);
+            } else {
+                VLOG_WARN("row id \"%s\" was created but only a weak "
+                          "reference to it was inserted, so it will not "
+                          "actually appear in the database", node->name);
+            }
+        }
+    }
 
-static const char *
-default_db(void)
-{
-    static char *def;
-    if (!def) {
-        def = getenv("OVN_NB_DB");
-        if (!def) {
-            def = xasprintf("unix:%s/db.sock", ovs_rundir());
+    status = ovsdb_idl_txn_commit_block(txn);
+    if (status == TXN_UNCHANGED || status == TXN_SUCCESS) {
+        for (c = commands; c < &commands[n_commands]; c++) {
+            if (c->syntax->postprocess) {
+                ctl_context_init(&ctx, c, idl, txn, symtab, NULL);
+                (c->syntax->postprocess)(&ctx);
+                ctl_context_done(&ctx, c);
+            }
         }
     }
-    return def;
-}
+    error = xstrdup(ovsdb_idl_txn_get_error(txn));
 
-int
-main(int argc, char *argv[])
-{
-    extern struct vlog_module VLM_reconnect;
-    struct ovs_cmdl_context ctx;
-    struct nbctl_context nb_ctx = { .idl = NULL, };
-    enum ovsdb_idl_txn_status txn_status;
-    unsigned int seqno;
-    int res = 0;
-    char *args;
+    switch (status) {
+    case TXN_UNCOMMITTED:
+    case TXN_INCOMPLETE:
+        OVS_NOT_REACHED();
 
-    fatal_ignore_sigpipe();
-    set_program_name(argv[0]);
-    vlog_set_levels(NULL, VLF_CONSOLE, VLL_WARN);
-    vlog_set_levels(&VLM_reconnect, VLF_ANY_DESTINATION, VLL_WARN);
-    parse_options(argc, argv);
-    nbrec_init();
+    case TXN_ABORTED:
+        /* Should not happen--we never call ovsdb_idl_txn_abort(). */
+        ctl_fatal("transaction aborted");
 
-    args = process_escape_args(argv);
+    case TXN_UNCHANGED:
+    case TXN_SUCCESS:
+        break;
 
-    nb_ctx.idl = ovsdb_idl_create(db, &nbrec_idl_class, true, false);
-    ctx.pvt = &nb_ctx;
-    ctx.argc = argc - optind;
-    ctx.argv = argv + optind;
+    case TXN_TRY_AGAIN:
+        goto try_again;
 
-    seqno = ovsdb_idl_get_seqno(nb_ctx.idl);
-    for (;;) {
-        ovsdb_idl_run(nb_ctx.idl);
+    case TXN_ERROR:
+        ctl_fatal("transaction error: %s", error);
 
-        if (!ovsdb_idl_is_alive(nb_ctx.idl)) {
-            int retval = ovsdb_idl_get_last_error(nb_ctx.idl);
-            VLOG_ERR("%s: database connection failed (%s)",
-                    db, ovs_retval_to_string(retval));
-            res = 1;
-            break;
-        }
+    case TXN_NOT_LOCKED:
+        /* Should not happen--we never call ovsdb_idl_set_lock(). */
+        ctl_fatal("database not locked");
 
-        if (seqno != ovsdb_idl_get_seqno(nb_ctx.idl)) {
-            nb_ctx.txn = ovsdb_idl_txn_create(nb_ctx.idl);
-            ovsdb_idl_txn_add_comment(nb_ctx.txn, "ovn-nbctl: %s", args);
-            ovs_cmdl_run_command(&ctx, get_all_commands());
-            txn_status = ovsdb_idl_txn_commit_block(nb_ctx.txn);
-            if (txn_status == TXN_TRY_AGAIN) {
-                ovsdb_idl_txn_destroy(nb_ctx.txn);
-                nb_ctx.txn = NULL;
-                continue;
-            } else {
-                break;
+    default:
+        OVS_NOT_REACHED();
+    }
+    free(error);
+
+    ovsdb_symbol_table_destroy(symtab);
+
+    for (c = commands; c < &commands[n_commands]; c++) {
+        struct ds *ds = &c->output;
+
+        if (c->table) {
+            table_print(c->table, &table_style);
+        } else if (oneline) {
+            size_t j;
+
+            ds_chomp(ds, '\n');
+            for (j = 0; j < ds->length; j++) {
+                int ch = ds->string[j];
+                switch (ch) {
+                case '\n':
+                    fputs("\\n", stdout);
+                    break;
+
+                case '\\':
+                    fputs("\\\\", stdout);
+                    break;
+
+                default:
+                    putchar(ch);
+                }
             }
+            putchar('\n');
+        } else {
+            fputs(ds_cstr(ds), stdout);
         }
+        ds_destroy(&c->output);
+        table_destroy(c->table);
+        free(c->table);
 
-        if (seqno == ovsdb_idl_get_seqno(nb_ctx.idl)) {
-            ovsdb_idl_wait(nb_ctx.idl);
-            poll_block();
-        }
+        shash_destroy_free_data(&c->options);
+    }
+    free(commands);
+    ovsdb_idl_txn_destroy(txn);
+    ovsdb_idl_destroy(idl);
+
+    exit(EXIT_SUCCESS);
+
+try_again:
+    /* Our transaction needs to be rerun, or a prerequisite was not met.  Free
+     * resources and return so that the caller can try again. */
+    if (txn) {
+        ovsdb_idl_txn_abort(txn);
+        ovsdb_idl_txn_destroy(txn);
+        the_idl_txn = NULL;
+    }
+    ovsdb_symbol_table_destroy(symtab);
+    for (c = commands; c < &commands[n_commands]; c++) {
+        ds_destroy(&c->output);
+        table_destroy(c->table);
+        free(c->table);
     }
+    free(error);
+}
 
-    if (nb_ctx.txn) {
-        ovsdb_idl_txn_destroy(nb_ctx.txn);
+/* Frees the current transaction and the underlying IDL and then calls
+ * exit(status).
+ *
+ * Freeing the transaction and the IDL is not strictly necessary, but it makes
+ * for a clean memory leak report from valgrind in the normal case.  That makes
+ * it easier to notice real memory leaks. */
+static void
+nbctl_exit(int status)
+{
+    if (the_idl_txn) {
+        ovsdb_idl_txn_abort(the_idl_txn);
+        ovsdb_idl_txn_destroy(the_idl_txn);
     }
-    ovsdb_idl_destroy(nb_ctx.idl);
-    free(args);
+    ovsdb_idl_destroy(the_idl);
+    exit(status);
+}
+
+static const struct ctl_command_syntax nbctl_commands[] = {
+    { "show", 0, 1, "[LSWITCH]", NULL, nbctl_show, NULL, "", RO },
+
+    /* lswitch commands. */
+    { "lswitch-add", 0, 1, "[LSWITCH]", NULL, nbctl_lswitch_add,
+      NULL, "", RW },
+    { "lswitch-del", 1, 1, "LSWITCH", NULL, nbctl_lswitch_del,
+      NULL, "", RW },
+    { "lswitch-list", 0, 0, "", NULL, nbctl_lswitch_list, NULL, "", RO },
+    { "lswitch-set-external-id", 2, 3, "LSWITCH KEY [VALUE]", NULL,
+      nbctl_lswitch_set_external_id, NULL, "", RW },
+    { "lswitch-get-external-id", 1, 2, "LSWITCH [KEY]", NULL,
+      nbctl_lswitch_get_external_id, NULL, "", RO },
+
+    /* lport commands. */
+    { "lport-add", 2, 4, "LSWITCH LPORT [PARENT] [TAG]", NULL, nbctl_lport_add,
+      NULL, "", RW },
+    { "lport-del", 1, 1, "LPORT", NULL, nbctl_lport_del, NULL, "", RO },
+    { "lport-list", 1, 1, "LSWITCH", NULL, nbctl_lport_list, NULL, "", RO },
+    { "lport-get-parent", 1, 1, "LPORT", NULL, nbctl_lport_get_parent, NULL,
+      "", RO },
+    { "lport-get-tag", 1, 1, "LPORT", NULL, nbctl_lport_get_tag, NULL, "",
+      RO },
+    { "lport-set-external-id", 2, 3, "LPORT KEY [VALUE]", NULL,
+      nbctl_lport_set_external_id, NULL, "", RW },
+    { "lport-get-external-id", 1, 2, "LPORT [KEY]", NULL,
+      nbctl_lport_get_external_id, NULL, "", RO },
+    { "lport-set-macs", 1, INT_MAX, "LPORT [MAC]...", NULL,
+      nbctl_lport_set_macs, NULL, "", RW },
+    { "lport-get-macs", 1, 1, "LPORT", NULL, nbctl_lport_get_macs, NULL,
+      "", RO },
+    { "lport-set-port-security", 0, INT_MAX, "LPORT [ADDRS]...", NULL,
+      nbctl_lport_set_port_security, NULL, "", RW },
+    { "lport-get-port-security", 1, 1, "LPORT", NULL,
+      nbctl_lport_get_port_security, NULL, "", RO },
+    { "lport-get-up", 1, 1, "LPORT", NULL, nbctl_lport_get_up, NULL, "", RO },
+    { "lport-set-enabled", 2, 2, "LPORT STATE", NULL, nbctl_lport_set_enabled,
+      NULL, "", RW },
+    { "lport-get-enabled", 1, 1, "LPORT", NULL, nbctl_lport_get_enabled, NULL,
+      "", RO },
+    { "lport-set-type", 2, 2, "LPORT TYPE", NULL, nbctl_lport_set_type, NULL,
+      "", RW },
+    { "lport-get-type", 1, 1, "LPORT", NULL, nbctl_lport_get_type, NULL, "",
+      RO },
+    { "lport-set-options", 1, INT_MAX, "LPORT KEY=VALUE [KEY=VALUE]...", NULL,
+      nbctl_lport_set_options, NULL, "", RW },
+    { "lport-get-options", 1, 1, "LPORT", NULL, nbctl_lport_get_options, NULL,
+      "", RO },
+
+    {NULL, 0, 0, NULL, NULL, NULL, NULL, "", RO},
+};
 
-    exit(res);
+/* Registers nbctl and common db commands. */
+static void
+nbctl_cmd_init(void)
+{
+    ctl_init(tables, NULL, nbctl_exit);
+    ctl_register_commands(nbctl_commands);
 }