ovs-vsctl: Support references among records at creation time.
authorBen Pfaff <blp@nicira.com>
Wed, 2 Jun 2010 18:08:03 +0000 (11:08 -0700)
committerBen Pfaff <blp@nicira.com>
Thu, 17 Jun 2010 17:30:18 +0000 (10:30 -0700)
This makes it easy to create a bunch of records that are all related to
each other in a single ovs-vsctl invocation.  It adds an example to the
ovs-vsctl manpage.

lib/ovsdb-data.c
lib/ovsdb-data.h
lib/ovsdb-idl.c
lib/ovsdb-idl.h
lib/shash.c
lib/shash.h
ovsdb/ovsdb-idlc.in
tests/test-ovsdb.c
utilities/ovs-vsctl.8.in
utilities/ovs-vsctl.c

index 42fdf1e..ee98c80 100644 (file)
@@ -366,7 +366,7 @@ ovsdb_atom_to_json(const union ovsdb_atom *atom, enum ovsdb_atomic_type type)
 
 static char *
 ovsdb_atom_from_string__(union ovsdb_atom *atom, enum ovsdb_atomic_type type,
-                         const char *s)
+                         const char *s, struct ovsdb_symbol_table *symtab)
 {
     switch (type) {
     case OVSDB_TYPE_VOID:
@@ -427,7 +427,9 @@ ovsdb_atom_from_string__(union ovsdb_atom *atom, enum ovsdb_atomic_type type,
         break;
 
     case OVSDB_TYPE_UUID:
-        if (!uuid_from_string(&atom->uuid, s)) {
+        if (*s == '@') {
+            atom->uuid = ovsdb_symbol_table_insert(symtab, s)->uuid;
+        } else if (!uuid_from_string(&atom->uuid, s)) {
             return xasprintf("\"%s\" is not a valid UUID", s);
         }
         break;
@@ -454,19 +456,24 @@ ovsdb_atom_from_string__(union ovsdb_atom *atom, enum ovsdb_atomic_type type,
  *      - OVSDB_TYPE_STRING: A JSON string if it begins with a quote, otherwise
  *        an arbitrary string.
  *
- *      - OVSDB_TYPE_UUID: A UUID in RFC 4122 format.
+ *      - OVSDB_TYPE_UUID: A UUID in RFC 4122 format.  If 'symtab' is nonnull,
+ *        then an identifier beginning with '@' is also acceptable.  If the
+ *        named identifier is already in 'symtab', then the associated UUID is
+ *        used; otherwise, a new, random UUID is used and added to the symbol
+ *        table.
  *
  * Returns a null pointer if successful, otherwise an error message describing
  * the problem.  The caller is responsible for freeing the error.
  */
 char *
 ovsdb_atom_from_string(union ovsdb_atom *atom,
-                       const struct ovsdb_base_type *base, const char *s)
+                       const struct ovsdb_base_type *base, const char *s,
+                       struct ovsdb_symbol_table *symtab)
 {
     struct ovsdb_error *error;
     char *msg;
 
-    msg = ovsdb_atom_from_string__(atom, base->type, s);
+    msg = ovsdb_atom_from_string__(atom, base->type, s, symtab);
     if (msg) {
         return msg;
     }
@@ -1028,13 +1035,13 @@ skip_spaces(const char *p)
 
 static char *
 parse_atom_token(const char **s, const struct ovsdb_base_type *base,
-                 union ovsdb_atom *atom)
+                 union ovsdb_atom *atom, struct ovsdb_symbol_table *symtab)
 {
     char *token, *error;
 
     error = ovsdb_token_parse(s, &token);
     if (!error) {
-        error = ovsdb_atom_from_string(atom, base, token);
+        error = ovsdb_atom_from_string(atom, base, token, symtab);
         free(token);
     }
     return error;
@@ -1042,18 +1049,19 @@ parse_atom_token(const char **s, const struct ovsdb_base_type *base,
 
 static char *
 parse_key_value(const char **s, const struct ovsdb_type *type,
-                union ovsdb_atom *key, union ovsdb_atom *value)
+                union ovsdb_atom *key, union ovsdb_atom *value,
+                struct ovsdb_symbol_table *symtab)
 {
     const char *start = *s;
     char *error;
 
-    error = parse_atom_token(s, &type->key, key);
+    error = parse_atom_token(s, &type->key, key, symtab);
     if (!error && type->value.type != OVSDB_TYPE_VOID) {
         *s = skip_spaces(*s);
         if (**s == '=') {
             (*s)++;
             *s = skip_spaces(*s);
-            error = parse_atom_token(s, &type->value, value);
+            error = parse_atom_token(s, &type->value, value, symtab);
         } else {
             error = xasprintf("%s: syntax error at \"%c\" expecting \"=\"",
                               start, **s);
@@ -1080,10 +1088,14 @@ free_key_value(const struct ovsdb_type *type,
  * or, for a map, '='-delimited pairs of atoms.  Each atom must in a format
  * acceptable to ovsdb_atom_from_string().  Optionally, a set may be enclosed
  * in "[]" or a map in "{}"; for an empty set or map these punctuators are
- * required. */
+ * required.
+ *
+ * Optionally, a symbol table may be supplied as 'symtab'.  It is passed to
+ * ovsdb_atom_to_string(). */
 char *
 ovsdb_datum_from_string(struct ovsdb_datum *datum,
-                        const struct ovsdb_type *type, const char *s)
+                        const struct ovsdb_type *type, const char *s,
+                        struct ovsdb_symbol_table *symtab)
 {
     bool is_map = ovsdb_type_is_map(type);
     struct ovsdb_error *dberror;
@@ -1118,7 +1130,7 @@ ovsdb_datum_from_string(struct ovsdb_datum *datum,
         }
 
         /* Add to datum. */
-        error = parse_key_value(&p, type, &key, &value);
+        error = parse_key_value(&p, type, &key, &value, symtab);
         if (error) {
             goto error;
         }
@@ -1549,6 +1561,21 @@ ovsdb_symbol_table_insert(struct ovsdb_symbol_table *symtab,
     }
     return symbol;
 }
+
+const char *
+ovsdb_symbol_table_find_unused(const struct ovsdb_symbol_table *symtab)
+{
+    struct shash_node *node;
+
+    SHASH_FOR_EACH (node, &symtab->sh) {
+        struct ovsdb_symbol *symbol = node->data;
+        if (!symbol->used) {
+            return node->name;
+        }
+    }
+
+    return NULL;
+}
 \f
 /* Extracts a token from the beginning of 's' and returns a pointer just after
  * the token.  Stores the token itself into '*outp', which the caller is
index a5c49f9..e12153c 100644 (file)
@@ -75,7 +75,8 @@ struct json *ovsdb_atom_to_json(const union ovsdb_atom *,
                                 enum ovsdb_atomic_type);
 
 char *ovsdb_atom_from_string(union ovsdb_atom *,
-                             const struct ovsdb_base_type *, const char *)
+                             const struct ovsdb_base_type *, const char *,
+                             struct ovsdb_symbol_table *)
     WARN_UNUSED_RESULT;
 void ovsdb_atom_to_string(const union ovsdb_atom *, enum ovsdb_atomic_type,
                           struct ds *);
@@ -142,7 +143,8 @@ struct json *ovsdb_datum_to_json(const struct ovsdb_datum *,
                                  const struct ovsdb_type *);
 
 char *ovsdb_datum_from_string(struct ovsdb_datum *,
-                             const struct ovsdb_type *, const char *)
+                              const struct ovsdb_type *, const char *,
+                              struct ovsdb_symbol_table *)
     WARN_UNUSED_RESULT;
 void ovsdb_datum_to_string(const struct ovsdb_datum *,
                            const struct ovsdb_type *, struct ds *);
@@ -216,6 +218,7 @@ struct ovsdb_symbol *ovsdb_symbol_table_put(struct ovsdb_symbol_table *,
                                             const struct uuid *, bool used);
 struct ovsdb_symbol *ovsdb_symbol_table_insert(struct ovsdb_symbol_table *,
                                                const char *name);
+const char *ovsdb_symbol_table_find_unused(const struct ovsdb_symbol_table *);
 \f
 /* Tokenization
  *
index 94dd466..ca77cc2 100644 (file)
@@ -1460,10 +1460,18 @@ ovsdb_idl_txn_delete(const struct ovsdb_idl_row *row_)
 
 const struct ovsdb_idl_row *
 ovsdb_idl_txn_insert(struct ovsdb_idl_txn *txn,
-                     const struct ovsdb_idl_table_class *class)
+                     const struct ovsdb_idl_table_class *class,
+                     const struct uuid *uuid)
 {
     struct ovsdb_idl_row *row = ovsdb_idl_row_create__(class);
-    uuid_generate(&row->uuid);
+
+    if (uuid) {
+        assert(!ovsdb_idl_txn_get_row(txn, uuid));
+        row->uuid = *uuid;
+    } else {
+        uuid_generate(&row->uuid);
+    }
+
     row->table = ovsdb_idl_table_from_class(txn->idl, class);
     row->new = xmalloc(class->n_columns * sizeof *row->new);
     row->written = bitmap_allocate(class->n_columns);
index 2aaaa71..31fae98 100644 (file)
@@ -84,7 +84,8 @@ void ovsdb_idl_txn_write(const struct ovsdb_idl_row *,
                          struct ovsdb_datum *);
 void ovsdb_idl_txn_delete(const struct ovsdb_idl_row *);
 const struct ovsdb_idl_row *ovsdb_idl_txn_insert(
-    struct ovsdb_idl_txn *, const struct ovsdb_idl_table_class *);
+    struct ovsdb_idl_txn *, const struct ovsdb_idl_table_class *,
+    const struct uuid *);
 
 struct ovsdb_idl *ovsdb_idl_txn_get_idl (struct ovsdb_idl_txn *);
 
index caac460..1a85b79 100644 (file)
@@ -79,15 +79,23 @@ shash_count(const struct shash *shash)
 /* It is the caller's responsibility to avoid duplicate names, if that is
  * desirable. */
 struct shash_node *
-shash_add(struct shash *sh, const char *name, const void *data)
+shash_add_nocopy(struct shash *sh, char *name, const void *data)
 {
     struct shash_node *node = xmalloc(sizeof *node);
-    node->name = xstrdup(name);
+    node->name = name;
     node->data = (void *) data;
     hmap_insert(&sh->map, &node->node, hash_name(name));
     return node;
 }
 
+/* It is the caller's responsibility to avoid duplicate names, if that is
+ * desirable. */
+struct shash_node *
+shash_add(struct shash *sh, const char *name, const void *data)
+{
+    return shash_add_nocopy(sh, xstrdup(name), data);
+}
+
 bool
 shash_add_once(struct shash *sh, const char *name, const void *data)
 {
index 38d0dbb..de85045 100644 (file)
@@ -50,6 +50,7 @@ void shash_clear(struct shash *);
 bool shash_is_empty(const struct shash *);
 size_t shash_count(const struct shash *);
 struct shash_node *shash_add(struct shash *, const char *, const void *);
+struct shash_node *shash_add_nocopy(struct shash *, char *, const void *);
 bool shash_add_once(struct shash *, const char *, const void *);
 void shash_add_assert(struct shash *, const char *, const void *);
 void shash_delete(struct shash *, struct shash_node *);
index c89048e..c010348 100755 (executable)
@@ -378,7 +378,7 @@ void
 struct %(s)s *
 %(s)s_insert(struct ovsdb_idl_txn *txn)
 {
-    return %(s)s_cast(ovsdb_idl_txn_insert(txn, &%(p)stable_classes[%(P)sTABLE_%(T)s]));
+    return %(s)s_cast(ovsdb_idl_txn_insert(txn, &%(p)stable_classes[%(P)sTABLE_%(T)s], NULL));
 }
 ''' % {'s': structName,
        'p': prefix,
index 672759b..21825d7 100644 (file)
@@ -396,7 +396,7 @@ do_parse_atom_strings(int argc, char *argv[])
         union ovsdb_atom atom;
         struct ds out;
 
-        die_if_error(ovsdb_atom_from_string(&atom, &base, argv[i]));
+        die_if_error(ovsdb_atom_from_string(&atom, &base, argv[i], NULL));
 
         ds_init(&out);
         ovsdb_atom_to_string(&atom, base.type, &out);
@@ -448,7 +448,7 @@ do_parse_data_strings(int argc, char *argv[])
         struct ovsdb_datum datum;
         struct ds out;
 
-        die_if_error(ovsdb_datum_from_string(&datum, &type, argv[i]));
+        die_if_error(ovsdb_datum_from_string(&datum, &type, argv[i], NULL));
 
         ds_init(&out);
         ovsdb_datum_to_string(&datum, &type, &out);
index ac4cf45..ab487ad 100644 (file)
@@ -487,8 +487,10 @@ the syntax is that of strings in JSON, e.g. backslashes may be used to
 escape special characters.  The empty string must be represented as a
 pair of double quotes (\fB""\fR).
 .IP "UUID"
-A universally unique identifier in the style of RFC 4122,
-e.g. \fBf81d4fae-7dec-11d0-a765-00a0c91e6bf6\fR.
+Either a universally unique identifier in the style of RFC 4122,
+e.g. \fBf81d4fae\-7dec\-11d0\-a765\-00a0c91e6bf6\fR, or an \fB@\fIname\fR
+defined by the \fBcreate\fR command within the same \fBovs\-vsctl\fR
+invocation.
 .PP
 Multiple values in a single column may be separated by spaces or a
 single comma.  When multiple values are present, duplicates are not
@@ -559,10 +561,15 @@ Sets each \fIcolumn\fR in \fIrecord\fR in \fItable\fR to the empty set
 or empty map, as appropriate.  This command applies only to columns
 that are allowed to be empty.
 .
-.IP "\fBcreate\fR \fItable column\fR[\fB:\fIkey\fR]\fB=\fIvalue\fR..."
+.IP "[\fB--id=@\fIname\fR] \fBcreate\fR \fItable column\fR[\fB:\fIkey\fR]\fB=\fIvalue\fR..."
 Creates a new record in \fItable\fR and sets the initial values of
 each \fIcolumn\fR.  Columns not explicitly set will receive their
 default values.  Outputs the UUID of the new row.
+.IP
+If \fB@\fIname\fR is specified, then the UUID for the new row may be
+referred to by that name elsewhere in the same \fBovs\-vsctl\fR
+invocation in contexts where a UUID is expected.  Such references may
+precede or follow the \fBcreate\fR command.
 .
 .IP "\fR[\fB\-\-if\-exists\fR] \fBdestroy \fItable record\fR..."
 Deletes each specified \fIrecord\fR from \fItable\fR.  Unless
@@ -586,6 +593,12 @@ Delete bridge \fBbr0\fR if it exists (the \fB\-\-\fR is required to
 separate \fBdel\-br\fR's options from the global options):
 .IP
 .B "ovs\-vsctl \-\- \-\-if\-exists del\-br br0"
+.PP
+Set the \fBqos\fR column of the \fBPort\fR record for \fBeth0\fR to
+point to a new \fBQoS\fR record, which in turn points with its queue 0
+to a new \fBQueue\fR record:
+.IP
+.B "ovs-vsctl \-\- set port eth0 qos=@newqos \-\- \-\-id=@newqos create qos type=linux\-htb other\-config:max\-rate=1000000 queues:0=@newqueue \-\- \-\-id=@newqueue create queue other\-config:min\-rate=1000000 other\-config:max\-rate=1000000"
 .
 .SH "EXIT STATUS"
 .IP "0"
@@ -599,3 +612,4 @@ bridge that does not exist.
 .
 .BR ovsdb\-server (1),
 .BR ovs\-vswitchd (8).
+\
index 9b40197..76a3831 100644 (file)
@@ -111,7 +111,8 @@ static void do_vsctl(const char *args,
 
 static const struct vsctl_table_class *get_table(const char *table_name);
 static void set_column(const struct vsctl_table_class *,
-                       const struct ovsdb_idl_row *, const char *arg);
+                       const struct ovsdb_idl_row *, const char *arg,
+                       struct ovsdb_symbol_table *);
 
 
 int
@@ -511,6 +512,7 @@ struct vsctl_context {
     struct ds output;
     struct ovsdb_idl *idl;
     struct ovsdb_idl_txn *txn;
+    struct ovsdb_symbol_table *symtab;
     const struct ovsrec_open_vswitch *ovs;
 };
 
@@ -1384,7 +1386,8 @@ add_port(struct vsctl_context *ctx,
     }
 
     for (i = 0; i < n_settings; i++) {
-        set_column(get_table("Port"), &port->header_, settings[i]);
+        set_column(get_table("Port"), &port->header_, settings[i],
+                   ctx->symtab);
     }
 
     bridge_insert_port((bridge->parent ? bridge->parent->br_cfg
@@ -2229,7 +2232,7 @@ cmd_get(struct vsctl_context *ctx)
 
             die_if_error(ovsdb_atom_from_string(&key,
                                                 &column->type.key,
-                                                key_string));
+                                                key_string, ctx->symtab));
 
             idx = ovsdb_datum_find_key(&datum, &key,
                                        column->type.key.type);
@@ -2309,7 +2312,8 @@ cmd_list(struct vsctl_context *ctx)
 
 static void
 set_column(const struct vsctl_table_class *table,
-           const struct ovsdb_idl_row *row, const char *arg)
+           const struct ovsdb_idl_row *row, const char *arg,
+           struct ovsdb_symbol_table *symtab)
 {
     const struct ovsdb_idl_column *column;
     char *key_string, *value_string;
@@ -2332,9 +2336,9 @@ set_column(const struct vsctl_table_class *table,
         }
 
         die_if_error(ovsdb_atom_from_string(&key, &column->type.key,
-                                            key_string));
+                                            key_string, symtab));
         die_if_error(ovsdb_atom_from_string(&value, &column->type.value,
-                                            value_string));
+                                            value_string, symtab));
 
         ovsdb_datum_init_empty(&new);
         ovsdb_datum_add_unsafe(&new, &key, &value, &column->type);
@@ -2351,7 +2355,7 @@ set_column(const struct vsctl_table_class *table,
         struct ovsdb_datum datum;
 
         die_if_error(ovsdb_datum_from_string(&datum, &column->type,
-                                             value_string));
+                                             value_string, symtab));
         ovsdb_idl_txn_write(row, column, &datum);
     }
 
@@ -2371,7 +2375,7 @@ cmd_set(struct vsctl_context *ctx)
     table = get_table(table_name);
     row = must_get_row(ctx, table, record_id);
     for (i = 3; i < ctx->argc; i++) {
-        set_column(table, row, ctx->argv[i]);
+        set_column(table, row, ctx->argv[i], ctx->symtab);
     }
 }
 
@@ -2401,7 +2405,8 @@ cmd_add(struct vsctl_context *ctx)
         add_type = *type;
         add_type.n_min = 1;
         add_type.n_max = UINT_MAX;
-        die_if_error(ovsdb_datum_from_string(&add, &add_type, ctx->argv[i]));
+        die_if_error(ovsdb_datum_from_string(&add, &add_type, ctx->argv[i],
+                                             ctx->symtab));
         ovsdb_datum_union(&old, &add, type, false);
         ovsdb_datum_destroy(&add, type);
     }
@@ -2442,11 +2447,13 @@ cmd_remove(struct vsctl_context *ctx)
         rm_type = *type;
         rm_type.n_min = 1;
         rm_type.n_max = UINT_MAX;
-        error = ovsdb_datum_from_string(&rm, &rm_type, ctx->argv[i]);
+        error = ovsdb_datum_from_string(&rm, &rm_type,
+                                        ctx->argv[i], ctx->symtab);
         if (error && ovsdb_type_is_map(&rm_type)) {
             free(error);
             rm_type.value.type = OVSDB_TYPE_VOID;
-            die_if_error(ovsdb_datum_from_string(&rm, &rm_type, ctx->argv[i]));
+            die_if_error(ovsdb_datum_from_string(&rm, &rm_type,
+                                                 ctx->argv[i], ctx->symtab));
         }
         ovsdb_datum_subtract(&old, type, &rm, &rm_type);
         ovsdb_datum_destroy(&rm, &rm_type);
@@ -2494,15 +2501,36 @@ cmd_clear(struct vsctl_context *ctx)
 static void
 cmd_create(struct vsctl_context *ctx)
 {
+    const char *id = shash_find_data(&ctx->options, "--id");
     const char *table_name = ctx->argv[1];
     const struct vsctl_table_class *table;
     const struct ovsdb_idl_row *row;
+    const struct uuid *uuid;
     int i;
 
+    if (id) {
+        struct ovsdb_symbol *symbol;
+
+        if (id[0] != '@') {
+            vsctl_fatal("row id \"%s\" does not begin with \"@\"", id);
+        }
+
+        symbol = ovsdb_symbol_table_insert(ctx->symtab, id);
+        if (symbol->used) {
+            vsctl_fatal("row id \"%s\" may only be used to insert a single "
+                        "row", id);
+        }
+        symbol->used = true;
+
+        uuid = &symbol->uuid;
+    } else {
+        uuid = NULL;
+    }
+
     table = get_table(table_name);
-    row = ovsdb_idl_txn_insert(ctx->txn, table->class);
+    row = ovsdb_idl_txn_insert(ctx->txn, table->class, uuid);
     for (i = 2; i < ctx->argc; i++) {
-        set_column(table, row, ctx->argv[i]);
+        set_column(table, row, ctx->argv[i], ctx->symtab);
     }
     ds_put_format(&ctx->output, UUID_FMT, UUID_ARGS(&row->uuid));
 }
@@ -2567,7 +2595,8 @@ where_uuid_equals(const struct uuid *uuid)
 static void
 vsctl_context_init(struct vsctl_context *ctx, struct vsctl_command *command,
                    struct ovsdb_idl *idl, struct ovsdb_idl_txn *txn,
-                   const struct ovsrec_open_vswitch *ovs)
+                   const struct ovsrec_open_vswitch *ovs,
+    struct ovsdb_symbol_table *symtab)
 {
     ctx->argc = command->argc;
     ctx->argv = command->argv;
@@ -2577,7 +2606,7 @@ vsctl_context_init(struct vsctl_context *ctx, struct vsctl_command *command,
     ctx->idl = idl;
     ctx->txn = txn;
     ctx->ovs = ovs;
-
+    ctx->symtab = symtab;
 }
 
 static void
@@ -2593,6 +2622,8 @@ do_vsctl(const char *args, struct vsctl_command *commands, size_t n_commands,
     struct ovsdb_idl_txn *txn;
     const struct ovsrec_open_vswitch *ovs;
     enum ovsdb_idl_txn_status status;
+    struct ovsdb_symbol_table *symtab;
+    const char *unused;
     struct vsctl_command *c;
     int64_t next_cfg = 0;
     char *error;
@@ -2616,11 +2647,12 @@ do_vsctl(const char *args, struct vsctl_command *commands, size_t n_commands,
         json_destroy(where);
     }
 
+    symtab = ovsdb_symbol_table_create();
     for (c = commands; c < &commands[n_commands]; c++) {
         struct vsctl_context ctx;
 
         ds_init(&c->output);
-        vsctl_context_init(&ctx, c, idl, txn, ovs);
+        vsctl_context_init(&ctx, c, idl, txn, ovs, symtab);
         (c->syntax->run)(&ctx);
         vsctl_context_done(&ctx, c);
     }
@@ -2634,7 +2666,7 @@ do_vsctl(const char *args, struct vsctl_command *commands, size_t n_commands,
             if (c->syntax->postprocess) {
                 struct vsctl_context ctx;
 
-                vsctl_context_init(&ctx, c, idl, txn, ovs);
+                vsctl_context_init(&ctx, c, idl, txn, ovs, symtab);
                 (c->syntax->postprocess)(&ctx);
                 vsctl_context_done(&ctx, c);
             }
@@ -2644,6 +2676,13 @@ do_vsctl(const char *args, struct vsctl_command *commands, size_t n_commands,
     ovsdb_idl_txn_destroy(txn);
     the_idl_txn = NULL;
 
+    unused = ovsdb_symbol_table_find_unused(symtab);
+    if (unused) {
+        vsctl_fatal("row id \"%s\" is referenced but never created (e.g. "
+                    "with \"-- --id=%s create ...\")", unused, unused);
+    }
+    ovsdb_symbol_table_destroy(symtab);
+
     switch (status) {
     case TXN_INCOMPLETE:
         NOT_REACHED();
@@ -2673,6 +2712,8 @@ do_vsctl(const char *args, struct vsctl_command *commands, size_t n_commands,
 
     for (c = commands; c < &commands[n_commands]; c++) {
         struct ds *ds = &c->output;
+        struct shash_node *node;
+
         if (oneline) {
             size_t j;
 
@@ -2697,6 +2738,10 @@ do_vsctl(const char *args, struct vsctl_command *commands, size_t n_commands,
             fputs(ds_cstr(ds), stdout);
         }
         ds_destroy(&c->output);
+
+        SHASH_FOR_EACH (node, &c->options) {
+            free(node->data);
+        }
         shash_destroy(&c->options);
     }
     free(commands);
@@ -2769,7 +2814,7 @@ static const struct vsctl_command_syntax all_commands[] = {
     {"add", 4, INT_MAX, cmd_add, NULL, ""},
     {"remove", 4, INT_MAX, cmd_remove, NULL, ""},
     {"clear", 3, INT_MAX, cmd_clear, NULL, ""},
-    {"create", 2, INT_MAX, cmd_create, post_create, ""},
+    {"create", 2, INT_MAX, cmd_create, post_create, "--id="},
     {"destroy", 1, INT_MAX, cmd_destroy, NULL, "--if-exists"},
 
     {NULL, 0, 0, NULL, NULL, NULL},