ovsdb: Implement C bindings for IDL.
authorBen Pfaff <blp@nicira.com>
Wed, 2 Dec 2009 19:26:15 +0000 (11:26 -0800)
committerBen Pfaff <blp@nicira.com>
Wed, 2 Dec 2009 19:26:15 +0000 (11:26 -0800)
16 files changed:
Makefile.am
lib/automake.mk
lib/ovsdb-idl-provider.h [new file with mode: 0644]
lib/ovsdb-idl.c [new file with mode: 0644]
lib/ovsdb-idl.h [new file with mode: 0644]
ovsdb/automake.mk
ovsdb/ovsdb-idlc.1
ovsdb/ovsdb-idlc.in
tests/automake.mk
tests/idltest.ovsidl [new file with mode: 0644]
tests/ovsdb-idl.at [new file with mode: 0644]
tests/ovsdb.at
tests/test-ovsdb.c
vswitchd/automake.mk
vswitchd/vswitch-idl.ovsidl [new file with mode: 0644]
vswitchd/vswitch.ovsidl [deleted file]

index 4229cb1dfcc72451691e211f38b1e373cd0f3600..301fc7c146bec83329bd9cceda890e11bbc25473 100644 (file)
@@ -41,16 +41,18 @@ dist_man_MANS =
 dist_pkgdata_SCRIPTS =
 dist_sbin_SCRIPTS =
 man_MANS =
+noinst_DATA =
 noinst_HEADERS =
 noinst_LIBRARIES =
 noinst_PROGRAMS =
 noinst_SCRIPTS =
+SUFFIXES =
 
 EXTRA_DIST += soexpand.pl
 
 ro_c = echo '/* -*- mode: c; buffer-read-only: t -*- */'
 
-SUFFIXES = .in
+SUFFIXES += .in
 .in:
        $(PERL) $(srcdir)/soexpand.pl -I$(srcdir) < $< | \
            sed \
index 406b4240b842f32135340a42703d707c983a09c6..ee5a1956d4a5ff1cd860b6de594b76494b35b1ee 100644 (file)
@@ -81,6 +81,9 @@ lib_libopenvswitch_a_SOURCES = \
        lib/ovsdb-data.h \
        lib/ovsdb-error.c \
        lib/ovsdb-error.h \
+       lib/ovsdb-idl-provider.h \
+       lib/ovsdb-idl.c \
+       lib/ovsdb-idl.h \
        lib/ovsdb-parser.c \
        lib/ovsdb-parser.h \
        lib/ovsdb-types.c \
