{"id":815895,"url":"http://patchwork.ozlabs.org/api/patches/815895/?format=json","web_url":"http://patchwork.ozlabs.org/project/openvswitch/patch/20170919220125.32535-43-blp@ovn.org/","project":{"id":47,"url":"http://patchwork.ozlabs.org/api/projects/47/?format=json","name":"Open vSwitch","link_name":"openvswitch","list_id":"ovs-dev.openvswitch.org","list_email":"ovs-dev@openvswitch.org","web_url":"http://openvswitch.org/","scm_url":"git@github.com:openvswitch/ovs.git","webscm_url":"https://github.com/openvswitch/ovs","list_archive_url":"","list_archive_url_format":"","commit_url_format":""},"msgid":"<20170919220125.32535-43-blp@ovn.org>","list_archive_url":null,"date":"2017-09-19T22:01:15","name":"[ovs-dev,RFC,42/52] ovsdb-server: Add support for a built-in _Server database.","commit_ref":null,"pull_url":null,"state":"rfc","archived":false,"hash":"0f485aefdddb329400a9c3d58484579c3edf3f59","submitter":{"id":67603,"url":"http://patchwork.ozlabs.org/api/people/67603/?format=json","name":"Ben Pfaff","email":"blp@ovn.org"},"delegate":null,"mbox":"http://patchwork.ozlabs.org/project/openvswitch/patch/20170919220125.32535-43-blp@ovn.org/mbox/","series":[{"id":3975,"url":"http://patchwork.ozlabs.org/api/series/3975/?format=json","web_url":"http://patchwork.ozlabs.org/project/openvswitch/list/?series=3975","date":"2017-09-19T22:00:34","name":"clustering implementation","version":1,"mbox":"http://patchwork.ozlabs.org/series/3975/mbox/"}],"comments":"http://patchwork.ozlabs.org/api/patches/815895/comments/","check":"pending","checks":"http://patchwork.ozlabs.org/api/patches/815895/checks/","tags":{},"related":[],"headers":{"Return-Path":"<ovs-dev-bounces@openvswitch.org>","X-Original-To":["incoming@patchwork.ozlabs.org","dev@openvswitch.org"],"Delivered-To":["patchwork-incoming@bilbo.ozlabs.org","ovs-dev@mail.linuxfoundation.org"],"Authentication-Results":"ozlabs.org;\n\tspf=pass (mailfrom) smtp.mailfrom=openvswitch.org\n\t(client-ip=140.211.169.12; helo=mail.linuxfoundation.org;\n\tenvelope-from=ovs-dev-bounces@openvswitch.org;\n\treceiver=<UNKNOWN>)","Received":["from mail.linuxfoundation.org (mail.linuxfoundation.org\n\t[140.211.169.12])\n\t(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256\n\tbits)) (No client certificate requested)\n\tby ozlabs.org (Postfix) with ESMTPS id 3xxclc2d1hz9sMN\n\tfor <incoming@patchwork.ozlabs.org>;\n\tWed, 20 Sep 2017 08:21:00 +1000 (AEST)","from mail.linux-foundation.org (localhost [127.0.0.1])\n\tby mail.linuxfoundation.org (Postfix) with ESMTP id BD0BCD16;\n\tTue, 19 Sep 2017 22:02:45 +0000 (UTC)","from smtp1.linuxfoundation.org (smtp1.linux-foundation.org\n\t[172.17.192.35])\n\tby mail.linuxfoundation.org (Postfix) with ESMTPS id 4DC35CE4\n\tfor <dev@openvswitch.org>; Tue, 19 Sep 2017 22:02:44 +0000 (UTC)","from relay4-d.mail.gandi.net (relay4-d.mail.gandi.net\n\t[217.70.183.196])\n\tby smtp1.linuxfoundation.org (Postfix) with ESMTPS id 82E201A6\n\tfor <dev@openvswitch.org>; Tue, 19 Sep 2017 22:02:42 +0000 (UTC)","from sigabrt.benpfaff.org (unknown [208.91.2.3])\n\t(Authenticated sender: blp@ovn.org)\n\tby relay4-d.mail.gandi.net (Postfix) with ESMTPSA id 40D5D172097;\n\tWed, 20 Sep 2017 00:02:39 +0200 (CEST)"],"X-Greylist":"domain auto-whitelisted by SQLgrey-1.7.6","X-Originating-IP":"208.91.2.3","From":"Ben Pfaff <blp@ovn.org>","To":"dev@openvswitch.org","Date":"Tue, 19 Sep 2017 15:01:15 -0700","Message-Id":"<20170919220125.32535-43-blp@ovn.org>","X-Mailer":"git-send-email 2.10.2","In-Reply-To":"<20170919220125.32535-1-blp@ovn.org>","References":"<20170919220125.32535-1-blp@ovn.org>","X-Spam-Status":"No, score=-0.2 required=5.0 tests=RCVD_IN_DNSWL_LOW,\n\tURI_NOVOWEL autolearn=disabled version=3.3.1","X-Spam-Checker-Version":"SpamAssassin 3.3.1 (2010-03-16) on\n\tsmtp1.linux-foundation.org","Cc":"Ben Pfaff <blp@ovn.org>","Subject":"[ovs-dev] [PATCH RFC 42/52] ovsdb-server: Add support for a\n\tbuilt-in _Server database.","X-BeenThere":"ovs-dev@openvswitch.org","X-Mailman-Version":"2.1.12","Precedence":"list","List-Id":"<ovs-dev.openvswitch.org>","List-Unsubscribe":"<https://mail.openvswitch.org/mailman/options/ovs-dev>,\n\t<mailto:ovs-dev-request@openvswitch.org?subject=unsubscribe>","List-Archive":"<http://mail.openvswitch.org/pipermail/ovs-dev/>","List-Post":"<mailto:ovs-dev@openvswitch.org>","List-Help":"<mailto:ovs-dev-request@openvswitch.org?subject=help>","List-Subscribe":"<https://mail.openvswitch.org/mailman/listinfo/ovs-dev>,\n\t<mailto:ovs-dev-request@openvswitch.org?subject=subscribe>","MIME-Version":"1.0","Content-Type":"text/plain; charset=\"us-ascii\"","Content-Transfer-Encoding":"7bit","Sender":"ovs-dev-bounces@openvswitch.org","Errors-To":"ovs-dev-bounces@openvswitch.org"},"content":"Initially this database only reports databases' names and schemas, but\nwhen clustering support is added in a later commit it will also report\nimportant aspects of clustering and cluster status.\n\nSigned-off-by: Ben Pfaff <blp@ovn.org>\n---\n Makefile.am             |   8 ---\n NEWS                    |   3 ++\n build-aux/automake.mk   |  12 ++++-\n build-aux/text2c        |  16 ++++++\n ovsdb/.gitignore        |   3 ++\n ovsdb/_server.ovsschema |   9 ++++\n ovsdb/_server.xml       |  31 ++++++++++++\n ovsdb/automake.mk       |  26 ++++++++++\n ovsdb/ovsdb-server.1.in |   5 ++\n ovsdb/ovsdb-server.c    | 131 +++++++++++++++++++++++++++++++++++++++++++++---\n ovsdb/ovsdb-util.c      |  93 +++++++++++++++++++++++++++++++---\n ovsdb/ovsdb-util.h      |   9 ++++\n tests/ovsdb-server.at   |  70 +++++++++++++-------------\n 13 files changed, 359 insertions(+), 57 deletions(-)\n create mode 100755 build-aux/text2c\n create mode 100644 ovsdb/_server.ovsschema\n create mode 100644 ovsdb/_server.xml","diff":"diff --git a/Makefile.am b/Makefile.am\nindex 31d6331792af..e11de801e0b6 100644\n--- a/Makefile.am\n+++ b/Makefile.am\n@@ -81,14 +81,6 @@ EXTRA_DIST = \\\n \t.travis/osx-prepare.sh \\\n \tappveyor.yml \\\n \tboot.sh \\\n-\tbuild-aux/cccl \\\n-\tbuild-aux/cksum-schema-check \\\n-\tbuild-aux/calculate-schema-cksum \\\n-\tbuild-aux/dist-docs \\\n-\tbuild-aux/dpdkstrip.pl \\\n-\tbuild-aux/sodepends.pl \\\n-\tbuild-aux/soexpand.pl \\\n-\tbuild-aux/xml2nroff \\\n \t$(MAN_FRAGMENTS) \\\n \t$(MAN_ROOTS) \\\n \tVagrantfile \\\ndiff --git a/NEWS b/NEWS\nindex 8392ccc89a02..1ab0f1038c24 100644\n--- a/NEWS\n+++ b/NEWS\n@@ -1,5 +1,8 @@\n Post-v2.8.0\n --------------------\n+   - OVSDB:\n+     * ovsdb-server now always hosts a built-in database named _Server.  See\n+       ovsdb-server(5) for more details.\n    - ovs-vsctl and other commands that display data in tables now support a\n      --max-column-width option to limit column width.\n    - OVN:\ndiff --git a/build-aux/automake.mk b/build-aux/automake.mk\nindex 9500e3a69402..df5a884f8c23 100644\n--- a/build-aux/automake.mk\n+++ b/build-aux/automake.mk\n@@ -1,3 +1,13 @@\n-# This file is purely used for checking the style of the python build tools.\n+EXTRA_DIST += \\\n+\tbuild-aux/calculate-schema-cksum \\\n+\tbuild-aux/cccl \\\n+\tbuild-aux/cksum-schema-check \\\n+\tbuild-aux/dist-docs \\\n+\tbuild-aux/dpdkstrip.pl \\\n+\tbuild-aux/sodepends.pl \\\n+\tbuild-aux/soexpand.pl \\\n+\tbuild-aux/text2c \\\n+\tbuild-aux/xml2nroff\n+\n FLAKE8_PYFILES += \\\n     $(srcdir)/build-aux/xml2nroff\ndiff --git a/build-aux/text2c b/build-aux/text2c\nnew file mode 100755\nindex 000000000000..cb1f256f1775\n--- /dev/null\n+++ b/build-aux/text2c\n@@ -0,0 +1,16 @@\n+#! /usr/bin/python\n+\n+import re\n+import sys\n+\n+\"\"\"This utility reads its input, which should be plain text, and\n+prints it back transformed into quoted strings that may be #included\n+into C source code.\"\"\"\n+\n+while True:\n+    line = sys.stdin.readline()\n+    if not line:\n+        break\n+\n+    s = line.replace(\"\\\\\", \"\\\\\\\\\").replace('\"', '\\\\\"').replace(\"\\n\", \"\\\\n\")\n+    print('\"' + s + '\"')\ndiff --git a/ovsdb/.gitignore b/ovsdb/.gitignore\nindex d715dee925b9..fbcefafc6e97 100644\n--- a/ovsdb/.gitignore\n+++ b/ovsdb/.gitignore\n@@ -1,3 +1,5 @@\n+/_server.ovsschema.inc\n+/_server.ovsschema.stamp\n /ovsdb-client\n /ovsdb-client.1\n /ovsdb-doc\n@@ -5,6 +7,7 @@\n /ovsdb-idlc\n /ovsdb-server\n /ovsdb-server.1\n+/ovsdb-server.5\n /ovsdb-tool\n /ovsdb-tool.1\n /libovsdb.pc\ndiff --git a/ovsdb/_server.ovsschema b/ovsdb/_server.ovsschema\nnew file mode 100644\nindex 000000000000..8997bae5fa36\n--- /dev/null\n+++ b/ovsdb/_server.ovsschema\n@@ -0,0 +1,9 @@\n+{\"name\": \"_Server\",\n+ \"version\": \"1.0.0\",\n+ \"cksum\": \"3931859656 185\",\n+ \"tables\": {\n+   \"Database\": {\n+     \"columns\": {\n+       \"name\": {\"type\": \"string\"},\n+       \"schema\": {\"type\": \"string\"}},\n+     \"isRoot\": true}}}\ndiff --git a/ovsdb/_server.xml b/ovsdb/_server.xml\nnew file mode 100644\nindex 000000000000..a55beb9bd6de\n--- /dev/null\n+++ b/ovsdb/_server.xml\n@@ -0,0 +1,31 @@\n+<?xml version=\"1.0\" encoding=\"utf-8\"?>\n+<database name=\"ovsdb-server\" title=\"ovsdb-server _Server schema\">\n+  <p>\n+    Every <code>ovsdb-server</code> (version 2.9 or later) always hosts an\n+    instance of this schema, which holds information on the status and\n+    configuration of the server itself.  This database is read-only.  This\n+    manpage describes the schema for this database.\n+  </p>\n+\n+  <table name=\"Database\" title=\"Databases.\">\n+    <p>\n+      This table describes the databases hosted by the database server, with\n+      one row per database.  As its database configuration and status changes,\n+      the server automatically and immediately updates the table to match.\n+    </p>\n+    <p>\n+      Clients can use the <code>_uuid</code> column in this table as a\n+      generation number.  The server generates a fresh <code>_uuid</code> every\n+      time it adds a database, so that removing and then re-adding a database\n+      to the server causes its row <code>_uuid</code> to change.\n+    </p>\n+\n+    <column name=\"name\">\n+      The database's name, as specified in its schema.\n+    </column>\n+\n+    <column name=\"schema\">\n+      The database schema, as a JSON string.\n+    </column>\n+  </table>\n+</database>\ndiff --git a/ovsdb/automake.mk b/ovsdb/automake.mk\nindex 50e5ab367eff..c1d402c43206 100644\n--- a/ovsdb/automake.mk\n+++ b/ovsdb/automake.mk\n@@ -117,3 +117,29 @@ EXTRA_DIST += ovsdb/ovsdb-dot.in ovsdb/dot2pic\n noinst_SCRIPTS += ovsdb/ovsdb-dot\n CLEANFILES += ovsdb/ovsdb-dot\n OVSDB_DOT = $(run_python) $(srcdir)/ovsdb/ovsdb-dot.in\n+\n+EXTRA_DIST += ovsdb/_server.ovsschema\n+CLEANFILES += ovsdb/_server.ovsschema.inc\n+ovsdb/ovsdb-server.o: ovsdb/_server.ovsschema.inc\n+ovsdb/_server.ovsschema.inc: ovsdb/_server.ovsschema $(srcdir)/build-aux/text2c\n+\t$(AM_V_GEN)$(run_python) $(srcdir)/build-aux/text2c < $< > $@.tmp\n+\t$(AM_V_at)mv $@.tmp $@\n+\n+# Version checking for _server.ovsschema.\n+ALL_LOCAL += ovsdb/_server.ovsschema.stamp\n+ovsdb/_server.ovsschema.stamp: ovsdb/_server.ovsschema\n+\t$(srcdir)/build-aux/cksum-schema-check $? $@\n+CLEANFILES += ovsdb/_server.ovsschema.stamp\n+\n+# _Server schema documentation\n+EXTRA_DIST += ovsdb/_server.xml\n+CLEANFILES += ovsdb/ovsdb-server.5\n+man_MANS += ovsdb/ovsdb-server.5\n+ovsdb/ovsdb-server.5: \\\n+\tovsdb/ovsdb-doc ovsdb/_server.xml ovsdb/_server.ovsschema \\\n+\t$(VSWITCH_PIC)\n+\t$(AM_V_GEN)$(OVSDB_DOC) \\\n+\t\t--version=$(VERSION) \\\n+\t\t$(srcdir)/ovsdb/_server.ovsschema \\\n+\t\t$(srcdir)/ovsdb/_server.xml > $@.tmp && \\\n+\tmv $@.tmp $@\ndiff --git a/ovsdb/ovsdb-server.1.in b/ovsdb/ovsdb-server.1.in\nindex f1c6466ccb75..455010c3f5fa 100644\n--- a/ovsdb/ovsdb-server.1.in\n+++ b/ovsdb/ovsdb-server.1.in\n@@ -36,6 +36,11 @@ Each OVSDB file may be specified on the command line as \\fIdatabase\\fR.\n If none is specified, the default is \\fB@DBDIR@/conf.db\\fR.  The database\n files must already have been created and initialized using, for\n example, \\fBovsdb\\-tool create\\fR.\n+.PP\n+In addition to user-specified databases, \\fBovsdb\\-server\\fR version\n+2.9 and later also always hosts a built-in database named\n+\\fB_Server\\fR.  Please see \\fBovsdb\\-server\\fR(5) for documentation on\n+this database's schema.\n .\n .SH \"ACTIVE and BACKUP\"\n \\fBovsdb\\-server\\fR runs either as a backup server, or as an active server.\ndiff --git a/ovsdb/ovsdb-server.c b/ovsdb/ovsdb-server.c\nindex 99ba6949f016..52eb21f89aa0 100644\n--- a/ovsdb/ovsdb-server.c\n+++ b/ovsdb/ovsdb-server.c\n@@ -65,6 +65,7 @@ struct db {\n     char *filename;\n     struct ovsdb_file *file;\n     struct ovsdb *db;\n+    struct uuid row_uuid;\n };\n \n /* SSL configuration. */\n@@ -107,6 +108,7 @@ static unixctl_cb_func ovsdb_server_remove_database;\n static unixctl_cb_func ovsdb_server_list_databases;\n \n static char *open_db(struct server_config *config, const char *filename);\n+static void add_server_db(struct server_config *);\n static void close_db(struct db *db);\n \n static void parse_options(int *argc, char **argvp[],\n@@ -124,6 +126,7 @@ static void report_error_if_changed(char *error, char **last_errorp);\n static void update_remote_status(const struct ovsdb_jsonrpc_server *jsonrpc,\n                                  const struct sset *remotes,\n                                  struct shash *all_dbs);\n+static void update_server_status(struct shash *all_dbs);\n \n static void save_config__(FILE *config_file, const struct sset *remotes,\n                           const struct sset *db_filenames,\n@@ -214,6 +217,8 @@ main_loop(struct ovsdb_jsonrpc_server *jsonrpc, struct shash *all_dbs,\n             update_remote_status(jsonrpc, remotes, all_dbs);\n         }\n \n+        update_server_status(all_dbs);\n+\n         memory_wait();\n         if (*is_backup) {\n             replication_wait();\n@@ -328,6 +333,7 @@ main(int argc, char *argv[])\n             ovs_fatal(0, \"%s\", error);\n         }\n     }\n+    add_server_db(&server_config);\n \n     error = reconfigure_remotes(jsonrpc, &all_dbs, &remotes);\n     if (!error) {\n@@ -490,6 +496,16 @@ close_db(struct db *db)\n     free(db);\n }\n \n+static void\n+add_db(struct server_config *config, const char *name, struct db *db)\n+{\n+    db->row_uuid = UUID_ZERO;\n+    shash_add_assert(config->all_dbs, name, db);\n+    bool ok OVS_UNUSED = ovsdb_jsonrpc_server_add_db(config->jsonrpc,\n+                                                     db->db);\n+    ovs_assert(ok);\n+}\n+\n static char *\n open_db(struct server_config *config, const char *filename)\n {\n@@ -525,6 +541,27 @@ open_db(struct server_config *config, const char *filename)\n     return error;\n }\n \n+/* Add the internal _Server database to the server configuration. */\n+static void\n+add_server_db(struct server_config *config)\n+{\n+    struct json *schema_json = json_from_string(\n+#include \"ovsdb/_server.ovsschema.inc\"\n+        );\n+    ovs_assert(schema_json->type == JSON_OBJECT);\n+\n+    struct ovsdb_schema *schema;\n+    struct ovsdb_error *error OVS_UNUSED = ovsdb_schema_from_json(schema_json,\n+                                                                  &schema);\n+    ovs_assert(!error);\n+    json_destroy(schema_json);\n+\n+    struct db *db = xzalloc(sizeof *db);\n+    db->filename = xstrdup(\"<internal>\");\n+    db->db = ovsdb_create(schema);\n+    add_db(config, db->db->schema->name, db);\n+}\n+\n static char * OVS_WARN_UNUSED_RESULT\n parse_db_column__(const struct shash *all_dbs,\n                   const char *name_, char *name,\n@@ -875,6 +912,18 @@ update_remote_rows(const struct shash *all_dbs, const struct db *db_,\n }\n \n static void\n+commit_txn(struct ovsdb_txn *txn, const char *name)\n+{\n+    struct ovsdb_error *error = ovsdb_txn_commit(txn, false);\n+    if (error) {\n+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);\n+        char *msg = ovsdb_error_to_string_free(error);\n+        VLOG_ERR_RL(&rl, \"Failed to update %s: %s\", name, msg);\n+        free(msg);\n+    }\n+}\n+\n+static void\n update_remote_status(const struct ovsdb_jsonrpc_server *jsonrpc,\n                      const struct sset *remotes,\n                      struct shash *all_dbs)\n@@ -890,14 +939,84 @@ update_remote_status(const struct ovsdb_jsonrpc_server *jsonrpc,\n             update_remote_rows(all_dbs, db, remote, jsonrpc, txn);\n         }\n \n-        struct ovsdb_error *error = ovsdb_txn_commit(txn, false);\n-        if (error) {\n-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);\n-            char *msg = ovsdb_error_to_string_free(error);\n-            VLOG_ERR_RL(&rl, \"Failed to update remote status: %s\", msg);\n-            free(msg);\n+        commit_txn(txn, node->name);\n+    }\n+}\n+\n+/* Updates 'row', a row in the _Server database's Database table, to match\n+ * 'db'. */\n+static void\n+update_database_status(struct ovsdb_row *row, struct db *db)\n+{\n+    ovsdb_util_write_string_column(row, \"name\", db->db->schema->name);\n+\n+    const struct uuid *row_uuid = ovsdb_row_get_uuid(row);\n+    if (!uuid_equals(row_uuid, &db->row_uuid)) {\n+        db->row_uuid = *row_uuid;\n+\n+        /* The schema can only change if the generation changes, so only update\n+         * it in that case.  (Schemas are often kilobytes in size and expensive\n+         * to serialize, so presumably it's worth optimizing.) */\n+        struct json *json_schema = ovsdb_schema_to_json(db->db->schema);\n+        char *schema = json_to_string(json_schema, JSSF_SORT);\n+        ovsdb_util_write_string_column(row, \"schema\", schema);\n+        free(schema);\n+        json_destroy(json_schema);\n+    }\n+}\n+\n+/* Updates the Database table in the _Server database. */\n+static void\n+update_server_status(struct shash *all_dbs)\n+{\n+    struct db *server_db = shash_find_data(all_dbs, \"_Server\");\n+    struct ovsdb_table *database_table = shash_find_data(\n+        &server_db->db->tables, \"Database\");\n+    struct ovsdb_txn *txn = ovsdb_txn_create(server_db->db);\n+\n+    /* Update rows for databases that still exist.\n+     * Delete rows for databases that no longer exist. */\n+    const struct ovsdb_row *row, *next_row;\n+    HMAP_FOR_EACH_SAFE (row, next_row, hmap_node, &database_table->rows) {\n+        const char *name;\n+        ovsdb_util_read_string_column(row, \"name\", &name);\n+        struct db *db = shash_find_data(all_dbs, name);\n+        if (!db || !db->db) {\n+            ovsdb_txn_row_delete(txn, row);\n+        } else {\n+            update_database_status(ovsdb_txn_row_modify(txn, row), db);\n         }\n     }\n+\n+    /* Add rows for new databases.\n+     *\n+     * This is O(n**2) but usually there are only 2 or 3 databases. */\n+    struct shash_node *node;\n+    SHASH_FOR_EACH (node, all_dbs) {\n+        struct db *db = node->data;\n+\n+        if (!db->db) {\n+            continue;\n+        }\n+\n+        HMAP_FOR_EACH (row, hmap_node, &database_table->rows) {\n+            const char *name;\n+            ovsdb_util_read_string_column(row, \"name\", &name);\n+            if (!strcmp(name, node->name)) {\n+                goto next;\n+            }\n+        }\n+\n+        /* Add row. */\n+        struct ovsdb_row *row = ovsdb_row_create(database_table);\n+        uuid_generate(ovsdb_row_get_uuid_rw(row));\n+        update_database_status(row, db);\n+        ovsdb_txn_row_insert(txn, row);\n+\n+    next:;\n+    }\n+\n+    commit_txn(txn, \"_Server\");\n }\n \n /* Reconfigures ovsdb-server's remotes based on information in the database. */\ndiff --git a/ovsdb/ovsdb-util.c b/ovsdb/ovsdb-util.c\nindex 5ee5e4ddaf8d..06d25af49a18 100644\n--- a/ovsdb/ovsdb-util.c\n+++ b/ovsdb/ovsdb-util.c\n@@ -22,6 +22,38 @@\n \n VLOG_DEFINE_THIS_MODULE(ovsdb_util);\n \n+static void\n+ovsdb_util_clear_column(struct ovsdb_row *row, const char *column_name)\n+{\n+    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);\n+    const struct ovsdb_table_schema *schema = row->table->schema;\n+    const struct ovsdb_column *column;\n+\n+    column = ovsdb_table_schema_get_column(schema, column_name);\n+    if (!column) {\n+        VLOG_DBG_RL(&rl, \"Table `%s' has no `%s' column\",\n+                    schema->name, column_name);\n+        return;\n+    }\n+\n+    if (column->type.n_min) {\n+        if (!VLOG_DROP_DBG(&rl)) {\n+            char *type_name = ovsdb_type_to_english(&column->type);\n+            VLOG_DBG(\"Table `%s' column `%s' has type %s, which requires \"\n+                     \"a value, when it was expected to be optional\",\n+                     schema->name, column_name, type_name);\n+            free(type_name);\n+        }\n+        return;\n+    }\n+\n+    struct ovsdb_datum *datum = &row->fields[column->index];\n+    if (datum->n) {\n+        ovsdb_datum_destroy(datum, &column->type);\n+        ovsdb_datum_init_empty(datum);\n+    }\n+}\n+\n struct ovsdb_datum *\n ovsdb_util_get_datum(struct ovsdb_row *row, const char *column_name,\n                     const enum ovsdb_atomic_type key_type,\n@@ -164,29 +196,74 @@ ovsdb_util_read_bool_column(const struct ovsdb_row *row,\n     return atom != NULL;\n }\n \n-void\n-ovsdb_util_write_bool_column(struct ovsdb_row *row, const char *column_name,\n-                             bool value)\n+bool\n+ovsdb_util_read_uuid_column(const struct ovsdb_row *row,\n+                            const char *column_name, struct uuid *uuid)\n+{\n+    const union ovsdb_atom *atom;\n+\n+    atom = ovsdb_util_read_column(row, column_name, OVSDB_TYPE_UUID);\n+    *uuid = atom ? atom->uuid : UUID_ZERO;\n+    return atom != NULL;\n+}\n+\n+static void\n+ovsdb_util_write_singleton(struct ovsdb_row *row, const char *column_name,\n+                           const union ovsdb_atom *atom,\n+                           enum ovsdb_atomic_type type)\n {\n     const struct ovsdb_column *column;\n     struct ovsdb_datum *datum;\n \n     column = ovsdb_table_schema_get_column(row->table->schema, column_name);\n-    datum = ovsdb_util_get_datum(row, column_name, OVSDB_TYPE_BOOLEAN,\n-                                 OVSDB_TYPE_VOID, 1);\n+    datum = ovsdb_util_get_datum(row, column_name, type, OVSDB_TYPE_VOID, 1);\n     if (!datum) {\n         return;\n     }\n \n-    if (datum->n != 1) {\n+    if (datum->n == 1) {\n+        if (ovsdb_atom_equals(&datum->keys[0], atom, type)) {\n+            return;\n+        }\n+    } else {\n         ovsdb_datum_destroy(datum, &column->type);\n-\n         datum->n = 1;\n         datum->keys = xmalloc(sizeof *datum->keys);\n         datum->values = NULL;\n     }\n+    ovsdb_atom_clone(&datum->keys[0], atom, type);\n+}\n \n-    datum->keys[0].boolean = value;\n+void\n+ovsdb_util_write_bool_column(struct ovsdb_row *row, const char *column_name,\n+                             bool value)\n+{\n+    const union ovsdb_atom atom = { .boolean = value };\n+    ovsdb_util_write_singleton(row, column_name, &atom, OVSDB_TYPE_BOOLEAN);\n+}\n+\n+void\n+ovsdb_util_write_uuid_column(struct ovsdb_row *row, const char *column_name,\n+                             const struct uuid *uuid)\n+{\n+    if (uuid) {\n+        const union ovsdb_atom atom = { .uuid = *uuid };\n+        ovsdb_util_write_singleton(row, column_name, &atom, OVSDB_TYPE_UUID);\n+    } else {\n+        ovsdb_util_clear_column(row, column_name);\n+    }\n+}\n+\n+void\n+ovsdb_util_write_string_column(struct ovsdb_row *row, const char *column_name,\n+                               const char *string)\n+{\n+    if (string) {\n+        const union ovsdb_atom atom = { .string = CONST_CAST(char *, string) };\n+        ovsdb_util_write_singleton(row, column_name, &atom, OVSDB_TYPE_STRING);\n+    } else {\n+        ovsdb_util_clear_column(row, column_name);\n+    }\n }\n \n void\ndiff --git a/ovsdb/ovsdb-util.h b/ovsdb/ovsdb-util.h\nindex abd81ff38cd2..a0404a3a7ff0 100644\n--- a/ovsdb/ovsdb-util.h\n+++ b/ovsdb/ovsdb-util.h\n@@ -38,6 +38,9 @@ bool ovsdb_util_read_integer_column(const struct ovsdb_row *row,\n bool ovsdb_util_read_string_column(const struct ovsdb_row *row,\n                                    const char *column_name,\n                                    const char **stringp);\n+void ovsdb_util_write_string_column(struct ovsdb_row *row,\n+                                    const char *column_name,\n+                                    const char *string);\n void ovsdb_util_write_string_string_column(struct ovsdb_row *row,\n                                            const char *column_name,\n                                            char **keys, char **values,\n@@ -48,5 +51,11 @@ bool ovsdb_util_read_bool_column(const struct ovsdb_row *row,\n void ovsdb_util_write_bool_column(struct ovsdb_row *row,\n                                   const char *column_name,\n                                   bool value);\n+bool ovsdb_util_read_uuid_column(const struct ovsdb_row *row,\n+                                 const char *column_name,\n+                                 struct uuid *);\n+void ovsdb_util_write_uuid_column(struct ovsdb_row *row,\n+                                  const char *column_name,\n+                                  const struct uuid *);\n \n #endif /* ovsdb/util.h */\ndiff --git a/tests/ovsdb-server.at b/tests/ovsdb-server.at\nindex 0f8c791128b1..ccbc1ac0b717 100644\n--- a/tests/ovsdb-server.at\n+++ b/tests/ovsdb-server.at\n@@ -147,20 +147,31 @@ AT_CHECK([ovsdb-client get-schema-version unix:socket ordinals], [0], [5.1.3\n OVSDB_SERVER_SHUTDOWN\n AT_CLEANUP\n \n+dnl CHECK_DBS([databases])\n+dnl\n+dnl Checks that ovsdb-server hosts the given 'databases', each of which\n+dnl needs to be followed by a newline.\n+m4_define([CHECK_DBS],\n+  [AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/list-dbs],\n+  [0], [_Server\n+$1])\n+AT_CHECK([ovsdb-client --no-headings dump _Server Database name | sort], [0], [dnl\n+Database table\n+_Server\n+$1])])\n+\n AT_SETUP([database multiplexing implementation])\n AT_KEYWORDS([ovsdb server positive])\n ordinal_schema > schema1\n constraint_schema > schema2\n AT_CHECK([ovsdb-tool create db1 schema1], [0], [ignore], [ignore])\n AT_CHECK([ovsdb-tool create db2 schema2], [0], [ignore], [ignore])\n-AT_CHECK([ovsdb-server --detach --no-chdir --pidfile --remote=punix:socket db1 db2], [0], [ignore], [ignore])\n-AT_CHECK(\n-  [[ovsdb-client list-dbs unix:socket]], \n-  [0], [constraints\n+AT_CHECK([ovsdb-server --detach --no-chdir --pidfile --remote=punix:db.sock db1 db2], [0], [ignore], [ignore])\n+CHECK_DBS([constraints\n ordinals\n-], [ignore], [test ! -e pid || kill `cat pid`])\n+])\n AT_CHECK(\n-  [[ovstest test-jsonrpc request unix:socket get_schema [\\\"nonexistent\\\"]]], [0],\n+  [[ovstest test-jsonrpc request unix:db.sock get_schema [\\\"nonexistent\\\"]]], [0],\n   [[{\"error\":{\"details\":\"get_schema request specifies unknown database nonexistent\",\"error\":\"unknown database\",\"syntax\":\"[\\\"nonexistent\\\"]\"},\"id\":0,\"result\":null}\n ]], [], [test ! -e pid || kill `cat pid`])\n OVSDB_SERVER_SHUTDOWN\n@@ -175,21 +186,19 @@ AT_CHECK([ovsdb-tool create db1 schema1], [0], [ignore], [ignore])\n AT_CHECK([ovsdb-tool create db2 schema2], [0], [ignore], [ignore])\n \n # Start ovsdb-server with just a single database - db1.\n-AT_CHECK([ovsdb-server --detach --no-chdir --pidfile --remote=punix:socket db1], [0])\n-AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/list-dbs],\n-  [0], [ordinals\n+AT_CHECK([ovsdb-server --detach --no-chdir --pidfile --remote=punix:db.sock db1], [0])\n+CHECK_DBS([ordinals\n ])\n \n # Add the second database.\n AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/add-db db2], [0])\n-AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/list-dbs],\n-  [0], [constraints\n+CHECK_DBS([constraints\n ordinals\n ])\n \n # The databases are responsive.\n-AT_CHECK([ovsdb-client list-tables unix:socket constraints], [0], [ignore], [ignore])\n-AT_CHECK([ovsdb-client list-tables unix:socket ordinals], [0], [ignore], [ignore])\n+AT_CHECK([ovsdb-client list-tables unix:db.sock constraints], [0], [ignore], [ignore])\n+AT_CHECK([ovsdb-client list-tables unix:db.sock ordinals], [0], [ignore], [ignore])\n \n # Add an already added database.\n if test $IS_WIN32 = \"yes\"; then\n@@ -215,25 +224,23 @@ ovs-appctl: ovsdb-server: server returned an error\n AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/add-remote db:ordinals,ordinals,name], [0])\n AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/list-remotes],\n   [0], [db:ordinals,ordinals,name\n-punix:socket\n+punix:db.sock\n ])\n \n # Removing db1 has no effect on its remote.\n AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/remove-db ordinals], [0])\n-AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/list-dbs],\n-  [0], [constraints\n+CHECK_DBS([constraints\n ])\n AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/list-remotes],\n   [0], [db:ordinals,ordinals,name\n-punix:socket\n+punix:db.sock\n ])\n-AT_CHECK([ovsdb-client list-tables unix:socket ordinals], [1], [ignore], [ignore])\n+AT_CHECK([ovsdb-client list-tables unix:db.sock ordinals], [1], [ignore], [ignore])\n \n # Remove db2.\n AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/remove-db constraints], [0])\n-AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/list-dbs],\n-  [0], [])\n-AT_CHECK([ovsdb-client list-tables unix:socket constraints], [1], [ignore], [ignore])\n+CHECK_DBS()\n+AT_CHECK([ovsdb-client list-tables unix:db.sock constraints], [1], [ignore], [ignore])\n \n # Remove a non-existent database.\n AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/remove-db ordinals], [2],\n@@ -243,10 +250,9 @@ ovs-appctl: ovsdb-server: server returned an error\n \n # Add a removed database.\n AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/add-db db2], [0])\n-AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/list-dbs],\n-  [0], [constraints\n+CHECK_DBS([constraints\n ])\n-AT_CHECK([ovsdb-client list-tables unix:socket constraints], [0], [ignore], [ignore])\n+AT_CHECK([ovsdb-client list-tables unix:db.sock constraints], [0], [ignore], [ignore])\n OVS_APP_EXIT_AND_WAIT([ovsdb-server])\n AT_CLEANUP\n \n@@ -257,14 +263,13 @@ AT_SKIP_IF([test \"$IS_WIN32\" = \"yes\"])\n ordinal_schema > schema\n AT_CHECK([ovsdb-tool create db1 schema], [0], [ignore], [ignore])\n on_exit 'kill `cat *.pid`'\n-AT_CHECK([ovsdb-server -v -vvlog:off --monitor --detach --no-chdir --pidfile --log-file db1])\n+AT_CHECK([ovsdb-server -v -vvlog:off --monitor --detach --no-chdir --pidfile --log-file --remote=punix:db.sock db1])\n \n # Add the second database.\n constraint_schema > schema2\n AT_CHECK([ovsdb-tool create db2 schema2], [0], [ignore], [ignore])\n AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/add-db db2], [0])\n-AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/list-dbs],\n-  [0], [constraints\n+CHECK_DBS([constraints\n ordinals\n ])\n \n@@ -276,8 +281,7 @@ OVS_WAIT_WHILE([kill -0 `cat old.pid`])\n OVS_WAIT_UNTIL(\n   [test -s ovsdb-server.pid && test `cat ovsdb-server.pid` != `cat old.pid`])\n OVS_WAIT_UNTIL([ovs-appctl -t ovsdb-server version])\n-AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/list-dbs],\n-  [0], [constraints\n+CHECK_DBS([constraints\n ordinals\n ])\n OVS_APP_EXIT_AND_WAIT([ovsdb-server])\n@@ -292,12 +296,11 @@ AT_CHECK([ovsdb-tool create db1 schema], [0], [ignore], [ignore])\n constraint_schema > schema2\n AT_CHECK([ovsdb-tool create db2 schema2], [0], [ignore], [ignore])\n on_exit 'kill `cat *.pid`'\n-AT_CHECK([ovsdb-server -v -vvlog:off --monitor --detach --no-chdir --pidfile --log-file db1 db2])\n+AT_CHECK([ovsdb-server -v -vvlog:off --monitor --detach --no-chdir --pidfile --log-file --remote=punix:db.sock db1 db2])\n \n # Remove the second database.\n AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/remove-db constraints])\n-AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/list-dbs],\n-  [0], [ordinals\n+CHECK_DBS([ordinals\n ])\n \n # Kill the daemon process, making it look like a segfault,\n@@ -308,8 +311,7 @@ OVS_WAIT_WHILE([kill -0 `cat old.pid`])\n OVS_WAIT_UNTIL(\n   [test -s ovsdb-server.pid && test `cat ovsdb-server.pid` != `cat old.pid`])\n OVS_WAIT_UNTIL([ovs-appctl -t ovsdb-server version])\n-AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/list-dbs],\n-  [0], [ordinals\n+CHECK_DBS([ordinals\n ])\n OVS_APP_EXIT_AND_WAIT([ovsdb-server])\n AT_CLEANUP\n","prefixes":["ovs-dev","RFC","42/52"]}