ovsdb: Extend "monitor" to select different operations in a single table.
authorBen Pfaff <blp@nicira.com>
Thu, 1 Jul 2010 16:47:46 +0000 (09:47 -0700)
committerBen Pfaff <blp@nicira.com>
Thu, 1 Jul 2010 16:47:46 +0000 (09:47 -0700)
Until now, "monitor" has only allowed the client to choose the kinds of
changes that will be monitored on a per-table basis.  However, it makes
sense to be able to choose operations on a per-column basis.  The
immediate need for this is to make sure that the final statistics of
deleted Interface records are known at time of deletion, even though the
intermediate values of the statistics are not important.

CC: Jeremy Stribling <strib@nicira.com>
ovsdb/SPECS
ovsdb/jsonrpc-server.c
ovsdb/ovsdb-client.1.in
ovsdb/ovsdb-client.c
tests/ovsdb-monitor.at

index 524e52f..d9c92de 100644 (file)
@@ -438,8 +438,10 @@ Request object members:
     "params": [<db-name>, <json-value>, <monitor-requests>]   required
     "id": <nonnull-json-value>                                required
 
-<monitor-requests> is an object that maps from a table name to a
-<monitor-request>.
+<monitor-requests> is an object that maps from a table name to an
+array of <monitor-request> objects.  For backward compatibility, a
+single <monitor-request> may be used instead of an array; it is
+treated as a single-element array.
 
 Each <monitor-request> is an object with the following members:
 
@@ -460,15 +462,16 @@ Response object members:
     "id": same "id" as request
 
 This JSON-RPC request enables a client to replicate tables or subsets
-of tables within database <db-name>.  Each <monitor-request> specifies
-a table to be replicated.  The JSON-RPC response to the "monitor"
-includes the initial contents of each table.  Afterward, when changes
-to those tables are committed, the changes are automatically sent to
-the client using the "update" monitor notification.  This monitoring
-persists until the JSON-RPC session terminates or until the client
-sends a "monitor_cancel" JSON-RPC request.
+of tables within database <db-name>.  Each element of
+<monitor-requests> specifies a table to be replicated.  The JSON-RPC
+response to the "monitor" includes the initial contents of each table,
+unless disabled (see below).  Afterward, when changes to those tables
+are committed, the changes are automatically sent to the client using
+the "update" monitor notification.  This monitoring persists until the
+JSON-RPC session terminates or until the client sends a
+"monitor_cancel" JSON-RPC request.
 
-Each <monitor-request> describes how to monitor a table:
+Each <monitor-request> describes how to monitor columns in a table:
 
     The circumstances in which an "update" notification is sent for a
     row within the table are determined by <monitor-select>:
@@ -486,8 +489,13 @@ Each <monitor-request> describes how to monitor a table:
         sent whenever when a row in the table is modified.
 
     The "columns" member specifies the columns whose values are
-    monitored.  If "columns" is omitted, all columns in the table,
-    except for "_uuid", are monitored.
+    monitored.  It must not contain duplicates.  If "columns" is
+    omitted, all columns in the table, except for "_uuid", are
+    monitored.
+
+If there is more than one <monitor-request> in an array of them, then
+each <monitor-request> in the array should specify both "columns" and
+"select", and the "columns" must be non-overlapping sets.
 
 The "result" in the JSON-RPC response to the "monitor" request is a
 <table-updates> object (see below) that contains the contents of the
index 18f59c5..bc717e4 100644 (file)
@@ -619,12 +619,26 @@ enum ovsdb_jsonrpc_monitor_selection {
     OJMS_MODIFY = 1 << 3        /* Modified rows. */
 };
 
+/* A particular column being monitored. */
+struct ovsdb_jsonrpc_monitor_column {
+    const struct ovsdb_column *column;
+    enum ovsdb_jsonrpc_monitor_selection select;
+};
+
+/* A particular table being monitored. */
 struct ovsdb_jsonrpc_monitor_table {
     const struct ovsdb_table *table;
+
+    /* This is the union (bitwise-OR) of the 'select' values in all of the
+     * members of 'columns' below. */
     enum ovsdb_jsonrpc_monitor_selection select;
-    struct ovsdb_column_set columns;
+
+    /* Columns being monitored. */
+    struct ovsdb_jsonrpc_monitor_column *columns;
+    size_t n_columns;
 };
 