diff --git a/lib/ovsdb-idl-provider.h b/lib/ovsdb-idl-provider.h
new file mode 100644 (file)
index 0000000..76197e8
--- /dev/null
@@ -0,0 +1,71 @@
+/* Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_IDL_PROVIDER_H
+#define OVSDB_IDL_PROVIDER_H 1
+
+#include "hmap.h"
+#include "list.h"
+#include "ovsdb-idl.h"
+#include "ovsdb-types.h"
+#include "shash.h"
+#include "uuid.h"
+
+struct ovsdb_idl_row {
+    struct hmap_node hmap_node; /* In struct ovsdb_idl_table's 'rows'. */
+    struct uuid uuid;           /* Row "_uuid" field. */
+    struct list src_arcs;       /* Forward arcs (ovsdb_idl_arc.src_node). */
+    struct list dst_arcs;       /* Backward arcs (ovsdb_idl_arc.dst_node). */
+    struct ovsdb_idl_table *table; /* Containing table. */
+    struct ovsdb_datum *fields;    /* Row data, or null if orphaned. */
+};
+
+struct ovsdb_idl_column {
+    char *name;
+    struct ovsdb_type type;
+};
+
+struct ovsdb_idl_table_class {
+    char *name;
+    const struct ovsdb_idl_column *columns;
+    size_t n_columns;
+    size_t allocation_size;
+    void (*parse)(struct ovsdb_idl_row *);
+    void (*unparse)(struct ovsdb_idl_row *);
+};
+
+struct ovsdb_idl_table {
+    const struct ovsdb_idl_table_class *class;
+    struct shash columns;    /* Contains "const struct ovsdb_idl_column *"s. */
+    struct hmap rows;        /* Contains "struct ovsdb_idl_row"s. */
+    struct ovsdb_idl *idl;   /* Containing idl. */
+};
+
+struct ovsdb_idl_class {
+    const struct ovsdb_idl_table_class *tables;
+    size_t n_tables;
+};
+
+struct ovsdb_idl_row *ovsdb_idl_get_row_arc(
+    struct ovsdb_idl_row *src,
+    struct ovsdb_idl_table_class *dst_table,
+    const struct uuid *dst_uuid);
+
+struct ovsdb_idl_row *ovsdb_idl_first_row(
+    const struct ovsdb_idl *, const struct ovsdb_idl_table_class *);
+
+struct ovsdb_idl_row *ovsdb_idl_next_row(const struct ovsdb_idl_row *);
+
+#endif /* ovsdb-idl-provider.h */
diff --git a/lib/ovsdb-idl.c b/lib/ovsdb-idl.c
new file mode 100644 (file)
index 0000000..26b5942
--- /dev/null
@@ -0,0 +1,691 @@
+/* Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "ovsdb-idl.h"
+
+#include <assert.h>
+#include <limits.h>
+#include <stdlib.h>
+
+#include "json.h"
+#include "jsonrpc.h"
+#include "ovsdb-data.h"
+#include "ovsdb-error.h"
+#include "ovsdb-idl-provider.h"
+#include "shash.h"
+#include "util.h"
+
+#define THIS_MODULE VLM_ovsdb_idl
+#include "vlog.h"
+
+/* An arc from one idl_row to another.  When row A contains a UUID that
+ * references row B, this is represented by an arc from A (the source) to B
+ * (the destination).
+ *
+ * Arcs from a row to itself are omitted, that is, src and dst are always
+ * different.
+ *
+ * Arcs are never duplicated, that is, even if there are multiple references
+ * from A to B, there is only a single arc from A to B.
+ *
+ * Arcs are directed: an arc from A to B is the converse of an an arc from B to
+ * A.  Both an arc and its converse may both be present, if each row refers
+ * to the other circularly.
+ *
+ * The source and destination row may be in the same table or in different
+ * tables.
+ */
+struct ovsdb_idl_arc {
+    struct list src_node;       /* In src->src_arcs list. */
+    struct list dst_node;       /* In dst->dst_arcs list. */
+    struct ovsdb_idl_row *src;  /* Source row. */
+    struct ovsdb_idl_row *dst;  /* Destination row. */
+};
+
+struct ovsdb_idl {
+    struct jsonrpc_session *session;
+    struct shash tables;
+    struct json *monitor_request_id;
+    unsigned int last_monitor_request_seqno;
+    unsigned int change_seqno;
+};
+
+static struct vlog_rate_limit syntax_rl = VLOG_RATE_LIMIT_INIT(1, 5);
+static struct vlog_rate_limit semantic_rl = VLOG_RATE_LIMIT_INIT(1, 5);
+
+static void ovsdb_idl_clear(struct ovsdb_idl *);
+static void ovsdb_idl_send_monitor_request(struct ovsdb_idl *);
+static void ovsdb_idl_parse_update(struct ovsdb_idl *, const struct json *);
+static struct ovsdb_error *ovsdb_idl_parse_update__(struct ovsdb_idl *,
+                                                    const struct json *);
+static void ovsdb_idl_process_update(struct ovsdb_idl_table *,
+                                     const struct uuid *,
+                                     const struct json *old,
+                                     const struct json *new);
+static void ovsdb_idl_insert_row(struct ovsdb_idl_row *, const struct json *);
+static void ovsdb_idl_delete_row(struct ovsdb_idl_row *);
+static void ovsdb_idl_modify_row(struct ovsdb_idl_row *, const struct json *);
+
+static bool ovsdb_idl_row_is_orphan(const struct ovsdb_idl_row *);
+static struct ovsdb_idl_row *ovsdb_idl_row_create(struct ovsdb_idl_table *,
+                                                  const struct uuid *);
+static void ovsdb_idl_row_destroy(struct ovsdb_idl_row *);
+
+static void ovsdb_idl_row_clear_fields(struct ovsdb_idl_row *);
+
+struct ovsdb_idl *
+ovsdb_idl_create(const char *remote, const struct ovsdb_idl_class *class)
+{
+    struct ovsdb_idl *idl;
+    size_t i;
+
+    idl = xzalloc(sizeof *idl);
+    idl->session = jsonrpc_session_open(remote);
+    shash_init(&idl->tables);
+    for (i = 0; i < class->n_tables; i++) {
+        const struct ovsdb_idl_table_class *tc = &class->tables[i];
+        struct ovsdb_idl_table *table;
+        size_t j;
+
+        table = xmalloc(sizeof *table);
+        assert(!shash_find(&idl->tables, tc->name));
+        shash_add(&idl->tables, tc->name, table);
+        table->class = tc;
+        shash_init(&table->columns);
+        for (j = 0; j < tc->n_columns; j++) {
+            const struct ovsdb_idl_column *column = &tc->columns[j];
+
+            assert(!shash_find(&table->columns, column->name));
+            shash_add(&table->columns, column->name, column);
+        }
+        hmap_init(&table->rows);
+        table->idl = idl;
+    }
+    idl->last_monitor_request_seqno = UINT_MAX;
+
+    return idl;
+}
+
+void
+ovsdb_idl_destroy(struct ovsdb_idl *idl)
+{
+    if (idl) {
+        struct shash_node *node;
+
+        ovsdb_idl_clear(idl);
+        jsonrpc_session_close(idl->session);
+
+        SHASH_FOR_EACH (node, &idl->tables) {
+            struct ovsdb_idl_table *table = node->data;
+
+            shash_destroy(&table->columns);
+            hmap_destroy(&table->rows);
+        }
+        shash_destroy(&idl->tables);
+        json_destroy(idl->monitor_request_id);
+        free(idl);
+    }
+}
+
+static void
+ovsdb_idl_clear(struct ovsdb_idl *idl)
+{
+    struct shash_node *node;
+    bool changed = false;
+
+    SHASH_FOR_EACH (node, &idl->tables) {
+        struct ovsdb_idl_table *table = node->data;
+        struct ovsdb_idl_row *row, *next_row;
+
+        if (hmap_is_empty(&table->rows)) {
+            continue;
+        }
+
+        changed = true;
+        HMAP_FOR_EACH_SAFE (row, next_row, struct ovsdb_idl_row, hmap_node,
+                            &table->rows) {
+            struct ovsdb_idl_arc *arc, *next_arc;
+
+            if (!ovsdb_idl_row_is_orphan(row)) {
+                (row->table->class->unparse)(row);
+                ovsdb_idl_row_clear_fields(row);
+            }
+            hmap_remove(&table->rows, &row->hmap_node);
+            LIST_FOR_EACH_SAFE (arc, next_arc, struct ovsdb_idl_arc, src_node,
+                                &row->src_arcs) {
+                free(arc);
+            }
+            /* No need to do anything with dst_arcs: some node has those arcs
+             * as forward arcs and will destroy them itself. */
+
+            free(row);
+        }
+    }
+
+    if (changed) {
+        idl->change_seqno++;
+    }
+}
+
+void
+ovsdb_idl_run(struct ovsdb_idl *idl)
+{
+    int i;
+
+    jsonrpc_session_run(idl->session);
+    for (i = 0; jsonrpc_session_is_connected(idl->session) && i < 50; i++) {
+        struct jsonrpc_msg *msg, *reply;
+        unsigned int seqno;
+
+        seqno = jsonrpc_session_get_seqno(idl->session);
+        if (idl->last_monitor_request_seqno != seqno) {
+            idl->last_monitor_request_seqno = seqno;
+            ovsdb_idl_send_monitor_request(idl);
+            break;
+        }
+
+        msg = jsonrpc_session_recv(idl->session);
+        if (!msg) {
+            break;
+        }
+
+        reply = NULL;
+        if (msg->type == JSONRPC_REQUEST && !strcmp(msg->method, "echo")) {
+            reply = jsonrpc_create_reply(json_clone(msg->params), msg->id);
+        } else if (msg->type == JSONRPC_NOTIFY
+                   && !strcmp(msg->method, "update")
+                   && msg->params->type == JSON_ARRAY
+                   && msg->params->u.array.n == 2
+                   && msg->params->u.array.elems[0]->type == JSON_NULL) {
+            ovsdb_idl_parse_update(idl, msg->params->u.array.elems[1]);
+        } else if (msg->type == JSONRPC_REPLY
+                   && idl->monitor_request_id
+                   && json_equal(idl->monitor_request_id, msg->id)) {
+            json_destroy(idl->monitor_request_id);
+            idl->monitor_request_id = NULL;
+            ovsdb_idl_clear(idl);
+            ovsdb_idl_parse_update(idl, msg->result);
+        } else if (msg->type == JSONRPC_REPLY
+                   && msg->id && msg->id->type == JSON_STRING
+                   && !strcmp(msg->id->u.string, "echo")) {
+            /* It's a reply to our echo request.  Ignore it. */
+        } else {
+            VLOG_WARN("%s: received unexpected %s message",
+                      jsonrpc_session_get_name(idl->session),
+                      jsonrpc_msg_type_to_string(msg->type));
+            jsonrpc_session_force_reconnect(idl->session);
+        }
+        if (reply) {
+            jsonrpc_session_send(idl->session, reply);
+        }
+        jsonrpc_msg_destroy(msg);
+    }
+}
+
+void
+ovsdb_idl_wait(struct ovsdb_idl *idl)
+{
+    jsonrpc_session_wait(idl->session);
+    jsonrpc_session_recv_wait(idl->session);
+}
+
+unsigned int
+ovsdb_idl_get_seqno(const struct ovsdb_idl *idl)
+{
+    return idl->change_seqno;
+}
+
+void
+ovsdb_idl_force_reconnect(struct ovsdb_idl *idl)
+{
+    jsonrpc_session_force_reconnect(idl->session);
+}
+\f
+static void
+ovsdb_idl_send_monitor_request(struct ovsdb_idl *idl)
+{
+    struct json *monitor_requests;
+    const struct shash_node *node;
+    struct jsonrpc_msg *msg;
+
+    monitor_requests = json_object_create();
+    SHASH_FOR_EACH (node, &idl->tables) {
+        const struct ovsdb_idl_table *table = node->data;
+        const struct ovsdb_idl_table_class *tc = table->class;
+        struct json *monitor_request, *columns;
+        size_t i;
+
+        monitor_request = json_object_create();
+        columns = json_array_create_empty();
+        for (i = 0; i < tc->n_columns; i++) {
+            const struct ovsdb_idl_column *column = &tc->columns[i];
+            json_array_add(columns, json_string_create(column->name));
+        }
+        json_object_put(monitor_request, "columns", columns);
+        json_object_put(monitor_requests, tc->name, monitor_request);
+    }
+
+    json_destroy(idl->monitor_request_id);
+    msg = jsonrpc_create_request(
+        "monitor", json_array_create_2(json_null_create(), monitor_requests),
+        &idl->monitor_request_id);
+    jsonrpc_session_send(idl->session, msg);
+}
+
+static void
+ovsdb_idl_parse_update(struct ovsdb_idl *idl, const struct json *table_updates)
+{
+    struct ovsdb_error *error;
+
+    idl->change_seqno++;
+
+    error = ovsdb_idl_parse_update__(idl, table_updates);
+    if (error) {
+        if (!VLOG_DROP_WARN(&syntax_rl)) {
+            char *s = ovsdb_error_to_string(error);
+            VLOG_WARN_RL(&syntax_rl, "%s", s);
+            free(s);
+        }
+        ovsdb_error_destroy(error);
+    }
+}
+
+static struct ovsdb_error *
+ovsdb_idl_parse_update__(struct ovsdb_idl *idl,
+                         const struct json *table_updates)
+{
+    const struct shash_node *tables_node;
+
+    if (table_updates->type != JSON_OBJECT) {
+        return ovsdb_syntax_error(table_updates, NULL,
+                                  "<table-updates> is not an object");
+    }
+    SHASH_FOR_EACH (tables_node, json_object(table_updates)) {
+        const struct json *table_update = tables_node->data;
+        const struct shash_node *table_node;
+        struct ovsdb_idl_table *table;
+
+        table = shash_find_data(&idl->tables, tables_node->name);
+        if (!table) {
+            return ovsdb_syntax_error(
+                table_updates, NULL,
+                "<table-updates> includes unknown table \"%s\"",
+                tables_node->name);
+        }
+
+        if (table_update->type != JSON_OBJECT) {
+            return ovsdb_syntax_error(table_update, NULL,
+                                      "<table-update> for table \"%s\" is "
+                                      "not an object", table->class->name);
+        }
+        SHASH_FOR_EACH (table_node, json_object(table_update)) {
+            const struct json *row_update = table_node->data;
+            const struct json *old_json, *new_json;
+            struct uuid uuid;
+
+            if (!uuid_from_string(&uuid, table_node->name)) {
+                return ovsdb_syntax_error(table_update, NULL,
+                                          "<table-update> for table \"%s\" "
+                                          "contains bad UUID "
+                                          "\"%s\" as member name",
+                                          table->class->name,
+                                          table_node->name);
+            }
+            if (row_update->type != JSON_OBJECT) {
+                return ovsdb_syntax_error(row_update, NULL,
+                                          "<table-update> for table \"%s\" "
+                                          "contains <row-update> for %s that "
+                                          "is not an object",
+                                          table->class->name,
+                                          table_node->name);
+            }
+
+            old_json = shash_find_data(json_object(row_update), "old");
+            new_json = shash_find_data(json_object(row_update), "new");
+            if (old_json && old_json->type != JSON_OBJECT) {
+                return ovsdb_syntax_error(old_json, NULL,
+                                          "\"old\" <row> is not object");
+            } else if (new_json && new_json->type != JSON_OBJECT) {
+                return ovsdb_syntax_error(new_json, NULL,
+                                          "\"new\" <row> is not object");
+            } else if ((old_json != NULL) + (new_json != NULL)
+                       != shash_count(json_object(row_update))) {
+                return ovsdb_syntax_error(row_update, NULL,
+                                          "<row-update> contains unexpected "
+                                          "member");
+            } else if (!old_json && !new_json) {
+                return ovsdb_syntax_error(row_update, NULL,
+                                          "<row-update> missing \"old\" "
+                                          "and \"new\" members");
+            }
+
+            ovsdb_idl_process_update(table, &uuid, old_json, new_json);
+        }
+    }
+
+    return NULL;
+}
+
+static struct ovsdb_idl_row *
+ovsdb_idl_get_row(struct ovsdb_idl_table *table, const struct uuid *uuid)
+{
+    struct ovsdb_idl_row *row;
+
+    HMAP_FOR_EACH_WITH_HASH (row, struct ovsdb_idl_row, hmap_node,
+                             uuid_hash(uuid), &table->rows) {
+        if (uuid_equals(&row->uuid, uuid)) {
+            return row;
+        }
+    }
+    return NULL;
+}
+
+static void
+ovsdb_idl_process_update(struct ovsdb_idl_table *table,
+                         const struct uuid *uuid, const struct json *old,
+                         const struct json *new)
+{
+    struct ovsdb_idl_row *row;
+
+    row = ovsdb_idl_get_row(table, uuid);
+    if (!new) {
+        /* Delete row. */
+        if (row && !ovsdb_idl_row_is_orphan(row)) {
+            /* XXX perhaps we should check the 'old' values? */
+            ovsdb_idl_delete_row(row);
+        } else {
+            VLOG_WARN_RL(&semantic_rl, "cannot delete missing row "UUID_FMT" "
+                         "from table %s",
+                         UUID_ARGS(uuid), table->class->name);
+        }
+    } else if (!old) {
+        /* Insert row. */
+        if (!row) {
+            ovsdb_idl_insert_row(ovsdb_idl_row_create(table, uuid), new);
+        } else if (ovsdb_idl_row_is_orphan(row)) {
+            ovsdb_idl_insert_row(row, new);
+        } else {
+            VLOG_WARN_RL(&semantic_rl, "cannot add existing row "UUID_FMT" to "
+                         "table %s", UUID_ARGS(uuid), table->class->name);
+            ovsdb_idl_modify_row(row, new);
+        }
+    } else {
+        /* Modify row. */
+        if (row) {
+            /* XXX perhaps we should check the 'old' values? */
+            if (!ovsdb_idl_row_is_orphan(row)) {
+                ovsdb_idl_modify_row(row, new);
+            } else {
+                VLOG_WARN_RL(&semantic_rl, "cannot modify missing but "
+                             "referenced row "UUID_FMT" in table %s",
+                             UUID_ARGS(uuid), table->class->name);
+                ovsdb_idl_insert_row(row, new);
+            }
+        } else {
+            VLOG_WARN_RL(&semantic_rl, "cannot modify missing row "UUID_FMT" "
+                         "in table %s", UUID_ARGS(uuid), table->class->name);
+            ovsdb_idl_insert_row(ovsdb_idl_row_create(table, uuid), new);
+        }
+    }
+}
+
+static void
+ovsdb_idl_row_update(struct ovsdb_idl_row *row, const struct json *row_json)
+{
+    struct ovsdb_idl_table *table = row->table;
+    struct shash_node *node;
+
+    SHASH_FOR_EACH (node, json_object(row_json)) {
+        const char *column_name = node->name;
+        const struct ovsdb_idl_column *column;
+        struct ovsdb_datum datum;
+        struct ovsdb_error *error;
+
+        column = shash_find_data(&table->columns, column_name);
+        if (!column) {
+            VLOG_WARN_RL(&syntax_rl, "unknown column %s updating row "UUID_FMT,
+                         column_name, UUID_ARGS(&row->uuid));
+            continue;
+        }
+
+        error = ovsdb_datum_from_json(&datum, &column->type, node->data, NULL);
+        if (!error) {
+            ovsdb_datum_swap(&row->fields[column - table->class->columns],
+                             &datum);
+            ovsdb_datum_destroy(&datum, &column->type);
+        } else {
+            char *s = ovsdb_error_to_string(error);
+            VLOG_WARN_RL(&syntax_rl, "error parsing column %s in row "UUID_FMT
+                         " in table %s: %s", column_name,
+                         UUID_ARGS(&row->uuid), table->class->name, s);
+            free(s);
+            ovsdb_error_destroy(error);
+        }
+    }
+}
+
+static bool
+ovsdb_idl_row_is_orphan(const struct ovsdb_idl_row *row)
+{
+    return !row->fields;
+}
+
+static void
+ovsdb_idl_row_clear_fields(struct ovsdb_idl_row *row)
+{
+    if (!ovsdb_idl_row_is_orphan(row)) {
+        const struct ovsdb_idl_table_class *class = row->table->class;
+        size_t i;
+
+        for (i = 0; i < class->n_columns; i++) {
+            ovsdb_datum_destroy(&row->fields[i], &class->columns[i].type);
+        }
+        row->fields = NULL;
+    }
+}
+
+static void
+ovsdb_idl_row_clear_arcs(struct ovsdb_idl_row *row, bool destroy_dsts)
+{
+    struct ovsdb_idl_arc *arc, *next;
+
+    /* Delete all forward arcs.  If 'destroy_dsts', destroy any orphaned rows
+     * that this causes to be unreferenced. */
+    LIST_FOR_EACH_SAFE (arc, next, struct ovsdb_idl_arc, src_node,
+                        &row->src_arcs) {
+        list_remove(&arc->dst_node);
+        if (destroy_dsts
+            && ovsdb_idl_row_is_orphan(arc->dst)
+            && list_is_empty(&arc->dst->dst_arcs)) {
+            ovsdb_idl_row_destroy(arc->dst);
+        }
+        free(arc);
+    }
+    list_init(&row->src_arcs);
+}
+
+/* Force nodes that reference 'row' to reparse. */
+static void
+ovsdb_idl_row_reparse_backrefs(struct ovsdb_idl_row *row, bool destroy_dsts)
+{
+    struct ovsdb_idl_arc *arc, *next;
+
+    /* This is trickier than it looks.  ovsdb_idl_row_clear_arcs() will destroy
+     * 'arc', so we need to use the "safe" variant of list traversal.  However,
+     * calling ref->table->class->parse will add an arc equivalent to 'arc' to
+     * row->arcs.  That could be a problem for traversal, but it adds it at the
+     * beginning of the list to prevent us from stumbling upon it again.
+     *
+     * (If duplicate arcs were possible then we would need to make sure that
+     * 'next' didn't also point into 'arc''s destination, but we forbid
+     * duplicate arcs.) */
+    LIST_FOR_EACH_SAFE (arc, next, struct ovsdb_idl_arc, dst_node,
+                        &row->dst_arcs) {
+        struct ovsdb_idl_row *ref = arc->src;
+
+        (ref->table->class->unparse)(ref);
+        ovsdb_idl_row_clear_arcs(ref, destroy_dsts);
+        (ref->table->class->parse)(ref);
+    }
+}
+
+static struct ovsdb_idl_row *
+ovsdb_idl_row_create(struct ovsdb_idl_table *table, const struct uuid *uuid)
+{
+    struct ovsdb_idl_row *row = xmalloc(table->class->allocation_size);
+    hmap_insert(&table->rows, &row->hmap_node, uuid_hash(uuid));
+    row->uuid = *uuid;
+    list_init(&row->src_arcs);
+    list_init(&row->dst_arcs);
+    row->table = table;
+    row->fields = NULL;
+    return row;
+}
+
+static void
+ovsdb_idl_row_destroy(struct ovsdb_idl_row *row)
+{
+    if (row) {
+        ovsdb_idl_row_clear_fields(row);
+        hmap_remove(&row->table->rows, &row->hmap_node);
+        free(row);
+    }
+}
+
+static void
+ovsdb_idl_insert_row(struct ovsdb_idl_row *row, const struct json *row_json)
+{
+    const struct ovsdb_idl_table_class *class = row->table->class;
+    size_t i;
+
+    assert(!row->fields);
+    row->fields = xmalloc(class->n_columns * sizeof *row->fields);
+    for (i = 0; i < class->n_columns; i++) {
+        ovsdb_datum_init_default(&row->fields[i], &class->columns[i].type);
+    }
+    ovsdb_idl_row_update(row, row_json);
+    (class->parse)(row);
+
+    ovsdb_idl_row_reparse_backrefs(row, false);
+}
+
+static void
+ovsdb_idl_delete_row(struct ovsdb_idl_row *row)
+{
+    (row->table->class->unparse)(row);
+    ovsdb_idl_row_clear_arcs(row, true);
+    ovsdb_idl_row_clear_fields(row);
+    if (list_is_empty(&row->dst_arcs)) {
+        ovsdb_idl_row_destroy(row);
+    } else {
+        ovsdb_idl_row_reparse_backrefs(row, true);
+    }
+}
+
+static void
+ovsdb_idl_modify_row(struct ovsdb_idl_row *row, const struct json *row_json)
+{
+    (row->table->class->unparse)(row);
+    ovsdb_idl_row_clear_arcs(row, true);
+    ovsdb_idl_row_update(row, row_json);
+    (row->table->class->parse)(row);
+}
+
+static bool
+may_add_arc(const struct ovsdb_idl_row *src, const struct ovsdb_idl_row *dst)
+{
+    const struct ovsdb_idl_arc *arc;
+
+    /* No self-arcs. */
+    if (src == dst) {
+        return false;
+    }
+
+    /* No duplicate arcs.
+     *
+     * We only need to test whether the first arc in dst->dst_arcs originates
+     * at 'src', since we add all of the arcs from a given source in a clump
+     * (in a single call to a row's ->parse function) and new arcs are always
+     * added at the front of the dst_arcs list. */
+    if (list_is_empty(&dst->dst_arcs)) {
+        return true;
+    }
+    arc = CONTAINER_OF(dst->dst_arcs.next, struct ovsdb_idl_arc, dst_node);
+    return arc->src != src;
+}
+
+struct ovsdb_idl_row *
+ovsdb_idl_get_row_arc(struct ovsdb_idl_row *src,
+                      struct ovsdb_idl_table_class *dst_table_class,
+                      const struct uuid *dst_uuid)
+{
+    struct ovsdb_idl *idl = src->table->idl;
+    struct ovsdb_idl_arc *arc;
+    struct ovsdb_idl_row *dst;
+
+    dst = ovsdb_idl_get_row(src->table, dst_uuid);
+    if (!dst) {
+        struct ovsdb_idl_table *dst_table;
+        dst_table = shash_find_data(&idl->tables, dst_table_class->name);
+        dst = ovsdb_idl_row_create(dst_table, dst_uuid);
+    }
+
+    /* Add a new arc, if it wouldn't be a self-arc or a duplicate arc. */
+    if (may_add_arc(src, dst)) {
+        /* The arc *must* be added at the front of the dst_arcs list.  See
+         * ovsdb_idl_row_reparse_backrefs() for details. */
+        arc = xmalloc(sizeof *arc);
+        list_push_front(&src->src_arcs, &arc->src_node);
+        list_push_front(&dst->dst_arcs, &arc->dst_node);
+        arc->src = src;
+        arc->dst = dst;
+    }
+
+    return !ovsdb_idl_row_is_orphan(dst) ? dst : NULL;
+}
+
+static struct ovsdb_idl_row *
+next_real_row(struct ovsdb_idl_table *table, struct hmap_node *node)
+{
+    for (; node; node = hmap_next(&table->rows, node)) {
+        struct ovsdb_idl_row *row;
+
+        row = CONTAINER_OF(node, struct ovsdb_idl_row, hmap_node);
+        if (!ovsdb_idl_row_is_orphan(row)) {
+            return row;
+        }
+    }
+    return NULL;
+}
+
+struct ovsdb_idl_row *
+ovsdb_idl_first_row(const struct ovsdb_idl *idl,
+                    const struct ovsdb_idl_table_class *table_class)
+{
+    struct ovsdb_idl_table *table;
+
+    table = shash_find_data(&idl->tables, table_class->name);
+    return next_real_row(table, hmap_first(&table->rows));
+}
+
+struct ovsdb_idl_row *
+ovsdb_idl_next_row(const struct ovsdb_idl_row *row)
+{
+    struct ovsdb_idl_table *table = row->table;
+
+    return next_real_row(table, hmap_next(&table->rows, &row->hmap_node));
+}
diff --git a/lib/ovsdb-idl.h b/lib/ovsdb-idl.h
new file mode 100644 (file)
index 0000000..7e95bb1
--- /dev/null
@@ -0,0 +1,31 @@
+/* Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_IDL_H
+#define OVSDB_IDL_H 1
+
+struct ovsdb_idl_class;
+
+struct ovsdb_idl *ovsdb_idl_create(const char *remote,
+                                   const struct ovsdb_idl_class *);
+void ovsdb_idl_destroy(struct ovsdb_idl *);
+
+void ovsdb_idl_run(struct ovsdb_idl *);
+void ovsdb_idl_wait(struct ovsdb_idl *);
+
+unsigned int ovsdb_idl_get_seqno(const struct ovsdb_idl *);
+void ovsdb_idl_force_reconnect(struct ovsdb_idl *);
+
+#endif /* ovsdb-idl.h */
index f3d4c976b4ad4dae86474660ca85e8a7a7ac4e99..2dcc46384169873f56ad5be6143ddcb214a9b4fd 100644 (file)
@@ -82,3 +82,13 @@ EXTRA_DIST += \
        ovsdb/ovsdb-idlc.in \
        ovsdb/ovsdb-idlc.1
 DISTCLEANFILES += ovsdb/ovsdb-idlc
