ovn-sb: Remove the "Gateway" table from the ovn-sb schema.
[cascardo/ovs.git] / ovn / ovn-nbctl.c
index 10fa5f8..0bdb3a3 100644 (file)
 #include <config.h>
 
 #include <getopt.h>
+#include <inttypes.h>
 #include <stdlib.h>
 #include <stdio.h>
 
 #include "command-line.h"
 #include "dirs.h"
 #include "fatal-signal.h"
-#include "ovn/ovn-nb-idl.h"
+#include "ovn/lib/ovn-nb-idl.h"
 #include "poll-loop.h"
+#include "process.h"
 #include "stream.h"
 #include "stream-ssl.h"
 #include "util.h"
@@ -46,27 +48,49 @@ usage(void)
 %s: OVN northbound DB management utility\n\
 usage: %s [OPTIONS] COMMAND [ARG...]\n\
 \n\
-Logical Switch Commands:\n\
-  lswitch-add [name]        Create a logical switch\n\
-  lswitch-del <lswitch>     Delete a logical switch\n\
-  lswitch-list              List configured logical switches\n\
-  lswitch-set-external-id <lswitch> <key> [value]\n\
-                            Set or delete an external:id on a logical switch\n\
-  lswitch-get-external-id <lswitch> [key]\n\
-                            List one or all external:ids set on a switch\n\
+General commands:\n\
+  show                      print overview of database contents\n\
+  show LSWITCH              print overview of database contents for LSWITCH\n\
 \n\
-Logical Port Commands:\n\
-  lport-add <name> <lswitch> Create a logical port on a logical switch\n\
-  lport-del <lport>         Delete a logical port (by name or UUID)\n\
-  lport-list <lswitch>      List ports on a logical switch\n\
-  lport-set-external-id <lport> <key> [value]\n\
-                            Set or delete an external:id on a logical port\n\
-  lport-get-external-id <lport> [key]\n\
-                            List one or all external:ids set on a port\n\
-  lport-set-macs <lport> [MAC] [MAC] [...]\n\
-                            Set MAC addresses for the logical port. Specify\n\
-                            more than one using additional arguments.\n\
-  lport-get-macs <lport>    Get a list of MAC addresses on the port.\n\
+Logical switch commands:\n\
+  lswitch-add [LSWITCH]     create a logical switch named LSWITCH\n\
+  lswitch-del LSWITCH       delete LSWITCH and all its ports\n\
+  lswitch-list              print the names of all logical switches\n\
+  lswitch-set-external-id LSWITCH KEY [VALUE]\n\
+                            set or delete an external-id on LSWITCH\n\
+  lswitch-get-external-id LSWITCH [KEY]\n\
+                            list one or all external-ids on LSWITCH\n\
+\n\
+Logical port commands:\n\
+  lport-add LSWITCH LPORT   add logical port LPORT on LSWITCH\n\
+  lport-add LSWITCH LPORT PARENT TAG\n\
+                            add logical port LPORT on LSWITCH with PARENT\n\
+                            on TAG\n\
+  lport-del LPORT           delete LPORT from its attached switch\n\
+  lport-list LSWITCH        print the names of all logical ports on LSWITCH\n\
+  lport-get-parent LPORT    get the parent of LPORT if set\n\
+  lport-get-tag LPORT       get the LPORT's tag if set\n\
+  lport-set-external-id LPORT KEY [VALUE]\n\
+                            set or delete an external-id on LPORT\n\
+  lport-get-external-id LPORT [KEY]\n\
+                            list one or all external-ids on LPORT\n\
+  lport-set-macs LPORT [MAC]...\n\
+                            set MAC addresses for LPORT.\n\
+  lport-get-macs LPORT      get a list of MAC addresses on LPORT\n\
+  lport-set-port-security LPORT [ADDRS]...\n\
+                            set port security addresses for LPORT.\n\
+  lport-get-port-security LPORT    get LPORT's port security addresses\n\
+  lport-get-up LPORT        get state of LPORT ('up' or 'down')\n\
+  lport-set-enabled LPORT STATE\n\
+                            set administrative state LPORT\n\
+                            ('enabled' or 'disabled')\n\
+  lport-get-enabled LPORT   get administrative state LPORT\n\
+                            ('enabled' or 'disabled')\n\
+  lport-set-type LPORT TYPE Set the type for LPORT\n\
+  lport-get-type LPORT      Get the type for LPORT\n\
+  lport-set-options LPORT KEY=VALUE [KEY=VALUE]...\n\
+                            Set options related to the type of LPORT\n\
+  lport-get-options LPORT   Get the type specific options for LPORT\n\
 \n\
 Options:\n\
   --db=DATABASE             connect to DATABASE\n\
