"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:
"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>:
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
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;
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)
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) {
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;
}
}
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;
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);
\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:"
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.
"\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);
}
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;
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);
{ "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 },
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
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],
[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],
[[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],
[[row,action,name,number,_version
<0>,old,"""five""",,"[""uuid"",""<1>""]"
,new,"""FIVE""",5,"[""uuid"",""<2>""]"
-]], [modify])
+]], [!initial,!insert,!delete])