+SUFFIXES += .ovsidl
+.ovsidl.c:
+       $(PYTHON) $(srcdir)/ovsdb/ovsdb-idlc.in c-idl-source $< > $@.tmp
+       mv $@.tmp $@
+.ovsidl.h:
+       $(PYTHON) $(srcdir)/ovsdb/ovsdb-idlc.in c-idl-header $< > $@.tmp
+       mv $@.tmp $@
+.ovsidl.ovsschema:
+       $(PYTHON) $(srcdir)/ovsdb/ovsdb-idlc.in ovsdb-schema $< > $@.tmp
+       mv $@.tmp $@
index ee27730af21560e58d0f8feb618554f6b43e2d7d..1311759c0f89680b1c6a2be3bc9ea455e9fc875e 100644 (file)
@@ -34,6 +34,17 @@ with a few additions:
 Lines that begin with \fB//\fR (two forward slashes) are ignored and
 thus can be used for comments.
 .
+.IP "\fB""\fBidlPrefix\fR"" member of <database-schema>"
+This member, which is required, specifies a string that is prefixed to
+top-level names in C bindings.  It should probably end in an
+underscore.
+.
+.IP "\fB""\fBidlHeader\fR"" member of <database-schema>"
+This member, which is required, specifies the name of the IDL header.
+It will be output on an \fB#include\fR line in the source file
+generated by the C bindings.  It should include the bracketing
+\fB""\fR or \fB<>\fR.
+.
 .IP "\fB""\fBkeyRefTable\fR"" member of <type>"
 A <type> whose \fBkey\fR is \fB"uuid"\fR may have an additional member
 named \fB"keyRefTable"\fR, whose value is a table name.  This
index 681cba5daaf55a7d9e7ea0ac148b62deb0b5fba5..68cb4ce6fa9f58f6ebc7d1ec6595c6f0dcbe1e4c 100755 (executable)
@@ -30,10 +30,12 @@ def mustGetMember(json, name, expectedType, description):
     return member
 
 class DbSchema:
-    def __init__(self, name, comment, tables):
+    def __init__(self, name, comment, tables, idlPrefix, idlHeader):
         self.name = name
         self.comment = comment
         self.tables = tables
+        self.idlPrefix = idlPrefix
+        self.idlHeader = idlHeader
 
     @staticmethod
     def fromJson(json):
@@ -41,9 +43,11 @@ class DbSchema:
         comment = getMember(json, 'comment', [unicode], 'database')
         tablesJson = mustGetMember(json, 'tables', [dict], 'database')
         tables = {}
-        for name, json in tablesJson.iteritems():
-            tables[name] = TableSchema.fromJson(json, "%s table" % name)
-        return DbSchema(name, comment, tables)
+        for name, tableJson in tablesJson.iteritems():
+            tables[name] = TableSchema.fromJson(tableJson, "%s table" % name)
+        idlPrefix = mustGetMember(json, 'idlPrefix', [unicode], 'database')
+        idlHeader = mustGetMember(json, 'idlHeader', [unicode], 'database')
+        return DbSchema(name, comment, tables, idlPrefix, idlHeader)
 
     def toJson(self):
         d = {"name": self.name,
@@ -156,7 +160,7 @@ def cBaseType(prefix, type, refTable=None):
                 'string': 'char *'}[type]
 
 def printCIDLHeader(schema):
-    prefix = 'ovsrec_'
+    prefix = schema.idlPrefix
     print '''\
 /* Generated automatically -- do not modify!    -*- buffer-read-only: t -*- */
 
@@ -166,6 +170,7 @@ def printCIDLHeader(schema):
 #include <stdbool.h>
 #include <stddef.h>
 #include <stdint.h>
+#include "ovsdb-idl-provider.h"
 #include "uuid.h"''' % {'prefix': prefix.upper()}
     for tableName, table in schema.tables.iteritems():
         print
@@ -173,10 +178,9 @@ def printCIDLHeader(schema):
             print "/* %s table (%s). */" % (tableName, table.comment)
         else:
             print "/* %s table. */" % (tableName)