@@ -119,6 +143,48 @@ lswitch_by_name_or_uuid(struct nbctl_context *nb_ctx, const char *id)
     return lswitch;
 }
 
+static void
+print_lswitch(const struct nbrec_logical_switch *lswitch)
+{
+    printf("    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);
+        if (lport->parent_name && lport->n_tag) {
+            printf("            parent: %s, tag:%"PRIu64"\n",
+                   lport->parent_name, lport->tag[0]);
+        }
+        if (lport->n_macs) {
+            printf("            macs:");
+            for (size_t j = 0; j < lport->n_macs; j++) {
+                printf(" %s", lport->macs[j]);
+            }
+            printf("\n");
+        }
+    }
+}
+
+static void
+do_show(struct ovs_cmdl_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]);
+        if (lswitch) {
+            print_lswitch(lswitch);
+        }
+    } else {
+        NBREC_LOGICAL_SWITCH_FOR_EACH(lswitch, nb_ctx->idl) {
+            print_lswitch(lswitch);
+        }
+    }
+}
+
 static void
 do_lswitch_add(struct ovs_cmdl_context *ctx)
 {
@@ -201,10 +267,8 @@ do_lswitch_get_external_id(struct ovs_cmdl_context *ctx)
         /* List one external ID */
 
         value = smap_get(&lswitch->external_ids, key);
-        if (value && *value) {
+        if (value) {
             printf("%s\n", value);
-        } else {
-            printf("external-id '%s' is not set.\n", key);
         }
     } else {
         struct smap_node *node;
@@ -251,15 +315,65 @@ do_lport_add(struct ovs_cmdl_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[2]);
+    lswitch = lswitch_by_name_or_uuid(nb_ctx, ctx->argv[1]);
     if (!lswitch) {
         return;
     }
 
+    if (ctx->argc != 3 && ctx->argc != 5) {
+        /* If a parent_name is specified, a tag must be specified as well. */
+        VLOG_WARN("Invalid arguments to lport-add.");
+        return;
+    }
+
+    if (ctx->argc == 5) {
+        /* Validate tag. */
+        if (!ovs_scan(ctx->argv[4], "%"SCNd64, &tag) || tag < 0 || tag > 4095) {
+            VLOG_WARN("Invalid tag '%s'", ctx->argv[4]);
+            return;
+        }
+    }
+
+    /* Create the logical port. */
     lport = nbrec_logical_port_insert(nb_ctx->txn);
-    nbrec_logical_port_set_name(lport, ctx->argv[1]);
-    nbrec_logical_port_set_lswitch(lport, lswitch);
+    nbrec_logical_port_set_name(lport, ctx->argv[2]);
+    if (ctx->argc == 5) {
+        nbrec_logical_port_set_parent_name(lport, ctx->argv[3]);
+        nbrec_logical_port_set_tag(lport, &tag, 1);
+    }
+
+    /* Insert the logical port into the logical switch. */
+    nbrec_logical_switch_verify_ports(lswitch);
+    struct nbrec_logical_port **new_ports = xmalloc(sizeof *new_ports *
+                                                    (lswitch->n_ports + 1));
+    memcpy(new_ports, lswitch->ports, sizeof *new_ports * lswitch->n_ports);
+    new_ports[lswitch->n_ports] = lport;
+    nbrec_logical_switch_set_ports(lswitch, new_ports, lswitch->n_ports + 1);
+    free(new_ports);
+}
+
+/* Removes lport 'lswitch->ports[idx]'. */
+static void
+remove_lport(const struct nbrec_logical_switch *lswitch, size_t idx)
+{
+    const struct nbrec_logical_port *lport = lswitch->ports[idx];
+
+    /* First remove 'lport' from the array of ports.  This is what will
+     * actually cause the logical port to be deleted when the transaction is
+     * sent to the database server (due to garbage collection). */
+    struct nbrec_logical_port **new_ports
+        = xmemdup(lswitch->ports, sizeof *new_ports * lswitch->n_ports);
+    new_ports[idx] = new_ports[lswitch->n_ports - 1];
+    nbrec_logical_switch_verify_ports(lswitch);
+    nbrec_logical_switch_set_ports(lswitch, new_ports, lswitch->n_ports - 1);
+    free(new_ports);
+
+    /* Delete 'lport' from the IDL.  This won't have a real effect on the
+     * database server (the IDL will suppress it in fact) but it means that it
+     * won't show up when we iterate with NBREC_LOGICAL_PORT_FOR_EACH later. */
+    nbrec_logical_port_delete(lport);
 }
 
 static void
@@ -273,46 +387,69 @@ do_lport_del(struct ovs_cmdl_context *ctx)
         return;
     }
 