+/* A collection of tables being monitored. */
 struct ovsdb_jsonrpc_monitor {
     struct ovsdb_replica replica;
     struct ovsdb_jsonrpc_session *session;
@@ -667,6 +681,118 @@ ovsdb_jsonrpc_monitor_find(struct ovsdb_jsonrpc_session *s,
     return NULL;
 }
 
+static void
+ovsdb_jsonrpc_add_monitor_column(struct ovsdb_jsonrpc_monitor_table *mt,
+                                 const struct ovsdb_column *column,
+                                 enum ovsdb_jsonrpc_monitor_selection select,
+                                 size_t *allocated_columns)
+{
+    struct ovsdb_jsonrpc_monitor_column *c;
+
+    if (mt->n_columns >= *allocated_columns) {
+        mt->columns = x2nrealloc(mt->columns, allocated_columns,
+                                 sizeof *mt->columns);
+    }
+
+    c = &mt->columns[mt->n_columns++];
+    c->column = column;
+    c->select = select;
+}
+
+static int
+compare_ovsdb_jsonrpc_monitor_column(const void *a_, const void *b_)
+{
+    const struct ovsdb_jsonrpc_monitor_column *a = a_;
+    const struct ovsdb_jsonrpc_monitor_column *b = b_;
+
+    return a->column < b->column ? -1 : a->column > b->column;
+}
+
+static struct ovsdb_error * WARN_UNUSED_RESULT
+ovsdb_jsonrpc_parse_monitor_request(struct ovsdb_jsonrpc_monitor_table *mt,
+                                    const struct json *monitor_request,
+                                    size_t *allocated_columns)
+{
+    const struct ovsdb_table_schema *ts = mt->table->schema;
+    enum ovsdb_jsonrpc_monitor_selection select;
+    const struct json *columns, *select_json;
+    struct ovsdb_parser parser;
+    struct ovsdb_error *error;
+
+    ovsdb_parser_init(&parser, monitor_request, "table %s", ts->name);
+    columns = ovsdb_parser_member(&parser, "columns", OP_ARRAY | OP_OPTIONAL);
+    select_json = ovsdb_parser_member(&parser, "select",
+                                      OP_OBJECT | OP_OPTIONAL);
+    error = ovsdb_parser_finish(&parser);
+    if (error) {
+        return error;
+    }
+
+    if (select_json) {
+        select = 0;
+        ovsdb_parser_init(&parser, select_json, "table %s select", ts->name);
+        if (parse_bool(&parser, "initial", true)) {
+            select |= OJMS_INITIAL;
+        }
+        if (parse_bool(&parser, "insert", true)) {
+            select |= OJMS_INSERT;
+        }
+        if (parse_bool(&parser, "delete", true)) {
+            select |= OJMS_DELETE;
+        }
+        if (parse_bool(&parser, "modify", true)) {
+            select |= OJMS_MODIFY;
+        }
+        error = ovsdb_parser_finish(&parser);
+        if (error) {
+            return error;
+        }
+    } else {
+        select = OJMS_INITIAL | OJMS_INSERT | OJMS_DELETE | OJMS_MODIFY;
+    }
+    mt->select |= select;
+
+    if (columns) {
+        size_t i;
+
+        if (columns->type != JSON_ARRAY) {
+            return ovsdb_syntax_error(columns, NULL,
+                                      "array of column names expected");
+        }
+
+        for (i = 0; i < columns->u.array.n; i++) {
+            const struct ovsdb_column *column;
+            const char *s;
+
+            if (columns->u.array.elems[i]->type != JSON_STRING) {
+                return ovsdb_syntax_error(columns, NULL,
+                                          "array of column names expected");
+            }
+
+            s = columns->u.array.elems[i]->u.string;
+            column = shash_find_data(&mt->table->schema->columns, s);
+            if (!column) {
+                return ovsdb_syntax_error(columns, NULL, "%s is not a valid "
+                                          "column name", s);
+            }
+            ovsdb_jsonrpc_add_monitor_column(mt, column, select,
+                                             allocated_columns);
+        }
+    } else {
+        struct shash_node *node;
+
+        SHASH_FOR_EACH (node, &ts->columns) {
+            const struct ovsdb_column *column = node->data;
+            if (column->index != OVSDB_COL_UUID) {
+                ovsdb_jsonrpc_add_monitor_column(mt, column, select,
+                                                 allocated_columns);
+            }
+        }
+    }
+
+    return NULL;
+}
+
 static struct json *
 ovsdb_jsonrpc_monitor_create(struct ovsdb_jsonrpc_session *s,
                              struct json *params)
@@ -705,8 +831,9 @@ ovsdb_jsonrpc_monitor_create(struct ovsdb_jsonrpc_session *s,
     SHASH_FOR_EACH (node, json_object(monitor_requests)) {
         const struct ovsdb_table *table;
         struct ovsdb_jsonrpc_monitor_table *mt;
-        const struct json *columns_json, *select_json;
-        struct ovsdb_parser parser;
+        size_t allocated_columns;
+        const struct json *mr_value;
+        size_t i;
 
         table = ovsdb_get_table(s->remote->server->db, node->name);
         if (!table) {
@@ -717,55 +844,37 @@ ovsdb_jsonrpc_monitor_create(struct ovsdb_jsonrpc_session *s,
 
         mt = xzalloc(sizeof *mt);
         mt->table = table;
-        mt->select = OJMS_INITIAL | OJMS_INSERT | OJMS_DELETE | OJMS_MODIFY;
-        ovsdb_column_set_init(&mt->columns);
         shash_add(&m->tables, table->schema->name, mt);
 
-        ovsdb_parser_init(&parser, node->data, "table %s", node->name);
-        columns_json = ovsdb_parser_member(&parser, "columns",
-                                           OP_ARRAY | OP_OPTIONAL);
-        select_json = ovsdb_parser_member(&parser, "select",
-                                          OP_OBJECT | OP_OPTIONAL);
-        error = ovsdb_parser_finish(&parser);
-        if (error) {
-            goto error;
-        }
-
-        if (columns_json) {
-            error = ovsdb_column_set_from_json(columns_json, table,
-                                               &mt->columns);
-            if (error) {
-                goto error;
+        /* Parse columns. */
+        mr_value = node->data;
+        allocated_columns = 0;
+        if (mr_value->type == JSON_ARRAY) {
+            const struct json_array *array = &mr_value->u.array;
+
+            for (i = 0; i < array->n; i++) {
+                error = ovsdb_jsonrpc_parse_monitor_request(
+                    mt, array->elems[i], &allocated_columns);
+                if (error) {
+                    goto error;
+                }
             }
         } else {
-            struct shash_node *node;
-
-            SHASH_FOR_EACH (node, &table->schema->columns) {
-                const struct ovsdb_column *column = node->data;
-                if (column->index != OVSDB_COL_UUID) {
-                    ovsdb_column_set_add(&mt->columns, column);
-                }
+            error = ovsdb_jsonrpc_parse_monitor_request(
+                mt, mr_value, &allocated_columns);
+            if (error) {
+                goto error;
             }
         }
 
-        if (select_json) {
-            mt->select = 0;
-            ovsdb_parser_init(&parser, select_json, "table %s select",
-                              table->schema->name);
-            if (parse_bool(&parser, "initial", true)) {
-                mt->select |= OJMS_INITIAL;
-            }
-            if (parse_bool(&parser, "insert", true)) {
-                mt->select |= OJMS_INSERT;
-            }
-            if (parse_bool(&parser, "delete", true)) {
-                mt->select |= OJMS_DELETE;
-            }
-            if (parse_bool(&parser, "modify", true)) {
-                mt->select |= OJMS_MODIFY;
-            }
-            error = ovsdb_parser_finish(&parser);
-            if (error) {
+        /* Check for duplicate columns. */
+        qsort(mt->columns, mt->n_columns, sizeof *mt->columns,
+              compare_ovsdb_jsonrpc_monitor_column);
+        for (i = 1; i < mt->n_columns; i++) {
+            if (mt->columns[i].column == mt->columns[i - 1].column) {
+                error = ovsdb_syntax_error(mr_value, NULL, "column %s "
+                                           "mentioned more than once",
+                                           mt->columns[i].column->name);
                 goto error;
             }
         }
@@ -871,11 +980,18 @@ ovsdb_jsonrpc_monitor_change_cb(const struct ovsdb_row *old,
 
     old_json = new_json = NULL;
     n_changed = 0;
-    for (i = 0; i < aux->mt->columns.n_columns; i++) {
-        const struct ovsdb_column *column = aux->mt->columns.columns[i];
-        unsigned int idx = column->index;
+    for (i = 0; i < aux->mt->n_columns; i++) {
+        const struct ovsdb_jsonrpc_monitor_column *c = &aux->mt->columns[i];
+        const struct ovsdb_column *column = c->column;
+        unsigned int idx = c->column->index;
         bool column_changed = false;
 
+        if (!(type & c->select)) {
+            /* We don't care about this type of change for this particular
+             * column (but we will care about it for some other column). */
+            continue;
+        }
+
         if (type == OJMS_MODIFY) {
             column_changed = bitmap_is_set(changed, idx);
             n_changed += column_changed;
@@ -999,7 +1115,7 @@ ovsdb_jsonrpc_monitor_destroy(struct ovsdb_replica *replica)
     json_destroy(m->monitor_id);
     SHASH_FOR_EACH (node, &m->tables) {
         struct ovsdb_jsonrpc_monitor_table *mt = node->data;
-        ovsdb_column_set_destroy(&mt->columns);
+        free(mt->columns);
         free(mt);
     }
     shash_destroy(&m->tables);
index a74b87e..c7a1fea 100644 (file)
@@ -25,8 +25,7 @@ ovsdb\-client \- command-line interface to \fBovsdb-server\fR(1)
 \fBovsdb\-client \fR[\fIoptions\fR] \fBdump\fI server database\fR
 .br
 \fBovsdb\-client \fR[\fIoptions\fR] \fBmonitor\fI server database table\fR
-[\fIcolumn\fR[\fB,\fIcolumn\fR]...]
-[\fIselect\fR[\fB,\fIselect\fR]...]
+[\fIcolumn\fR[\fB,\fIcolumn\fR]...]...
 .br
 \fBovsdb\-client help\fR
 .IP "Output formatting options:"
@@ -79,23 +78,29 @@ operations, and prints the received reply on stdout.
 Connects to \fIserver\fR, retrieves all of the data in \fIdatabase\fR,
 and prints it on stdout as a series of tables.
 .
-.IP "\fBmonitor\fI server database table\fR [\fIcolumn\fR[\fB,\fIcolumn\fR]...] [\fIselect\fR[\fB,\fIselect\fR]...]"
+.IP "\fBmonitor\fI server database table\fR [\fIcolumn\fR[\fB,\fIcolumn\fR]...]..."
 Connects to \fIserver\fR and monitors the contents of \fItable\fR in
 \fIdatabase\fR.  By default, the initial contents of \fItable\fR are
 printed, followed by each change as it occurs.  If at least one
-\fIcolumn\fR is specified, only those columns are monitored.  If at
-least one \fIselect\fR is specified, they are interpreted as follows:
+\fIcolumn\fR is specified, only those columns are monitored.  The
+following \fIcolumn\fR names have special meanings:
 .RS
-.IP "\fBinitial\fR"
-Print the initial contents of the specified columns.
-.IP "\fBinsert\fR"
-Print newly inserted rows.
-.IP "\fBdelete\fR"
-Print deleted rows.
-.IP "\fBmodify\fR"
-Print old and new values of modified rows.
+.IP "\fB!initial\fR"
+Do not print the initial contents of the specified columns.
+.IP "\fB!insert\fR"
+Do not print newly inserted rows.
+.IP "\fB!delete\fR"
+Do not print deleted rows.
+.IP "\fB!modify\fR"
+Do not print modifications to existing rows.
 .RE
 .IP
+Multiple [\fIcolumn\fR[\fB,\fIcolumn\fR]...] groups may be specified
+as separate arguments, e.g. to apply different reporting parameters to
+each group.  Whether multiple groups or only a single group is
+specified, any given column may only be mentioned once on the command
+line.
+.IP
 If \fB\-\-detach\fR is used with \fBmonitor\fR, then \fBovsdb\-client\fR
 detaches after it has successfully received and printed the initial
 contents of \fItable\fR.
index 0199b0e..fef87d1 100644 (file)
@@ -188,9 +188,10 @@ usage(void)
            "\n  transact SERVER TRANSACTION\n"
            "    run TRANSACTION (a JSON array of operations) on SERVER\n"
            "    and print the results as JSON on stdout\n"
-           "\n  monitor SERVER DATABASE TABLE [COLUMN,...] [SELECT,...]\n"
-           "    monitor contents of (COLUMNs in) TABLE in DATABASE on SERVER\n"
-           "    Valid SELECTs are: initial, insert, delete, modify\n"
+           "\n  monitor SERVER DATABASE TABLE [COLUMN,...]...\n"
+           "    monitor contents of COLUMNs in TABLE in DATABASE on SERVER.\n"
+           "    COLUMNs may include !initial, !insert, !delete, !modify\n"
+           "    to avoid seeing the specified kinds of changes.\n"
            "\n  dump SERVER DATABASE\n"
            "    dump contents of DATABASE on SERVER to stdout\n",
            program_name, program_name);
@@ -879,44 +880,56 @@ monitor_print(struct json *table_updates,
 }
 
 static void
-do_monitor(int argc, char *argv[])
+add_column(const char *server, const struct ovsdb_column *column,
+           struct ovsdb_column_set *columns, struct json *columns_json)
 {
-    const char *server = argv[1];
-    const char *database = argv[2];
-    const char *table_name = argv[3];
-    struct ovsdb_column_set columns = OVSDB_COLUMN_SET_INITIALIZER;
-    struct ovsdb_table_schema *table;
-    struct ovsdb_schema *schema;
-    struct jsonrpc_msg *request;
-    struct jsonrpc *rpc;
-    struct json *select, *monitor, *monitor_request, *monitor_requests,
-        *request_id;
-
-    rpc = open_jsonrpc(server);
-
-    schema = fetch_schema_from_rpc(rpc, database);
-    table = shash_find_data(&schema->tables, table_name);
-    if (!table) {
-        ovs_fatal(0, "%s: %s does not have a table named \"%s\"",
-                  server, database, table_name);
+    if (ovsdb_column_set_contains(columns, column->index)) {
+        ovs_fatal(0, "%s: column \"%s\" mentioned multiple times",
+                  server, column->name);
     }
+    ovsdb_column_set_add(columns, column);
+    json_array_add(columns_json, json_string_create(column->name));
+}
 
-    if (argc >= 5 && *argv[4] != '\0') {
-        char *save_ptr = NULL;
-        char *token;
-
-        for (token = strtok_r(argv[4], ",", &save_ptr); token != NULL;
-             token = strtok_r(NULL, ",", &save_ptr)) {
+static struct json *
+parse_monitor_columns(char *arg, const char *server, const char *database,
+                      const struct ovsdb_table_schema *table,
+                      struct ovsdb_column_set *columns)
+{
+    bool initial, insert, delete, modify;
+    struct json *mr, *columns_json;
+    char *save_ptr = NULL;
+    char *token;
+
+    mr = json_object_create();
+    columns_json = json_array_create_empty();
+    json_object_put(mr, "columns", columns_json);
+
+    initial = insert = delete = modify = true;
+    for (token = strtok_r(arg, ",", &save_ptr); token != NULL;
+         token = strtok_r(NULL, ",", &save_ptr)) {
+        if (!strcmp(token, "!initial")) {
+            initial = false;
+        } else if (!strcmp(token, "!insert")) {
+            insert = false;
+        } else if (!strcmp(token, "!delete")) {
+            delete = false;
+        } else if (!strcmp(token, "!modify")) {
+            modify = false;
+        } else {
             const struct ovsdb_column *column;
+
             column = ovsdb_table_schema_get_column(table, token);
             if (!column) {
                 ovs_fatal(0, "%s: table \"%s\" in %s does not have a "
                           "column named \"%s\"",
-                          server, table_name, database, token);
+                          server, table->name, database, token);
             }
-            ovsdb_column_set_add(&columns, column);
+            add_column(server, column, columns, columns_json);
         }
-    } else {
+    }
+
+    if (columns_json->u.array.n == 0) {
         const struct shash_node **nodes;
         size_t i, n;
 
@@ -926,53 +939,71 @@ do_monitor(int argc, char *argv[])
             const struct ovsdb_column *column = nodes[i]->data;
             if (column->index != OVSDB_COL_UUID
                 && column->index != OVSDB_COL_VERSION) {
-                ovsdb_column_set_add(&columns, column);
+                add_column(server, column, columns, columns_json);
             }
         }
         free(nodes);
 
-        ovsdb_column_set_add(&columns,
-                             ovsdb_table_schema_get_column(table, "_version"));
+        add_column(server, ovsdb_table_schema_get_column(table,"_version"),
+                   columns, columns_json);
     }
 
-    if (argc >= 6 && *argv[5] != '\0') {
-        bool initial, insert, delete, modify;
-        char *save_ptr = NULL;
-        char *token;
-
-        initial = insert = delete = modify = false;
-
-        for (token = strtok_r(argv[5], ",", &save_ptr); token != NULL;
-             token = strtok_r(NULL, ",", &save_ptr)) {
-            if (!strcmp(token, "initial")) {
-                initial = true;
-            } else if (!strcmp(token, "insert")) {
-                insert = true;
-            } else if (!strcmp(token, "delete")) {
-                delete = true;
-            } else if (!strcmp(token, "modify")) {
-                modify = true;
-            }
-        }
-
-        select = json_object_create();
+    if (!initial || !insert || !delete || !modify) {
+        struct json *select = json_object_create();
         json_object_put(select, "initial", json_boolean_create(initial));
         json_object_put(select, "insert", json_boolean_create(insert));
         json_object_put(select, "delete", json_boolean_create(delete));
         json_object_put(select, "modify", json_boolean_create(modify));
-    } else {
-        select = NULL;
+        json_object_put(mr, "select", select);
+    }
+
+    return mr;
+}
+
+static void
+do_monitor(int argc, char *argv[])
+{
+    const char *server = argv[1];
+    const char *database = argv[2];
+    const char *table_name = argv[3];
+    struct ovsdb_column_set columns = OVSDB_COLUMN_SET_INITIALIZER;
+    struct ovsdb_table_schema *table;
+    struct ovsdb_schema *schema;
+    struct jsonrpc_msg *request;
+    struct jsonrpc *rpc;
+    struct json *monitor, *monitor_request_array,
+        *monitor_requests, *request_id;
+
+    rpc = open_jsonrpc(server);
+
+    schema = fetch_schema_from_rpc(rpc, database);
+    table = shash_find_data(&schema->tables, table_name);
+    if (!table) {
+        ovs_fatal(0, "%s: %s does not have a table named \"%s\"",
+                  server, database, table_name);
     }
 
-    monitor_request = json_object_create();
-    json_object_put(monitor_request,
-                    "columns", ovsdb_column_set_to_json(&columns));
-    if (select) {
-        json_object_put(monitor_request, "select", select);
+    monitor_request_array = json_array_create_empty();
+    if (argc > 4) {
+        int i;
+
+        for (i = 4; i < argc; i++) {
+            json_array_add(
+                monitor_request_array,
+                parse_monitor_columns(argv[i], server, database, table,
+                                      &columns));
+        }
+    } else {
+        /* Allocate a writable empty string since parse_monitor_columns() is
+         * going to strtok() it and that's risky with literal "". */
+        char empty[] = "";
+        json_array_add(
+            monitor_request_array,
+            parse_monitor_columns(empty, server, database, table, &columns));
     }
 
     monitor_requests = json_object_create();
-    json_object_put(monitor_requests, table_name, monitor_request);
+    json_object_put(monitor_requests, table_name, monitor_request_array);
 
     monitor = json_array_create_3(json_string_create(database),
                                   json_null_create(), monitor_requests);
@@ -1266,7 +1297,7 @@ static const struct command all_commands[] = {
     { "list-tables", 2, 2, do_list_tables },
     { "list-columns", 2, 3, do_list_columns },
     { "transact", 2, 2, do_transact },
-    { "monitor", 3, 5, do_monitor },
+    { "monitor", 3, INT_MAX, do_monitor },
     { "dump", 2, 2, do_dump },
     { "help", 0, INT_MAX, do_help },
     { NULL, 0, 0, NULL },
index 110a313..e382905 100644 (file)
@@ -1,14 +1,13 @@
 AT_BANNER([OVSDB -- ovsdb-server monitors])
 
 # OVSDB_CHECK_MONITOR(TITLE, SCHEMA, [PRE-MONITOR-TXN], DB, TABLE,
-#                     TRANSACTIONS, OUTPUT, [SELECT], [KEYWORDS])
+#                     TRANSACTIONS, OUTPUT, [COLUMNS], [KEYWORDS])
 #
 # Creates a database with the given SCHEMA, starts an ovsdb-server on
 # that database, and runs each of the TRANSACTIONS (which should be a
 # quoted list of quoted strings) against it with ovsdb-client one at a
-# time.  SELECT, if specified, is passed to ovsdb-client as the
-# operations to select.  It should be a comma-separated list of
-# "initial", "insert", "delete", or "modify" keywords.
+# time.  COLUMNS, if specified, is passed to ovsdb-client as the set
+# of columns and operations to select.
 #
 # Checks that the overall output is OUTPUT, but UUIDs in the output
 # are replaced by markers of the form <N> where N is a number.  The
@@ -28,7 +27,7 @@ m4_define([OVSDB_CHECK_MONITOR],
    AT_CAPTURE_FILE([ovsdb-server-log])
    AT_CHECK([ovsdb-server --detach --pidfile=$PWD/server-pid --remote=punix:socket --unixctl=$PWD/unixctl --log-file=$PWD/ovsdb-server-log db >/dev/null 2>&1],
             [0], [], [])
-   AT_CHECK([ovsdb-client -vjsonrpc --detach --pidfile=$PWD/client-pid -d json monitor --format=csv unix:socket $4 $5 '' $8 > output], 
+   AT_CHECK([ovsdb-client -vjsonrpc --detach --pidfile=$PWD/client-pid -d json monitor --format=csv unix:socket $4 $5 $8 > output], 
             [0], [ignore], [ignore], [kill `cat server-pid`])
    m4_foreach([txn], [$6],
      [AT_CHECK([ovsdb-client transact unix:socket 'txn'], [0],
@@ -294,14 +293,14 @@ OVSDB_CHECK_MONITOR([monitor initial only],
   [ordinals], [ordinals], [OVSDB_MONITOR_TXNS],
   [[row,action,name,number,_version
 <0>,initial,"""ten""",10,"[""uuid"",""<1>""]"
-]], [initial])
+]], [!insert,!delete,!modify])
 
 OVSDB_CHECK_MONITOR([monitor insert only],
   [ORDINAL_SCHEMA], [OVSDB_MONITOR_INITIAL],
   [ordinals], [ordinals], [OVSDB_MONITOR_TXNS],
   [[row,action,name,number,_version
 <0>,insert,"""five""",5,"[""uuid"",""<1>""]"
-]], [insert])
+]], [!initial,!delete,!modify])
 
 OVSDB_CHECK_MONITOR([monitor delete only],
   [ORDINAL_SCHEMA], [OVSDB_MONITOR_INITIAL],
@@ -309,7 +308,7 @@ OVSDB_CHECK_MONITOR([monitor delete only],
   [[row,action,name,number,_version
 <0>,delete,"""FIVE""",5,"[""uuid"",""<1>""]"
 <2>,delete,"""ten""",10,"[""uuid"",""<3>""]"
-]], [delete])
+]], [!initial,!insert,!modify])
 
 OVSDB_CHECK_MONITOR([monitor modify only],
   [ORDINAL_SCHEMA], [OVSDB_MONITOR_INITIAL],
@@ -317,4 +316,4 @@ OVSDB_CHECK_MONITOR([monitor modify only],
   [[row,action,name,number,_version
 <0>,old,"""five""",,"[""uuid"",""<1>""]"
 ,new,"""FIVE""",5,"[""uuid"",""<2>""]"
-]], [modify])
+]], [!initial,!insert,!delete])