-        print "struct %s%s {" % (prefix, tableName.lower())
-        print "\t/* Columns automatically included in every table. */"
-        print "\tstruct uuid uuid_;"
-        print "\tstruct uuid version_;"
+        structName = "%s%s" % (prefix, tableName.lower())
+        print "struct %s {" % structName
+        print "\tstruct ovsdb_idl_row header_;"
         for columnName, column in table.columns.iteritems():
             print "\n\t/* %s column. */" % columnName
             type = column.type
@@ -193,9 +197,205 @@ def printCIDLHeader(schema):
                 print "\t%s%s%s;" % (cBaseType(prefix, type.key, type.keyRefTable), pointer, columnName)
             if not singleton:
                 print "\tsize_t n_%s;" % columnName
+        print '''
+};
+
+const struct %(s)s *%(s)s_first(const struct ovsdb_idl *);
+const struct %(s)s *%(s)s_next(const struct %(s)s *);
+#define %(S)s_FOR_EACH(ROW, IDL) for ((ROW) = %(s)s_first(IDL); (ROW); (ROW) = %(s)s_next(ROW))''' % {'s': structName, 'S': structName.upper()}
+    print "\nextern struct ovsdb_idl_class %sidl_class;" % prefix
+    print "\n#endif /* %(prefix)sIDL_HEADER */" % {'prefix': prefix.upper()}
+
+def printEnum(members):
+    if len(members) == 0:
+        return
+
+    print "\nenum {";
+    for member in members[:-1]:
+        print "    %s," % member
+    print "    %s" % members[-1]
+    print "};"
+
+def printCIDLSource(schema):
+    prefix = schema.idlPrefix
+    print '''\
+/* Generated automatically -- do not modify!    -*- buffer-read-only: t -*- */
+
+#include <config.h>
+#include %s
+#include <limits.h>
+#include "ovsdb-data.h"''' % schema.idlHeader
+
+    # Table indexes.
+    printEnum(["%sTABLE_%s" % (prefix.upper(), tableName.upper()) for tableName in schema.tables] + ["%sN_TABLES" % prefix.upper()])
+    print "\nstatic struct ovsdb_idl_table_class %stable_classes[%sN_TABLES];" % (prefix, prefix.upper())
+
+    for tableName, table in schema.tables.iteritems():
+        structName = "%s%s" % (prefix, tableName.lower())
+        print "\f"
+        if table.comment != None:
+            print "/* %s table (%s). */" % (tableName, table.comment)
+        else:
+            print "/* %s table. */" % (tableName)
+
+        # Column indexes.
+        printEnum(["%s_COL_%s" % (structName.upper(), columnName.upper())
+                   for columnName in table.columns]
+                  + ["%s_N_COLUMNS" % structName.upper()])
+
+        # Parse function.
+        print '''
+static void
+%s_parse(struct ovsdb_idl_row *row_)
+{
+    struct %s *row = (struct %s *) row_;
+    const struct ovsdb_datum *datum;
+    size_t i UNUSED;
+
+    memset(row_ + 1, 0, sizeof *row - sizeof *row_);''' % (structName, structName, structName)
+
+
+        for columnName, column in table.columns.iteritems():
+            type = column.type
+            refKey = type.key == "uuid" and type.keyRefTable
+            refValue = type.value == "uuid" and type.valueRefTable
+            print
+            print "    datum = &row_->fields[%s_COL_%s];" % (structName.upper(), columnName.upper())
+            if type.value:
+                keyVar = "row->key_%s" % columnName
+                valueVar = "row->value_%s" % columnName
+            else:
+                keyVar = "row->%s" % columnName
+                valueVar = None
+
+            if type.min == 1 and type.max == 1:
+                print "    if (datum->n >= 1) {"
+                if not refKey:
+                    print "        %s = datum->keys[0].%s;" % (keyVar, type.key)
+                else:
+                    print "        %s = (struct %s%s *) ovsdb_idl_get_row_arc(row_, &%stable_classes[%sTABLE_%s], &datum->keys[0].uuid);" % (keyVar, prefix, type.keyRefTable.lower(), prefix, prefix.upper(), type.keyRefTable.upper())
+
+                if valueVar:
+                    if refValue:
+                        print "        %s = datum->values[0].%s;" % (valueVar, type.value)
+                    else:
+                        print "        %s = (struct %s%s *) ovsdb_idl_get_row_arc(row_, &%stable_classes[%sTABLE_%s], &datum->values[0].uuid);" % (valueVar, prefix, type.valueRefTable.lower(), prefix, prefix.upper(), type.valueRefTable.upper())
+                print "    }"
+            else:
+                if type.max != 'unlimited':
+                    nMax = "MIN(%d, datum->n)" % type.max
+                else:
+                    nMax = "datum->n"
+                print "    for (i = 0; i < %s; i++) {" % nMax
+                refs = []
+                if refKey:
+                    print "        struct %s%s *keyRow = (struct %s%s *) ovsdb_idl_get_row_arc(row_, &%stable_classes[%sTABLE_%s], &datum->keys[i].uuid);" % (prefix, type.keyRefTable.lower(), prefix, type.keyRefTable.lower(), prefix, prefix.upper(), type.keyRefTable.upper())
+                    keySrc = "keyRow"
+                    refs.append('keyRow')
+                else:
+                    keySrc = "datum->keys[i].%s" % type.key
+                if refValue:
+                    print "        struct %s%s *valueRow = (struct %s%s *) ovsdb_idl_get_row_arc(row_, &%stable_classes[%sTABLE_%s], &datum->values[i].uuid);" % (prefix, type.valueRefTable.lower(), prefix, type.valueRefTable.lower(), prefix, prefix.upper(), type.valueRefTable.upper())
+                    valueSrc = "valueRow"
+                    refs.append('valueRow')
+                elif valueVar:
+                    valueSrc = "datum->values[i].%s" % type.value
+                if refs:
+                    print "        if (%s) {" % ' && '.join(refs)
+                    indent = "            "
+                else:
+                    indent = "        "
+                print "%sif (!row->n_%s) {" % (indent, columnName)
+                print "%s    %s = xmalloc(%s * sizeof *%s);" % (indent, keyVar, nMax, keyVar)
+                if valueVar:
+                    print "%s    %s = xmalloc(%s * sizeof %%s);" % (indent, valueVar, nMax, valueVar)
+                print "%s}" % indent
+                print "%s%s[row->n_%s] = %s;" % (indent, keyVar, columnName, keySrc)
+                if valueVar:
+                    print "%s[row->n_%s] = %s;" % (indent, valueVar, columnName, valueSrc)
+                print "%srow->n_%s++;" % (indent, columnName)
+                if refs:
+                    print "        }"
+                print "    }"
+        print "}"
+
+        # Unparse function.
+        nArrays = 0
+        for columnName, column in table.columns.iteritems():
+            type = column.type
+            if type.min != 1 or type.max != 1:
+                if not nArrays:
+                    print '''
+static void
+%s_unparse(struct ovsdb_idl_row *row_)
+{
+    struct %s *row = (struct %s *) row_;
+''' % (structName, structName, structName)
+                if type.value:
+                    keyVar = "row->key_%s" % columnName
+                    valueVar = "row->value_%s" % columnName
+                else:
+                    keyVar = "row->%s" % columnName
+                    valueVar = None
+                print "    free(%s);" % keyVar
+                if valueVar:
+                    print "    free(%s);" % valueVar
+                nArrays += 1
+        if not nArrays:
+            print '''
+static void
+%s_unparse(struct ovsdb_idl_row *row UNUSED)
+{''' % (structName)
+        print "}"
+
+        # First, next functions.
+        print '''
+const struct %(s)s *%(s)s_first(const struct ovsdb_idl *idl)
+{
+    return (const struct %(s)s *) ovsdb_idl_first_row(idl, &%(p)stable_classes[%(P)sTABLE_%(T)s]);
+}
+
+const struct %(s)s *%(s)s_next(const struct %(s)s *row)
+{
+    return (const struct %(s)s *) ovsdb_idl_next_row(&row->header_);
+}''' % {'s': structName, 'p': prefix, 'P': prefix.upper(), 'T': tableName.upper()}
+
+        # Table columns.
+        print "\nstatic struct ovsdb_idl_column %s_columns[%s_N_COLUMNS] = {" % (
+            structName, structName.upper())
+        for columnName, column in table.columns.iteritems():
+            type = column.type
+            
+            if type.value:
+                valueTypeName = type.value.upper()
+            else:
+                valueTypeName = "VOID"
+            if type.max == "unlimited":
+                max = "UINT_MAX"
+            else:
+                max = type.max
+            print "    {\"%s\", {OVSDB_TYPE_%s, OVSDB_TYPE_%s, %d, %s}}," % (
+                columnName, type.key.upper(), valueTypeName,
+                type.min, max)
         print "};"
-    print
-    print "#endif /* %(prefix)sIDL_HEADER */" % {'prefix': prefix.upper()}
+
+    # Table classes.
+    print "\f"
+    print "static struct ovsdb_idl_table_class %stable_classes[%sN_TABLES] = {" % (prefix, prefix.upper())
+    for tableName, table in schema.tables.iteritems():
+        structName = "%s%s" % (prefix, tableName.lower())
+        print "    {\"%s\"," % tableName
+        print "     %s_columns, ARRAY_SIZE(%s_columns)," % (
+            structName, structName)
+        print "     sizeof(struct %s)," % structName
+        print "     %s_parse," % structName
+        print "     %s_unparse}," % structName
+    print "};"
+
+    # IDL class.
+    print "\nstruct ovsdb_idl_class %sidl_class = {" % prefix
+    print "    %stable_classes, ARRAY_SIZE(%stable_classes)" % (prefix, prefix)
+    print "};"
 
 def ovsdb_escape(string):
     def escape(match):
index b9d74836aac9da7bc678d9a21ba971f04cf338d4..cb962a844cc2d9abe09c5bfbdaf4b2dfdaa7df7a 100644 (file)
@@ -31,6 +31,7 @@ TESTSUITE_AT = \
        tests/ovsdb-file.at \
        tests/ovsdb-server.at \
        tests/ovsdb-monitor.at \
+       tests/ovsdb-idl.at \
        tests/stp.at \
        tests/ovs-vsctl.at \
        tests/lcov-post.at
@@ -106,9 +107,12 @@ tests_test_lockfile_SOURCES = tests/test-lockfile.c
 tests_test_lockfile_LDADD = lib/libopenvswitch.a
 
 noinst_PROGRAMS += tests/test-ovsdb
-tests_test_ovsdb_SOURCES = tests/test-ovsdb.c
+tests_test_ovsdb_SOURCES = tests/test-ovsdb.c tests/idltest.c tests/idltest.h
 tests_test_ovsdb_LDADD = ovsdb/libovsdb.a lib/libopenvswitch.a
-EXTRA_DIST += tests/uuidfilt.pl
+EXTRA_DIST += tests/uuidfilt.pl tests/idltest.ovsidl
+BUILT_SOURCES += tests/idltest.c tests/idltest.h
+noinst_DATA += tests/idltest.ovsschema
+DISTCLEANFILES += tests/idltest.ovsschema
 
 noinst_PROGRAMS += tests/test-reconnect
 tests_test_reconnect_SOURCES = tests/test-reconnect.c