-    nbrec_logical_port_delete(lport);
+    /* Find the switch that contains 'lport', then delete it. */
+    const struct nbrec_logical_switch *lswitch;
+    NBREC_LOGICAL_SWITCH_FOR_EACH (lswitch, nb_ctx->idl) {
+        for (size_t i = 0; i < lswitch->n_ports; i++) {
+            if (lswitch->ports[i] == lport) {
+                remove_lport(lswitch, i);
+                return;
+            }
+        }
+    }
+
+    VLOG_WARN("logical port %s is not part of any logical switch",
+              ctx->argv[1]);
 }
 
-static bool
-is_lswitch(const struct nbrec_logical_switch *lswitch,
-        struct uuid *lswitch_uuid, const char *name)
+static void
+do_lport_list(struct ovs_cmdl_context *ctx)
 {
-    if (lswitch_uuid) {
-        return uuid_equals(lswitch_uuid, &lswitch->header_.uuid);
-    } else {
-        return !strcmp(lswitch->name, name);
+    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);
+    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);
     }
 }
 
+static void
+do_lport_get_parent(struct ovs_cmdl_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]);
+    if (!lport) {
+        return;
+    }
+
+    if (lport->parent_name) {
+        printf("%s\n", lport->parent_name);
+    }
+}
 
 static void
-do_lport_list(struct ovs_cmdl_context *ctx)
+do_lport_get_tag(struct ovs_cmdl_context *ctx)
 {
     struct nbctl_context *nb_ctx = ctx->pvt;
-    const char *id = ctx->argv[1];
     const struct nbrec_logical_port *lport;
-    bool is_uuid = false;
-    struct uuid lswitch_uuid;
 
-    if (uuid_from_string(&lswitch_uuid, id)) {
-        is_uuid = true;
+    lport = lport_by_name_or_uuid(nb_ctx, ctx->argv[1]);
+    if (!lport) {
+        return;
     }
 
-    NBREC_LOGICAL_PORT_FOR_EACH(lport, nb_ctx->idl) {
-        bool match;
-        if (is_uuid) {
-            match = is_lswitch(lport->lswitch, &lswitch_uuid, NULL);
-        } else {
-            match = is_lswitch(lport->lswitch, NULL, id);
-        }
-        if (!match) {
-            continue;
-        }
-        printf(UUID_FMT " (%s)\n",
-               UUID_ARGS(&lport->header_.uuid), lport->name);
+    if (lport->n_tag > 0) {
+        printf("%"PRId64"\n", lport->tag[0]);
     }
 }
 
@@ -359,10 +496,8 @@ do_lport_get_external_id(struct ovs_cmdl_context *ctx)
         /* List one external ID */
 
         value = smap_get(&lport->external_ids, key);
-        if (value && *value) {
+        if (value) {
             printf("%s\n", value);
-        } else {
-            printf("external-id '%s' is not set.\n", key);
         }
     } else {
         struct smap_node *node;
@@ -408,6 +543,173 @@ do_lport_get_macs(struct ovs_cmdl_context *ctx)
         printf("%s\n", lport->macs[i]);
     }
 }
+
+static void
+do_lport_set_port_security(struct ovs_cmdl_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);
+    if (!lport) {
+        return;
+    }
+
+    nbrec_logical_port_set_port_security(lport,
+            (const char **) ctx->argv + 2, ctx->argc - 2);
+}
+
+static void
+do_lport_get_port_security(struct ovs_cmdl_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);
+    if (!lport) {
+        return;
+    }
+
+    for (i = 0; i < lport->n_port_security; i++) {
+        printf("%s\n", lport->port_security[i]);
+    }
+}
+
+static void
+do_lport_get_up(struct ovs_cmdl_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);
+    if (!lport) {
+        return;
+    }
+
+    printf("%s\n", (lport->up && *lport->up) ? "up" : "down");
+}
+
+static void
+do_lport_set_enabled(struct ovs_cmdl_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);
+    if (!lport) {
+        return;
+    }
+
+    if (!strcasecmp(state, "enabled")) {
+        bool enabled = true;
+        nbrec_logical_port_set_enabled(lport, &enabled, 1);
+    } else if (!strcasecmp(state, "disabled")) {
+        bool enabled = false;
+        nbrec_logical_port_set_enabled(lport, &enabled, 1);
+    } else {
+        VLOG_ERR("Invalid state '%s' provided to lport-set-enabled", state);
+    }
+}
+
+static void
+do_lport_get_enabled(struct ovs_cmdl_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);
+    if (!lport) {
+        return;
+    }
+
+    printf("%s\n",
+           (!lport->enabled || *lport->enabled) ? "enabled" : "disabled");
+}
+
+static void
+do_lport_set_type(struct ovs_cmdl_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);
+    if (!lport) {
+        return;
+    }
+
+    nbrec_logical_port_set_type(lport, type);
+}
+
+static void
+do_lport_get_type(struct ovs_cmdl_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);
+    if (!lport) {
+        return;
+    }
+
+    printf("%s\n", lport->type);
+}
+
+static void
+do_lport_set_options(struct ovs_cmdl_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);
+    if (!lport) {
+        return;
+    }
+
+    for (i = 2; i < ctx->argc; i++) {
+        char *key, *value;
+        value = xstrdup(ctx->argv[i]);
+        key = strsep(&value, "=");
+        if (value) {
+            smap_add(&options, key, value);
+        }
+        free(key);
+    }
+
+    nbrec_logical_port_set_options(lport, &options);
+
+    smap_destroy(&options);
+}
+
+static void
+do_lport_get_options(struct ovs_cmdl_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);
+    if (!lport) {
+        return;
+    }
+
+    SMAP_FOR_EACH(node, &lport->options) {
+        printf("%s=%s\n", node->key, node->value);
+    }
+}
 \f
 static void
 parse_options(int argc, char *argv[])