diff --git a/tests/idltest.ovsidl b/tests/idltest.ovsidl
new file mode 100644 (file)
index 0000000..ec5cec3
--- /dev/null
@@ -0,0 +1,29 @@
+//
+// This is an ovsdb-idl schema.  The OVSDB IDL compiler, ovsdb-idlc,
+// can translate it into an OVSDB schema (which simply entails
+// deleting some members from the schema) or C headers or source for
+// use with the IDL at runtime.
+//
+
+{"name": "idltest",
+ "idlPrefix": "idltest_",
+ "idlHeader": "\"tests/idltest.h\"",
+ "tables": {
+   "simple": {
+     "columns": {
+       "i": {"type": "integer"},
+       "r": {"type": "real"},
+       "b": {"type": "boolean"},
+       "s": {"type": "string"},
+       "u": {"type": "uuid"},
+       "ia": {"type": {"key": "integer", "min": 0, "max": "unlimited"}},
+       "ra": {"type": {"key": "real", "min": 0, "max": "unlimited"}},
+       "ba": {"type": {"key": "boolean", "min": 0, "max": "unlimited"}},
+       "sa": {"type": {"key": "string", "min": 0, "max": "unlimited"}},
+       "ua": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}}}},
+   "selfLink": {
+     "columns": {
+       "i": {"type": "integer"},
+       "k": {"type": {"key": "uuid", "keyRefTable": "selfLink"}},
+       "ka": {"type": {"key": "uuid", "keyRefTable": "selfLink",
+                       "min": 0, "max": "unlimited"}}}}}}
diff --git a/tests/ovsdb-idl.at b/tests/ovsdb-idl.at
new file mode 100644 (file)
index 0000000..f0e6ff8
--- /dev/null
@@ -0,0 +1,266 @@
+AT_BANNER([OVSDB -- interface description language (IDL)])
+
+# OVSDB_CHECK_IDL(TITLE, [PRE-IDL-TXN], TRANSACTIONS, OUTPUT, [KEYWORDS])
+#
+# Creates a database with a schema derived from idltest.ovsidl, runs
+# each PRE-IDL-TXN (if any), starts an ovsdb-server on that database,
+# and runs "test-ovsdb idl" passing each of the TRANSACTIONS along.
+#
+# Checks that the overall output is OUTPUT.  Before comparison, the
+# output is sorted (using "sort") and UUIDs in the output are replaced
+# by markers of the form <N> where N is a number.  The first unique
+# UUID is replaced by <0>, the next by <1>, and so on.  If a given
+# UUID appears more than once it is always replaced by the same
+# marker.
+#
+# TITLE is provided to AT_SETUP and KEYWORDS to AT_KEYWORDS.
+m4_define([OVSDB_CHECK_IDL], 
+  [AT_SETUP([$1])
+   AT_KEYWORDS([ovsdb server idl positive $5])
+   OVS_CHECK_LCOV([ovsdb-tool create db $abs_builddir/idltest.ovsschema], 
+                  [0], [stdout], [ignore])
+   AT_CHECK([ovsdb-server --detach --pidfile=$PWD/server-pid --listen=punix:socket --unixctl=$PWD/unixctl db])
+   m4_if([$2], [], [],
+     [OVS_CHECK_LCOV([ovsdb-client transact unix:socket $2], [0], [ignore], [ignore], [kill `cat server-pid`])])
+   AT_CHECK([test-ovsdb -vjsonrpc -t10 idl unix:socket $3], 
+            [0], [stdout], [ignore], [kill `cat server-pid`])
+   AT_CHECK([sort stdout | perl $srcdir/uuidfilt.pl], [0], [$4], [],
+            [kill `cat server-pid`])
+   kill `cat server-pid`
+   AT_CLEANUP])
+
+OVSDB_CHECK_IDL([simple idl, initially empty, no ops],
+  [],
+  [],
+  [000: empty
+001: done
+])
+
+OVSDB_CHECK_IDL([simple idl, initially empty, various ops],
+  [],
+  [['[{"op": "insert",
+       "table": "simple",
+       "row": {"i": 1,
+               "r": 2.0,
+               "b": true,
+               "s": "mystring",
+               "u": ["uuid", "84f5c8f5-ac76-4dbc-a24f-8860eb407fc1"],
+               "ia": ["set", [1, 2, 3]],
+               "ra": ["set", [-0.5]],
+               "ba": ["set", [true, false]],
+               "sa": ["set", ["abc", "def"]], 
+               "ua": ["set", [["uuid", "69443985-7806-45e2-b35f-574a04e720f9"],
+                              ["uuid", "aad11ef0-816a-4b01-93e6-03b8b4256b98"]]]}},
+      {"op": "insert",
+       "table": "simple",
+       "row": {}}]' \
+    '[{"op": "update",
+       "table": "simple",
+       "where": [],
+       "row": {"b": true}}]' \
+    '[{"op": "update",
+       "table": "simple",
+       "where": [],
+       "row": {"r": 123.5}}]' \
+    '[{"op": "insert",
+       "table": "simple",
+       "row": {"i": -1,
+               "r": 125,
+               "b": false,
+               "s": "",
+               "ia": ["set", [1]],
+               "ra": ["set", [1.5]],
+               "ba": ["set", [false]],
+               "sa": ["set", []], 
+               "ua": ["set", []]}}]' \
+    '[{"op": "update",
+       "table": "simple",
+       "where": [["i", "<", 1]],
+       "row": {"s": "newstring"}}]' \
+    '[{"op": "delete",
+       "table": "simple",
+       "where": [["i", "==", 0]]}]' \
+    'reconnect']],
+  [[000: empty
+001: {"error":null,"result":[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]}]}
+002: i=0 r=0 b=false s= u=<2> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1>
+002: i=1 r=2 b=true s=mystring u=<3> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<4> <5>] uuid=<0>
+003: {"error":null,"result":[{"count":2}]}
+004: i=0 r=0 b=true s= u=<2> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1>
+004: i=1 r=2 b=true s=mystring u=<3> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<4> <5>] uuid=<0>
+005: {"error":null,"result":[{"count":2}]}
+006: i=0 r=123.5 b=true s= u=<2> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1>
+006: i=1 r=123.5 b=true s=mystring u=<3> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<4> <5>] uuid=<0>
+007: {"error":null,"result":[{"uuid":["uuid","<6>"]}]}
+008: i=-1 r=125 b=false s= u=<2> ia=[1] ra=[1.5] ba=[false] sa=[] ua=[] uuid=<6>
+008: i=0 r=123.5 b=true s= u=<2> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1>
+008: i=1 r=123.5 b=true s=mystring u=<3> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<4> <5>] uuid=<0>
+009: {"error":null,"result":[{"count":2}]}
+010: i=-1 r=125 b=false s=newstring u=<2> ia=[1] ra=[1.5] ba=[false] sa=[] ua=[] uuid=<6>
+010: i=0 r=123.5 b=true s=newstring u=<2> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1>
+010: i=1 r=123.5 b=true s=mystring u=<3> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<4> <5>] uuid=<0>
+011: {"error":null,"result":[{"count":1}]}
+012: i=-1 r=125 b=false s=newstring u=<2> ia=[1] ra=[1.5] ba=[false] sa=[] ua=[] uuid=<6>
+012: i=1 r=123.5 b=true s=mystring u=<3> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<4> <5>] uuid=<0>
+013: reconnect
+014: i=-1 r=125 b=false s=newstring u=<2> ia=[1] ra=[1.5] ba=[false] sa=[] ua=[] uuid=<6>
+014: i=1 r=123.5 b=true s=mystring u=<3> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<4> <5>] uuid=<0>
+015: done
+]])
+
+OVSDB_CHECK_IDL([simple idl, initially populated],
+  [['[{"op": "insert",
+       "table": "simple",
+       "row": {"i": 1,
+               "r": 2.0,
+               "b": true,
+               "s": "mystring",
+               "u": ["uuid", "84f5c8f5-ac76-4dbc-a24f-8860eb407fc1"],
+               "ia": ["set", [1, 2, 3]],
+               "ra": ["set", [-0.5]],
+               "ba": ["set", [true, false]],
+               "sa": ["set", ["abc", "def"]], 
+               "ua": ["set", [["uuid", "69443985-7806-45e2-b35f-574a04e720f9"],
+                              ["uuid", "aad11ef0-816a-4b01-93e6-03b8b4256b98"]]]}},
+      {"op": "insert",
+       "table": "simple",
+       "row": {}}]']],
+  [['[{"op": "update",
+       "table": "simple",
+       "where": [],
+       "row": {"b": true}}]']],
+  [[000: i=0 r=0 b=false s= u=<0> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1>
+000: i=1 r=2 b=true s=mystring u=<2> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<3> <4>] uuid=<5>
+001: {"error":null,"result":[{"count":2}]}
+002: i=0 r=0 b=true s= u=<0> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1>
+002: i=1 r=2 b=true s=mystring u=<2> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<3> <4>] uuid=<5>
+003: done
+]])
+
+OVSDB_CHECK_IDL([self-linking idl, consistent ops],
+  [],
+  [['[{"op": "insert",
+       "table": "selfLink",
+       "row": {"i": 0, "k": ["named-uuid", "self"]},
+       "uuid-name": "self"}]' \
+    '[{"op": "insert",
+       "table": "selfLink",
+       "row": {"i": 1},
+       "uuid-name": "row1"},
+      {"op": "insert",
+       "table": "selfLink",
+       "row": {"i": 2, "k": ["named-uuid", "row1"]},
+       "uuid-name": "row2"},
+      {"op": "update",
+       "table": "selfLink",
+       "where": [["i", "==", 1]],
+       "row": {"k": ["named-uuid", "row2"]}}]' \
+    '[{"op": "update",
+       "table": "selfLink",
+       "where": [["i", "==", 1]],
+       "row": {"k": ["uuid", "#1#"]}}]' \
+    '[{"op": "update",
+       "table": "selfLink",
+       "where": [],
+       "row": {"k": ["uuid", "#0#"]}}]']],
+  [[000: empty
+001: {"error":null,"result":[{"uuid":["uuid","<0>"]}]}
+002: i=0 k=0 ka=[] uuid=<0>
+003: {"error":null,"result":[{"uuid":["uuid","<1>"]},{"uuid":["uuid","<2>"]},{"count":1}]}
+004: i=0 k=0 ka=[] uuid=<0>
+004: i=1 k=2 ka=[] uuid=<1>
+004: i=2 k=1 ka=[] uuid=<2>
+005: {"error":null,"result":[{"count":1}]}
+006: i=0 k=0 ka=[] uuid=<0>
+006: i=1 k=1 ka=[] uuid=<1>
+006: i=2 k=1 ka=[] uuid=<2>
+007: {"error":null,"result":[{"count":3}]}
+008: i=0 k=0 ka=[] uuid=<0>
+008: i=1 k=0 ka=[] uuid=<1>
+008: i=2 k=0 ka=[] uuid=<2>
+009: done
+]])
+
+OVSDB_CHECK_IDL([self-linking idl, inconsistent ops],
+  [],
+  [['[{"op": "insert",
+       "table": "selfLink",
+       "row": {"i": 0, "k": ["uuid", "cf197cc5-c8c9-42f5-82d5-c71a9f2cb96b"]}}]' \
+     '[{"op": "update",
+       "table": "selfLink",
+       "where": [],
+       "row": {"k": ["uuid", "#0#"]}}]' \
+     '[{"op": "update",
+       "table": "selfLink",
+       "where": [],
+       "row": {"k": ["uuid", "c2fca39a-e69a-42a4-9c56-5eca85839ce9"]}}]' \
+     '[{"op": "insert",
+       "table": "selfLink",
+       "row": {"i": 1, "k": ["uuid", "52d752a3-b062-4668-9446-d2e0d4a14703"]}}]' \
+     '[{"op": "update",
+       "table": "selfLink",
+       "where": [],
+       "row": {"k": ["uuid", "#1#"]}}]' \
+]],
+  [[000: empty
+001: {"error":null,"result":[{"uuid":["uuid","<0>"]}]}
+002: i=0 k= ka=[] uuid=<0>
+003: {"error":null,"result":[{"count":1}]}
+004: i=0 k=0 ka=[] uuid=<0>
+005: {"error":null,"result":[{"count":1}]}
+006: i=0 k= ka=[] uuid=<0>
+007: {"error":null,"result":[{"uuid":["uuid","<1>"]}]}
+008: i=0 k= ka=[] uuid=<0>
+008: i=1 k= ka=[] uuid=<1>
+009: {"error":null,"result":[{"count":2}]}
+010: i=0 k=1 ka=[] uuid=<0>
+010: i=1 k=1 ka=[] uuid=<1>
+011: done
+]])
+
+OVSDB_CHECK_IDL([self-linking idl, sets],
+  [],
+  [['[{"op": "insert",
+       "table": "selfLink",
+       "row": {"i": 0, "ka": ["set", [["named-uuid", "i0"]]]},
+       "uuid-name": "i0"},
+      {"op": "insert",
+       "table": "selfLink",
+       "row": {"i": 1, "ka": ["set", [["named-uuid", "i1"]]]},
+       "uuid-name": "i1"},
+      {"op": "insert",
+       "table": "selfLink",
+       "row": {"i": 2, "ka": ["set", [["named-uuid", "i2"]]]},
+       "uuid-name": "i2"},
+      {"op": "insert",
+       "table": "selfLink",
+       "row": {"i": 3, "ka": ["set", [["named-uuid", "i3"]]]},
+       "uuid-name": "i3"}]' \
+    '[{"op": "update",
+       "table": "selfLink",
+       "where": [],
+       "row": {"ka": ["set", [["uuid", "#0#"], ["uuid", "#1#"], ["uuid", "#2#"], ["uuid", "#3#"]]]}}]' \
+    '[{"op": "update",
+       "table": "selfLink",
+       "where": [],
+       "row": {"ka": ["set", [["uuid", "#0#"], ["uuid", "88702e78-845b-4a6e-ad08-cf68922ae84a"], ["uuid", "#2#"], ["uuid", "1ac2b12e-b767-4805-a55d-43976e40c465"]]]}}]']],
+  [[000: empty
+001: {"error":null,"result":[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"uuid":["uuid","<2>"]},{"uuid":["uuid","<3>"]}]}
+002: i=0 k= ka=[0] uuid=<0>
+002: i=1 k= ka=[1] uuid=<1>
+002: i=2 k= ka=[2] uuid=<2>
+002: i=3 k= ka=[3] uuid=<3>
+003: {"error":null,"result":[{"count":4}]}
+004: i=0 k= ka=[0 1 2 3] uuid=<0>
+004: i=1 k= ka=[0 1 2 3] uuid=<1>
+004: i=2 k= ka=[0 1 2 3] uuid=<2>
+004: i=3 k= ka=[0 1 2 3] uuid=<3>
+005: {"error":null,"result":[{"count":4}]}
+006: i=0 k= ka=[0 2] uuid=<0>
+006: i=1 k= ka=[0 2] uuid=<1>
+006: i=2 k= ka=[0 2] uuid=<2>
+006: i=3 k= ka=[0 2] uuid=<3>
+007: done
+]])
+
+# XXX self-linking idl, maps
index 690e99874e9825f3263d5c6f245f931520a8e18c..6e3f63d852d4880eb57a9953d97e45494b4bcac2 100644 (file)
@@ -49,3 +49,4 @@ m4_include([tests/ovsdb-trigger.at])
 m4_include([tests/ovsdb-file.at])
 m4_include([tests/ovsdb-server.at])
 m4_include([tests/ovsdb-monitor.at])