@@ -467,16 +769,23 @@ parse_options(int argc, char *argv[])
 }
 
 static const struct ovs_cmdl_command all_commands[] = {
+    {
+        .name = "show",
+        .usage = "[LSWITCH]",
+        .min_args = 0,
+        .max_args = 1,
+        .handler = do_show,
+    },
     {
         .name = "lswitch-add",
-        .usage = "[lswitch]",
+        .usage = "[LSWITCH]",
         .min_args = 0,
         .max_args = 1,
         .handler = do_lswitch_add,
     },
     {
         .name = "lswitch-del",
-        .usage = "<lswitch>",
+        .usage = "LSWITCH",
         .min_args = 1,
         .max_args = 1,
         .handler = do_lswitch_del,
@@ -490,56 +799,70 @@ static const struct ovs_cmdl_command all_commands[] = {
     },
     {
         .name = "lswitch-set-external-id",
-        .usage = "<lswitch> <key> [value]",
+        .usage = "LSWITCH KEY [VALUE]",
         .min_args = 2,
         .max_args = 3,
         .handler = do_lswitch_set_external_id,
     },
     {
         .name = "lswitch-get-external-id",
-        .usage = "<lswitch> [key]",
+        .usage = "LSWITCH [KEY]",
         .min_args = 1,
         .max_args = 2,
         .handler = do_lswitch_get_external_id,
     },
     {
         .name = "lport-add",
-        .usage = "<name> <lswitch>",
+        .usage = "LSWITCH LPORT [PARENT] [TAG]",
         .min_args = 2,
-        .max_args = 2,
+        .max_args = 4,
         .handler = do_lport_add,
     },
     {
         .name = "lport-del",
-        .usage = "<lport>",
+        .usage = "LPORT",
         .min_args = 1,
         .max_args = 1,
         .handler = do_lport_del,
     },
     {
         .name = "lport-list",
-        .usage = "<lswitch>",
+        .usage = "LSWITCH",
         .min_args = 1,
         .max_args = 1,
         .handler = do_lport_list,
     },
+    {
+        .name = "lport-get-parent",
+        .usage = "LPORT",
+        .min_args = 1,
+        .max_args = 1,
+        .handler = do_lport_get_parent,
+    },
+    {
+        .name = "lport-get-tag",
+        .usage = "LPORT",
+        .min_args = 1,
+        .max_args = 1,
+        .handler = do_lport_get_tag,
+    },
     {
         .name = "lport-set-external-id",
-        .usage = "<lport> <key> [value]",
+        .usage = "LPORT KEY [VALUE]",
         .min_args = 2,
         .max_args = 3,
         .handler = do_lport_set_external_id,
     },
     {
         .name = "lport-get-external-id",
-        .usage = "<lport> [key]",
+        .usage = "LPORT [KEY]",
         .min_args = 1,
         .max_args = 2,
         .handler = do_lport_get_external_id,
     },
     {
         .name = "lport-set-macs",
-        .usage = "<lport> [MAC] [MAC] [...]",
+        .usage = "LPORT [MAC]...",
         .min_args = 1,
         /* Accept however many arguments the system will allow. */
         .max_args = INT_MAX,
@@ -547,11 +870,75 @@ static const struct ovs_cmdl_command all_commands[] = {
     },
     {
         .name = "lport-get-macs",
-        .usage = "<lport>",
+        .usage = "LPORT",
         .min_args = 1,
         .max_args = 1,
         .handler = do_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 = do_lport_set_port_security,
+    },
+    {
+        .name = "lport-get-port-security",
+        .usage = "LPORT",
+        .min_args = 1,
+        .max_args = 1,
+        .handler = do_lport_get_port_security,
+    },
+    {
+        .name = "lport-get-up",
+        .usage = "LPORT",
+        .min_args = 1,
+        .max_args = 1,
+        .handler = do_lport_get_up,
+    },
+    {
+        .name = "lport-set-enabled",
+        .usage = "LPORT STATE",
+        .min_args = 2,
+        .max_args = 2,
+        .handler = do_lport_set_enabled,
+    },
+    {
+        .name = "lport-get-enabled",
+        .usage = "LPORT",
+        .min_args = 1,
+        .max_args = 1,
+        .handler = do_lport_get_enabled,
+    },
+    {
+        .name = "lport-set-type",
+        .usage = "LPORT TYPE",
+        .min_args = 2,
+        .max_args = 2,
+        .handler = do_lport_set_type,
+    },
+    {
+        .name = "lport-get-type",
+        .usage = "LPORT",
+        .min_args = 1,
+        .max_args = 1,
+        .handler = do_lport_get_type,
+    },
+    {
+        .name = "lport-set-options",
+        .usage = "LPORT KEY=VALUE [KEY=VALUE]...",
+        .min_args = 1,
+        .max_args = INT_MAX,
+        .handler = do_lport_set_options
+    },
+    {
+        .name = "lport-get-options",
+        .usage = "LPORT",
+        .min_args = 1,
+        .max_args = 1,
+        .handler = do_lport_get_options,
+    },
 
     {
         /* sentinel */
@@ -570,7 +957,10 @@ default_db(void)
 {
     static char *def;
     if (!def) {
-        def = xasprintf("unix:%s/db.sock", ovs_rundir());
+        def = getenv("OVN_NB_DB");
+        if (!def) {
+            def = xasprintf("unix:%s/db.sock", ovs_rundir());
+        }
     }
     return def;
 }
@@ -584,6 +974,7 @@ main(int argc, char *argv[])
     enum ovsdb_idl_txn_status txn_status;
     unsigned int seqno;
     int res = 0;
+    char *args;
 
     fatal_ignore_sigpipe();
     set_program_name(argv[0]);
@@ -592,6 +983,8 @@ main(int argc, char *argv[])
     parse_options(argc, argv);
     nbrec_init();
 
+    args = process_escape_args(argv);
+
     nb_ctx.idl = ovsdb_idl_create(db, &nbrec_idl_class, true, false);
     ctx.pvt = &nb_ctx;
     ctx.argc = argc - optind;
@@ -611,6 +1004,7 @@ main(int argc, char *argv[])
 
         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) {
@@ -632,6 +1026,7 @@ main(int argc, char *argv[])
         ovsdb_idl_txn_destroy(nb_ctx.txn);
     }
     ovsdb_idl_destroy(nb_ctx.idl);
+    free(args);
 
     exit(res);
 }