+m4_include([tests/ovsdb-idl.at])
index 27523fb222d674146133826d245073bbc54ed4f9..f3305edd0acd052e7b129f021d211dc04327f72e 100644 (file)
 
 #include "command-line.h"
 #include "json.h"
+#include "jsonrpc.h"
 #include "ovsdb-data.h"
 #include "ovsdb-error.h"
+#include "ovsdb-idl.h"
 #include "ovsdb-types.h"
 #include "ovsdb/column.h"
 #include "ovsdb/condition.h"
@@ -39,7 +41,9 @@
 #include "ovsdb/transaction.h"
 #include "ovsdb/trigger.h"
 #include "poll-loop.h"
+#include "stream.h"
 #include "svec.h"
+#include "tests/idltest.h"
 #include "timeval.h"
 #include "util.h"
 #include "vlog.h"
@@ -64,6 +68,7 @@ static void
 parse_options(int argc, char *argv[])
 {
     static struct option long_options[] = {
+        {"timeout", required_argument, 0, 't'},
         {"verbose", optional_argument, 0, 'v'},
         {"help", no_argument, 0, 'h'},
         {0, 0, 0, 0},
@@ -71,12 +76,25 @@ parse_options(int argc, char *argv[])
     char *short_options = long_options_to_short_options(long_options);
 
     for (;;) {
-        int c = getopt_long(argc, argv, short_options, long_options, NULL);
+        unsigned long int timeout;
+        int c;
+
+        c = getopt_long(argc, argv, short_options, long_options, NULL);
         if (c == -1) {
             break;
         }
 
         switch (c) {
+        case 't':
+            timeout = strtoul(optarg, NULL, 10);
+            if (timeout <= 0) {
+                ovs_fatal(0, "value %s on -t or --timeout is not at least 1",
+                          optarg);
+            } else {
+                time_alarm(timeout);
+            }
+            break;
+
         case 'h':
             usage();
 
@@ -144,10 +162,16 @@ usage(void)
            "    executes each TRANSACTION on an initially empty database\n"
            "    the specified SCHEMA.   A TRANSACTION of the form\n"
            "    [\"advance\", NUMBER] advances NUMBER milliseconds in\n"
-           "    simulated time, for causing triggers to time out.\n",
+           "    simulated time, for causing triggers to time out.\n"
+           "  idl SERVER [TRANSACTION...]\n"
+           "    connect to SERVER and dump the contents of the database\n"
+           "    as seen initially by the IDL implementation and after\n"
+           "    executing each TRANSACTION.  (Each TRANSACTION must modify\n"
+           "    the database or this command will hang.)\n",
            program_name, program_name);
     vlog_usage();
     printf("\nOther options:\n"
+           "  -t, --timeout=SECS          give up after SECS seconds\n"
            "  -h, --help                  display this help message\n");
     exit(EXIT_SUCCESS);
 }
@@ -1207,6 +1231,215 @@ do_transact(int argc, char *argv[])
     ovsdb_destroy(do_transact_db); /* Also destroys 'schema'. */
 }
 
+static int
+compare_selflink(const void *a_, const void *b_)
+{
+    const struct idltest_selflink *const *ap = a_;
+    const struct idltest_selflink *const *bp = b_;
+    const struct idltest_selflink *a = *ap;
+    const struct idltest_selflink *b = *bp;
+
+
+    return a->i < b->i ? -1 : a->i > b->i;
+}
+
+static void
+print_idl(struct ovsdb_idl *idl, int step)
+{
+    const struct idltest_simple *s;
+    const struct idltest_selflink *sl;
+    int n = 0;
+
+    IDLTEST_SIMPLE_FOR_EACH (s, idl) {
+        size_t i;
+
+        printf("%03d: i=%"PRId64" r=%g b=%s s=%s u="UUID_FMT" ia=[",
+               step, s->i, s->r, s->b ? "true" : "false",
+               s->s, UUID_ARGS(&s->u));
+        for (i = 0; i < s->n_ia; i++) {
+            printf("%s%"PRId64, i ? " " : "", s->ia[i]);
+        }
+        printf("] ra=[");
+        for (i = 0; i < s->n_ra; i++) {
+            printf("%s%g", i ? " " : "", s->ra[i]);
+        }
+        printf("] ba=[");
+        for (i = 0; i < s->n_ba; i++) {
+            printf("%s%s", i ? " " : "", s->ba[i] ? "true" : "false");
+        }
+        printf("] sa=[");
+        for (i = 0; i < s->n_sa; i++) {
+            printf("%s%s", i ? " " : "", s->sa[i]);
+        }
+        printf("] ua=[");
+        for (i = 0; i < s->n_ua; i++) {
+            printf("%s"UUID_FMT, i ? " " : "", UUID_ARGS(&s->ua[i]));
+        }
+        printf("] uuid="UUID_FMT"\n", UUID_ARGS(&s->header_.uuid));
+        n++;
+    }
+    IDLTEST_SELFLINK_FOR_EACH (sl, idl) {
+        struct idltest_selflink **links;
+        size_t i;
+
+        printf("%03d: i=%"PRId64" k=", step, sl->i);
+        if (sl->k) {
+            printf("%"PRId64, sl->k->i);
+        }
+        printf(" ka=[");
+        links = xmemdup(sl->ka, sl->n_ka * sizeof *sl->ka);
+        qsort(links, sl->n_ka, sizeof *links, compare_selflink);
+        for (i = 0; i < sl->n_ka; i++) {
+            printf("%s%"PRId64, i ? " " : "", links[i]->i);
+        }
+        free(links);
+        printf("] uuid="UUID_FMT"\n", UUID_ARGS(&sl->header_.uuid));
+        n++;
+    }
+    if (!n) {
+        printf("%03d: empty\n", step);
+    }
+}
+
+static unsigned int
+print_updated_idl(struct ovsdb_idl *idl, struct jsonrpc *rpc,
+                  int step, unsigned int seqno)
+{
+    for (;;) {
+        unsigned int new_seqno;
+
+        if (rpc) {
+            jsonrpc_run(rpc);
+        }
+        ovsdb_idl_run(idl);
+        new_seqno = ovsdb_idl_get_seqno(idl);
+        if (new_seqno != seqno) {
+            print_idl(idl, step);
+            return new_seqno;
+        }
+
+        if (rpc) {
+            jsonrpc_wait(rpc);
+        }
+        ovsdb_idl_wait(idl);
+        poll_block();
+    }
+}
+
+static void
+parse_uuids(const struct json *json, struct ovsdb_symbol_table *symtab,
+            size_t *n)
+{
+    struct uuid uuid;
+
+    if (json->type == JSON_STRING && uuid_from_string(&uuid, json->u.string)) {
+        char *name = xasprintf("#%d#", *n);
+        ovsdb_symbol_table_put(symtab, name, &uuid);
+        free(name);
+        *n += 1;
+    } else if (json->type == JSON_ARRAY) {
+        size_t i;
+
+        for (i = 0; i < json->u.array.n; i++) {
+            parse_uuids(json->u.array.elems[i], symtab, n);
+        }
+    } else if (json->type == JSON_OBJECT) {
+        const struct shash_node *node;
+
+        SHASH_FOR_EACH (node, json_object(json)) {
+            parse_uuids(node->data, symtab, n);
+        }
+    }
+}
+
+static void
+substitute_uuids(struct json *json, const struct ovsdb_symbol_table *symtab)
+{
+    if (json->type == JSON_STRING) {
+        const struct uuid *uuid;
+
+        uuid = ovsdb_symbol_table_get(symtab, json->u.string);
+        if (uuid) {
+            free(json->u.string);
+            json->u.string = xasprintf(UUID_FMT, UUID_ARGS(uuid));
+        }
+    } else if (json->type == JSON_ARRAY) {
+        size_t i;
+
+        for (i = 0; i < json->u.array.n; i++) {
+            substitute_uuids(json->u.array.elems[i], symtab);
+        }
+    } else if (json->type == JSON_OBJECT) {
+        const struct shash_node *node;
+
+        SHASH_FOR_EACH (node, json_object(json)) {
+            substitute_uuids(node->data, symtab);
+        }
+    }
+}
+
+static void
+do_idl(int argc, char *argv[])
+{
+    struct jsonrpc *rpc;
+    struct ovsdb_idl *idl;
+    unsigned int seqno = 0;
+    struct ovsdb_symbol_table *symtab;
+    size_t n_uuids = 0;
+    int step = 0;
+    int error;
+    int i;
+
+    idl = ovsdb_idl_create(argv[1], &idltest_idl_class);
+    if (argc > 2) {
+        struct stream *stream;
+
+        error = stream_open_block(argv[1], &stream);
+        if (error) {
+            ovs_fatal(error, "failed to connect to \"%s\"", argv[1]);
+        }
+        rpc = jsonrpc_open(stream);
+    } else {
+        rpc = NULL;
+    }
+
+    symtab = ovsdb_symbol_table_create();
+    for (i = 2; i < argc; i++) {
+        struct jsonrpc_msg *request, *reply;
+        int error;
+
+        seqno = print_updated_idl(idl, rpc, step++, seqno);
+
+        if (!strcmp(argv[i], "reconnect")) {
+            printf("%03d: reconnect\n", step++);
+            ovsdb_idl_force_reconnect(idl);
+        } else {
+            struct json *json = parse_json(argv[i]);
+            substitute_uuids(json, symtab);
+            request = jsonrpc_create_request("transact", json, NULL);
+            error = jsonrpc_transact_block(rpc, request, &reply);
+            if (error) {
+                ovs_fatal(error, "jsonrpc transaction failed");
+            }
+            printf("%03d: ", step++);
+            if (reply->result) {
+                parse_uuids(reply->result, symtab, &n_uuids);
+            }
+            json_destroy(reply->id);
+            reply->id = NULL;
+            print_and_free_json(jsonrpc_msg_to_json(reply));
+        }
+    }
+    ovsdb_symbol_table_destroy(symtab);
+
+    if (rpc) {
+        jsonrpc_close(rpc);
+    }
+    print_updated_idl(idl, NULL, step++, seqno);
+    ovsdb_idl_destroy(idl);
+    printf("%03d: done\n", step);
+}
+
 static struct command all_commands[] = {
     { "log-io", 2, INT_MAX, do_log_io },
     { "parse-atomic-type", 1, 1, do_parse_atomic_type },
@@ -1225,6 +1458,7 @@ static struct command all_commands[] = {
     { "transact", 1, INT_MAX, do_transact },
     { "execute", 2, INT_MAX, do_execute },
     { "trigger", 2, INT_MAX, do_trigger },
+    { "idl", 1, INT_MAX, do_idl },
     { "help", 0, INT_MAX, do_help },
     { NULL, 0, 0, NULL },
 };
index 6860488f63f57b19786ae245e002bbc4ffd90424..db8fdf694a4d53cd61716733ea142795fba85edf 100644 (file)
@@ -17,6 +17,7 @@ vswitchd_ovs_vswitchd_SOURCES = \
        vswitchd/proc-net-compat.h \
        vswitchd/ovs-vswitchd.c \
        vswitchd/ovs-vswitchd.h \
+       vswitchd/vswitch-idl.c \
        vswitchd/vswitch-idl.h \
        vswitchd/xenserver.c \
        vswitchd/xenserver.h
@@ -38,10 +39,8 @@ EXTRA_DIST += \
        vswitchd/ovs-vswitchd.8.in \
        vswitchd/ovs-brcompatd.8.in
 
-EXTRA_DIST += vswitchd/vswitch.ovsidl
-BUILT_SOURCES += vswitchd/vswitch-idl.h
-DISTCLEANFILES += \
-       vswitchd/vswitch.ovsidl \
-       vswitchd/vswitch-idl.h
-vswitchd/vswitch-idl.h: vswitchd/vswitch.ovsidl ovsdb/ovsdb-idlc
-       ovsdb/ovsdb-idlc c-idl-header $(srcdir)/vswitchd/vswitch.ovsidl > $@
+EXTRA_DIST += vswitchd/vswitch-idl.ovsidl
+BUILT_SOURCES += vswitchd/vswitch-idl.c vswitchd/vswitch-idl.h
+DISTCLEANFILES += vswitchd/vswitch-idl.c vswitchd/vswitch-idl.h
+noinst_DATA += vswitchd/vswitch-idl.ovsschema
+DISTCLEANFILES += vswitchd/vswitch-idl.ovsschema
diff --git a/vswitchd/vswitch-idl.ovsidl b/vswitchd/vswitch-idl.ovsidl
new file mode 100644 (file)
index 0000000..df50bf0
--- /dev/null
@@ -0,0 +1,175 @@
+//
+// This is an ovsdb-idl schema.  The OVSDB IDL compiler, ovsdb-idlc,
+// can translate it into an OVSDB schema (which simply entails
+// deleting some members from the schema) or C headers or source for
+// use with the IDL at runtime.
+//
+
+{"name": "ovs_vswitchd_db",
+ "comment": "Configuration for one Open vSwitch daemon.",
+ "idlPrefix": "ovsrec_",
+ "idlHeader": "\"vswitchd/vswitch-idl.h\"",
+ "tables": {
+   "Open_vSwitch": {
+     "comment": "Configuration for an Open vSwitch daemon.",
+     "columns": {
+       "bridges": {
+         "comment": "Set of bridges managed by the daemon.",
+         "type": {"key": "uuid", "keyRefTable": "Bridge",
+                  "min": 0, "max": "unlimited"}},
+       "management_id": {
+         "comment": "Exactly 12 hex digits that identify the daemon.",
+         "type": "string"},
+       "controller": {
+         "comment": "Default Controller used by bridges.",
+         "type": {"key": "uuid", "keyRefTable": "Controller", "min": 0, "max": 1}},
+       "ssl": {
+         "comment": "SSL used globally by the daemon.",
+         "type": {"key": "uuid", "keyRefTable": "SSL", "min": 0, "max": 1}}}},
+   "Bridge": {
+     "comment": "Configuration for a bridge within an Open_vSwitch.",
+     "columns": {
+       "name": {
+         "comment": "Bridge identifier.  Should be alphanumeric and no more than about 8 bytes long.  Must be unique among the names of ports, interfaces, and bridges on a host.",
+         "type": "string"},
+       "datapath_id": {
+         "comment": "OpenFlow datapath ID.  Exactly 12 hex digits.",
+         "type": {"key": "string", "min": 0, "max": 1}},
+       "hwaddr": {
+         "comment": "Ethernet address to use for bridge.  Exactly 12 hex digits in the form XX:XX:XX:XX:XX:XX.",
+         "type": {"key": "string", "min": 0, "max": 1}},
+       "ports": {
+         "comment": "Ports included in the bridge.",
+         "type": {"key": "uuid", "keyRefTable": "Port", "min": 0, "max": "unlimited"}},
+       "mirrors": {
+         "comment": "Port mirroring configuration.",
+         "type": {"key": "uuid", "keyRefTable": "Mirror", "min": 0, "max": "unlimited"}},
+       "netflow": {
+         "comment": "NetFlow configuration.",
+         "type": {"key": "uuid", "keyRefTable": "NetFlow", "min": 0, "max": "unlimited"}},
+       "controller": {
+         "comment": "OpenFlow controller.  If unset, defaults to that specified by the parent Open_vSwitch.",
+         "type": {"key": "uuid", "keyRefTable": "Controller", "min": 0, "max": 1}}}},
+   "Port": {
+     "comment": "A port within a Bridge.  May contain a single Interface or multiple (bonded) Interfaces.",
+     "columns": {
+       "name": {
+         "comment": "Port name.  Should be alphanumeric and no more than about 8 bytes long.    May be the same as the interface name, for non-bonded ports.  Must otherwise be unique among the names of ports, interfaces, and bridges on a host.",
+         "type": "string"},
+       "interfaces": {
+         "comment": "The Port's Interfaces.  If there is more than one, this is a bonded Port.",
+         "type": {"key": "uuid", "keyRefTable": "Interface", "min": 1, "max": "unlimited"}},
+       "trunks": {
+         "comment": "The 802.1Q VLAN(s) that this port trunks.  Should be empty if this port trunks all VLAN(s) or if this is not a trunk port.",
+         "type": {"key": "integer", "min": 0, "max": 4096}},
+       "tag": {
+         "comment": "This port's implicitly tagged VLAN.  Should be empty if this is a trunk port.",
+         "type": {"key": "integer", "min": 0, "max": 1}},
+       "updelay": {
+         "comment": "For a bonded port, the number of milliseconds for which carrier must stay up on an interface before the interface is considered to be up.  Ignored for non-bonded ports.",
+         "type": "integer"},
+       "downdelay": {
+         "comment": "For a bonded port, the number of milliseconds for which carrier must stay down on an interface before the interface is considered to be down.  Ignored for non-bonded ports.",
+         "type": "integer"}}},
+   "Interface": {
+     "comment": "An interface within a Port.",
+     "columns": {
+       "name": {
+         "comment": "Interface name.  Should be alphanumeric and no more than about 8 bytes long.  May be the same as the port name, for non-bonded ports.  Must otherwise be unique among the names of ports, interfaces, and bridges on a host.",
+         "type": "string"},
+       "internal": {
+         "comment": "An \"internal\" port is one that is implemented in software as a logical device.",
+         "type": "boolean"},
+       "ingress_policing_rate": {
+         "comment": "Maximum rate for data received on this interface, in kbps.  Set to 0 to disable policing.",
+         "type": "integer"},
+       "ingress_policing_burst": {
+         "comment": "Maximum burst size for data received on this interface, in kb.  The default burst size if set to 0 is 10 kb.",
+         "type": "integer"}}},
+   "Mirror": {
+     "comment": "A port mirror within a Bridge.",
+     "columns": {
+       "name": {
+         "comment": "Arbitrary identifier for the Mirror.",
+         "type": "string"},
+       "select_src_port": {
+         "comment": "Ports on which arriving packets are selected for mirroring.",
+         "type": {"key": "uuid", "keyRefTable": "Port", "min": 0, "max": "unlimited"}},
+       "select_dst_port": {
+         "comment": "Ports on which departing packets are selected for mirroring.",
+         "type": {"key": "uuid", "keyRefTable": "Port", "min": 0, "max": "unlimited"}},
+       "select_vlan": {
+         "comment": "VLANs on which packets are selected for mirroring.",
+         "type": {"key": "integer", "min": 0, "max": 4096}},
+       "output_port": {
+         "comment": "Output port for selected packets.  Mutually exclusive with output_vlan.",
+         "type": {"key": "uuid", "keyRefTable": "Port", "min": 0, "max": 1}},
+       "output_vlan": {
+         "comment": "Output VLAN for selected packets.  Mutually exclusive with output_port.",
+         "type": {"key": "integer", "min": 0, "max": 1}}}},
+   "NetFlow": {
+     "comment": "A NetFlow target.",
+     "columns": {
+       "target": {
+         "comment": "NetFlow target in the form \"IP:PORT\".",
+         "type": "string"},
+       "engine_type": {
+         "comment": "Engine type to use in NetFlow messages.  Defaults to datapath index if not specified.",
+         "type": "integer", "min":0, "max":1},
+       "engine_id": {
+         "comment": "Engine ID to use in NetFlow messages.  Defaults to datapath index if not specified.",
+         "type": "integer", "min":0, "max":1},
+       "add_id_to_interface": {
+         "comment": "Place least-significant 7 bits of engine ID into most significant bits of ingress and egress interface fields of NetFlow records?",
+         "type": "boolean"}}},
+   "Controller": {
+     "comment": "An OpenFlow controller.",
+     "columns": {
+       "target": {
+         "comment": "Connection method for controller, e.g. \"ssl:...\", \"tcp:...\".  The special string \"discover\" enables controller discovery.",
+         "type": "string"},
+       "max_backoff": {
+         "comment": "Maximum number of milliseconds to wait between connection attempts.  Default is implementation-specific.",
+         "type": {"key": "integer", "min": 0, "max": 1}},
+       "inactivity_probe": {
+         "comment": "Maximum number of milliseconds of idle time on connection to controller before sending an inactivity probe message.  Default is implementation-specific.",
+         "type": {"key": "integer", "min": 0, "max": 1}},
+       "fail_mode": {
+         "comment": "Either \"standalone\" or \"secure\", or empty to use the implementation's default.",
+         "type": {"key": "string", "min": 0, "max": 1}},
+       "discover_accept_regex": {
+         "comment": "If \"target\" is \"discover\", a POSIX extended regular expression against which the discovered controller location is validated.  If not specified, the default is implementation-specific.",
+         "type": {"key": "string", "min": 0, "max": 1}},
+       "discover_update_resolv_conf": {
+         "comment": "If \"target\" is \"discover\", whether to update /etc/resolv.conf when the controller is discovered.  If not specified, the default is implementation-specific.",
+         "type": {"key": "boolean", "min": 0, "max": 1}},
+       "connection_mode": {
+         "comment": "Either \"in-band\" or \"out-of-band\".  If not specified, the default is implementation-specific.",
+         "type": {"key": "string", "min": 0, "max": 1}},
+       "local_ip": {
+         "comment": "If \"target\" is not \"discover\", the IP address to configure on the local port.",
+         "type": {"key": "string", "min": 0, "max": 1}},
+       "local_netmask": {
+         "comment": "If \"target\" is not \"discover\", the IP netmask to configure on the local port.",
+         "type": {"key": "string", "min": 0, "max": 1}},
+       "local_gateway": {
+         "comment": "If \"target\" is not \"discover\", the IP gateway to configure on the local port.",
+         "type": {"key": "string", "min": 0, "max": 1}},
+       "controller_rate_limit": {
+         "comment": "The maximum rate at which packets will be forwarded to the OpenFlow controller, in packets per second.  If not specified, the default is implementation-specific.",
+         "type": {"key": "integer", "min": 0, "max": 1}},
+       "controller_burst_limit": {
+         "comment": "The maximum number of unused packet credits that the bridge will allow to accumulate, in packets.  If not specified, the default is implementation-specific.",
+         "type": {"key": "integer", "min": 0, "max": 1}}}},
+   "SSL": {
+     "comment": "SSL configuration for an Open_vSwitch.",
+     "columns": {
+       "private_key": {
+         "comment": "Name of a PEM file containing the private key used as the switch's identity for SSL connections to the controller.",
+         "type": "string"},
+       "certificate": {
+         "comment": "Name of a PEM file containing a certificate, signed by the certificate authority (CA) used by the controller and manager, that certifies the switch's private key, identifying a trustworthy switch.",
+         "type": "string"},
+       "ca_cert": {
+         "comment": "Name of a PEM file containing the CA certificate used to verify that the switch is connected to a trustworthy controller.",
+         "type": "string"}}}}}
diff --git a/vswitchd/vswitch.ovsidl b/vswitchd/vswitch.ovsidl
deleted file mode 100644 (file)
index 263d7fd..0000000
+++ /dev/null
@@ -1,173 +0,0 @@
-//
-// This is an ovsdb-idl schema.  The OVSDB IDL compiler, ovsdb-idlc,
-// can translate it into an OVSDB schema (which simply entails
-// deleting some members from the schema) or C headers or source for
-// use with the IDL at runtime.
-//
-
-{"name": "ovs_vswitchd_db",
- "comment": "Configuration for one Open vSwitch daemon.",
- "tables": {
-   "Open_vSwitch": {
-     "comment": "Configuration for an Open vSwitch daemon.",
-     "columns": {
-       "bridges": {
-         "comment": "Set of bridges managed by the daemon.",
-         "type": {"key": "uuid", "keyRefTable": "Bridge",
-                  "min": 0, "max": "unlimited"}},
-       "management_id": {
-         "comment": "Exactly 12 hex digits that identify the daemon.",
-         "type": "string"},
-       "controller": {
-         "comment": "Default Controller used by bridges.",
-         "type": {"key": "uuid", "keyRefTable": "Controller", "min": 0, "max": 1}},
-       "ssl": {
-         "comment": "SSL used globally by the daemon.",
-         "type": {"key": "uuid", "keyRefTable": "SSL", "min": 0, "max": 1}}}},
-   "Bridge": {
-     "comment": "Configuration for a bridge within an Open_vSwitch.",
-     "columns": {
-       "name": {
-         "comment": "Bridge identifier.  Should be alphanumeric and no more than about 8 bytes long.  Must be unique among the names of ports, interfaces, and bridges on a host.",
-         "type": "string"},
-       "datapath_id": {
-         "comment": "OpenFlow datapath ID.  Exactly 12 hex digits.",
-         "type": {"key": "string", "min": 0, "max": 1}},
-       "hwaddr": {
-         "comment": "Ethernet address to use for bridge.  Exactly 12 hex digits in the form XX:XX:XX:XX:XX:XX.",
-         "type": {"key": "string", "min": 0, "max": 1}},
-       "ports": {
-         "comment": "Ports included in the bridge.",
-         "type": {"key": "uuid", "keyRefTable": "Port", "min": 0, "max": "unlimited"}},
-       "mirrors": {
-         "comment": "Port mirroring configuration.",
-         "type": {"key": "uuid", "keyRefTable": "Mirror", "min": 0, "max": "unlimited"}},
-       "netflow": {
-         "comment": "NetFlow configuration.",
-         "type": {"key": "uuid", "keyRefTable": "NetFlow", "min": 0, "max": "unlimited"}},
-       "controller": {
-         "comment": "OpenFlow controller.  If unset, defaults to that specified by the parent Open_vSwitch.",
-         "type": {"key": "uuid", "keyRefTable": "Controller", "min": 0, "max": 1}}}},
-   "Port": {
-     "comment": "A port within a Bridge.  May contain a single Interface or multiple (bonded) Interfaces.",
-     "columns": {
-       "name": {
-         "comment": "Port name.  Should be alphanumeric and no more than about 8 bytes long.    May be the same as the interface name, for non-bonded ports.  Must otherwise be unique among the names of ports, interfaces, and bridges on a host.",
-         "type": "string"},
-       "interfaces": {
-         "comment": "The Port's Interfaces.  If there is more than one, this is a bonded Port.",
-         "type": {"key": "uuid", "keyRefTable": "Interface", "min": 1, "max": "unlimited"}},
-       "trunks": {
-         "comment": "The 802.1Q VLAN(s) that this port trunks.  Should be empty if this port trunks all VLAN(s) or if this is not a trunk port.",
-         "type": {"key": "integer", "min": 0, "max": 4096}},
-       "tag": {
-         "comment": "This port's implicitly tagged VLAN.  Should be empty if this is a trunk port.",
-         "type": {"key": "integer", "min": 0, "max": 1}},
-       "updelay": {
-         "comment": "For a bonded port, the number of milliseconds for which carrier must stay up on an interface before the interface is considered to be up.  Ignored for non-bonded ports.",
-         "type": "integer"},
-       "downdelay": {
-         "comment": "For a bonded port, the number of milliseconds for which carrier must stay down on an interface before the interface is considered to be down.  Ignored for non-bonded ports.",
-         "type": "integer"}}},
-   "Interface": {
-     "comment": "An interface within a Port.",
-     "columns": {
-       "name": {
-         "comment": "Interface name.  Should be alphanumeric and no more than about 8 bytes long.  May be the same as the port name, for non-bonded ports.  Must otherwise be unique among the names of ports, interfaces, and bridges on a host.",
-         "type": "string"},
-       "internal": {
-         "comment": "An \"internal\" port is one that is implemented in software as a logical device.",
-         "type": "boolean"},
-       "ingress_policing_rate": {
-         "comment": "Maximum rate for data received on this interface, in kbps.  Set to 0 to disable policing.",
-         "type": "integer"},
-       "ingress_policing_burst": {
-         "comment": "Maximum burst size for data received on this interface, in kb.  The default burst size if set to 0 is 10 kb.",
-         "type": "integer"}}},
-   "Mirror": {
-     "comment": "A port mirror within a Bridge.",
-     "columns": {
-       "name": {
-         "comment": "Arbitrary identifier for the Mirror.",
-         "type": "string"},
-       "select_src_port": {
-         "comment": "Ports on which arriving packets are selected for mirroring.",
-         "type": {"key": "uuid", "keyRefTable": "Port", "min": 0, "max": "unlimited"}},
-       "select_dst_port": {
-         "comment": "Ports on which departing packets are selected for mirroring.",
-         "type": {"key": "uuid", "keyRefTable": "Port", "min": 0, "max": "unlimited"}},
-       "select_vlan": {
-         "comment": "VLANs on which packets are selected for mirroring.",
-         "type": {"key": "integer", "min": 0, "max": 4096}},
-       "output_port": {
-         "comment": "Output port for selected packets.  Mutually exclusive with output_vlan.",
-         "type": {"key": "uuid", "keyRefTable": "Port", "min": 0, "max": 1}},
-       "output_vlan": {
-         "comment": "Output VLAN for selected packets.  Mutually exclusive with output_port.",
-         "type": {"key": "integer", "min": 0, "max": 1}}}},
-   "NetFlow": {
-     "comment": "A NetFlow target.",
-     "columns": {
-       "target": {
-         "comment": "NetFlow target in the form \"IP:PORT\".",
-         "type": "string"},
-       "engine_type": {
-         "comment": "Engine type to use in NetFlow messages.  Defaults to datapath index if not specified.",
-         "type": "integer", "min":0, "max":1},
-       "engine_id": {
-         "comment": "Engine ID to use in NetFlow messages.  Defaults to datapath index if not specified.",
-         "type": "integer", "min":0, "max":1},
-       "add_id_to_interface": {
-         "comment": "Place least-significant 7 bits of engine ID into most significant bits of ingress and egress interface fields of NetFlow records?",
-         "type": "boolean"}}},
-   "Controller": {
-     "comment": "An OpenFlow controller.",
-     "columns": {
-       "target": {
-         "comment": "Connection method for controller, e.g. \"ssl:...\", \"tcp:...\".  The special string \"discover\" enables controller discovery.",
-         "type": "string"},
-       "max_backoff": {
-         "comment": "Maximum number of milliseconds to wait between connection attempts.  Default is implementation-specific.",
-         "type": {"key": "integer", "min": 0, "max": 1}},
-       "inactivity_probe": {
-         "comment": "Maximum number of milliseconds of idle time on connection to controller before sending an inactivity probe message.  Default is implementation-specific.",
-         "type": {"key": "integer", "min": 0, "max": 1}},
-       "fail_mode": {
-         "comment": "Either \"standalone\" or \"secure\", or empty to use the implementation's default.",
-         "type": {"key": "string", "min": 0, "max": 1}},
-       "discover_accept_regex": {
-         "comment": "If \"target\" is \"discover\", a POSIX extended regular expression against which the discovered controller location is validated.  If not specified, the default is implementation-specific.",
-         "type": {"key": "string", "min": 0, "max": 1}},
-       "discover_update_resolv_conf": {
-         "comment": "If \"target\" is \"discover\", whether to update /etc/resolv.conf when the controller is discovered.  If not specified, the default is implementation-specific.",
-         "type": {"key": "boolean", "min": 0, "max": 1}},
-       "connection_mode": {
-         "comment": "Either \"in-band\" or \"out-of-band\".  If not specified, the default is implementation-specific.",
-         "type": {"key": "string", "min": 0, "max": 1}},
-       "local_ip": {
-         "comment": "If \"target\" is not \"discover\", the IP address to configure on the local port.",
-         "type": {"key": "string", "min": 0, "max": 1}},
-       "local_netmask": {
-         "comment": "If \"target\" is not \"discover\", the IP netmask to configure on the local port.",
-         "type": {"key": "string", "min": 0, "max": 1}},
-       "local_gateway": {
-         "comment": "If \"target\" is not \"discover\", the IP gateway to configure on the local port.",
-         "type": {"key": "string", "min": 0, "max": 1}},
-       "controller_rate_limit": {
-         "comment": "The maximum rate at which packets will be forwarded to the OpenFlow controller, in packets per second.  If not specified, the default is implementation-specific.",
-         "type": {"key": "integer", "min": 0, "max": 1}},
-       "controller_burst_limit": {
-         "comment": "The maximum number of unused packet credits that the bridge will allow to accumulate, in packets.  If not specified, the default is implementation-specific.",
-         "type": {"key": "integer", "min": 0, "max": 1}}}},
-   "SSL": {
-     "comment": "SSL configuration for an Open_vSwitch.",
-     "columns": {
-       "private_key": {
-         "comment": "Name of a PEM file containing the private key used as the switch's identity for SSL connections to the controller.",
-         "type": "string"},
-       "certificate": {
-         "comment": "Name of a PEM file containing a certificate, signed by the certificate authority (CA) used by the controller and manager, that certifies the switch's private key, identifying a trustworthy switch.",
-         "type": "string"},
-       "ca_cert": {
-         "comment": "Name of a PEM file containing the CA certificate used to verify that the switch is connected to a trustworthy controller.",
-         "type": "string"}}}}}