[ovs-dev,09/15] ovsdb-server: Add support for a built-in _Server database.

Message ID 20180101051640.13043-9-blp@ovn.org
State Accepted
Delegated to: Justin Pettit
Headers show
Series
  • [ovs-dev,01/15] log: Add async commit support.
Related show

Commit Message

Ben Pfaff Jan. 1, 2018, 5:16 a.m.
The _Server database is valuable primarily because it provides database
clients a way to find out the details of changes to databases, schemas,
etc. in a granular, natural way.  Until now, the only way that the server
could notify clients about these kinds of changes was to close the session;
when the client reconnects, it is expected to reassess the server's state.
One way to provide this kind of granular information would be to add
specific JSON-RPC requests to obtain notifications for different kinds of
changes, but since ovsdb-server already provides granular and flexible
notification support for databases, using a database for the purpose is
convenient and avoids duplicating functionality.

Initially this database only reports databases' names and schemas, but
when clustering support is added in a later commit it will also report
important aspects of clustering and cluster status.  Thus, this database
also reduces the need to add JSON-RPC calls to retrieve information about
new features.

Signed-off-by: Ben Pfaff <blp@ovn.org>
---
 Makefile.am             |   8 ---
 NEWS                    |   2 +
 build-aux/automake.mk   |  12 ++++-
 build-aux/text2c        |  16 ++++++
 ovsdb/.gitignore        |   3 ++
 ovsdb/_server.ovsschema |   9 ++++
 ovsdb/_server.xml       |  31 ++++++++++++
 ovsdb/automake.mk       |  26 ++++++++++
 ovsdb/ovsdb-server.1.in |   6 +++
 ovsdb/ovsdb-server.c    | 131 +++++++++++++++++++++++++++++++++++++++++++++---
 ovsdb/ovsdb-util.c      |  93 +++++++++++++++++++++++++++++++---
 ovsdb/ovsdb-util.h      |   9 ++++
 tests/ovsdb-server.at   |  70 +++++++++++++-------------
 13 files changed, 359 insertions(+), 57 deletions(-)
 create mode 100755 build-aux/text2c
 create mode 100644 ovsdb/_server.ovsschema
 create mode 100644 ovsdb/_server.xml

Comments

Justin Pettit Jan. 30, 2018, 11:27 p.m. | #1
> On Dec 31, 2017, at 9:16 PM, Ben Pfaff <blp@ovn.org> wrote:
> 
> @@ -5,6 +7,7 @@
> /ovsdb-idlc
> /ovsdb-server
> /ovsdb-server.1
> +/ovsdb-server.5

Do you need to add the new man page to the distributions?

	debian/openvswitch-switch.manpages
	rhel/openvswitch-fedora.spec.in
	rhel/openvswitch.spec.in
	xenserver/openvswitch-xen.spec.in

Also, I think you may want to add a reference to "Documentation/ref/index.rst".

> +# _Server schema documentation
> +EXTRA_DIST += ovsdb/_server.xml
> +CLEANFILES += ovsdb/ovsdb-server.5
> +man_MANS += ovsdb/ovsdb-server.5
> +ovsdb/ovsdb-server.5: \
> +	ovsdb/ovsdb-doc ovsdb/_server.xml ovsdb/_server.ovsschema \
> +	$(VSWITCH_PIC)

Did you intend to include VSWITCH_PIC?

> @@ -328,6 +333,7 @@ main(int argc, char *argv[])
>             ovs_fatal(0, "%s", error);
>         }
>     }
> +    add_server_db(&server_config);

I don't think there's anything that frees 'server_config'.  Not a huge deal since it would be on shutdown, but it's always nice to have a clean valgrind run.

> +/* Add the internal _Server database to the server configuration. */
> +static void
> +add_server_db(struct server_config *config)
> +{
> +    struct json *schema_json = json_from_string(
> +#include "ovsdb/_server.ovsschema.inc"
> +        );
> +    ovs_assert(schema_json->type == JSON_OBJECT);
> +
> +    struct ovsdb_schema *schema;
> +    struct ovsdb_error *error OVS_UNUSED = ovsdb_schema_from_json(schema_json,
> +                                                                  &schema);
> +    ovs_assert(!error);
> +    json_destroy(schema_json);
> +
> +    struct db *db = xzalloc(sizeof *db);
> +    db->filename = xstrdup("<internal>");
> +    db->db = ovsdb_create(schema);
> +    add_db(config, db->db->schema->name, db);
> +}

Probably not a huge deal, since there's a single instance that runs as long as the process, but I don't think there's anything that free the memory allocated from this internal database.

> +/* Updates the Database table in the _Server database. */
> +static void
> +update_server_status(struct shash *all_dbs)
> +{
> +    struct db *server_db = shash_find_data(all_dbs, "_Server");
> +    struct ovsdb_table *database_table = shash_find_data(
> +        &server_db->db->tables, "Database");
> +    struct ovsdb_txn *txn = ovsdb_txn_create(server_db->db);
> +
> +    /* Update rows for databases that still exist.
> +     * Delete rows for databases that no longer exist. */
> +    const struct ovsdb_row *row, *next_row;
> +    HMAP_FOR_EACH_SAFE (row, next_row, hmap_node, &database_table->rows) {
> +        const char *name;
> +        ovsdb_util_read_string_column(row, "name", &name);
> +        struct db *db = shash_find_data(all_dbs, name);
> +        if (!db || !db->db) {
> +            ovsdb_txn_row_delete(txn, row);
> +        } else {
> +            update_database_status(ovsdb_txn_row_modify(txn, row), db);
>         }
>     }
> +
> +    /* Add rows for new databases.
> +     *
> +     * This is O(n**2) but usually there are only 2 or 3 databases. */
> +    struct shash_node *node;
> +    SHASH_FOR_EACH (node, all_dbs) {
> +        struct db *db = node->data;
> +
> +        if (!db->db) {
> +            continue;
> +        }
> +
> +        HMAP_FOR_EACH (row, hmap_node, &database_table->rows) {
> +            const char *name;
> +            ovsdb_util_read_string_column(row, "name", &name);
> +            if (!strcmp(name, node->name)) {
> +                goto next;
> +            }
> +        }
> +
> +        /* Add row. */
> +        struct ovsdb_row *row = ovsdb_row_create(database_table);
> +        uuid_generate(ovsdb_row_get_uuid_rw(row));
> +        update_database_status(row, db);
> +        ovsdb_txn_row_insert(txn, row);
> +
> +    next:;
> +    }
> +
> +    commit_txn(txn, "_Server");
> }

I see a memory leak when I run unit tests (e.g., "testing database multiplexing implementation") under valgrind:

==123484== 6 bytes in 6 blocks are definitely lost in loss record 4 of 115
==123484==    at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd6
4-linux.so)
==123484==    by 0x437084: xmalloc (util.c:120)
==123484==    by 0x4370B3: xmemdup (util.c:142)
==123484==    by 0x4257BE: ovsdb_atom_init_default (ovsdb-data.c:77)
==123484==    by 0x42580D: alloc_default_atoms (ovsdb-data.c:317)
==123484==    by 0x425F48: ovsdb_datum_init_default (ovsdb-data.c:913)
==123484==    by 0x412C12: ovsdb_row_create (row.c:56)
==123484==    by 0x4051F7: update_server_status (ovsdb-server.c:1030)
==123484==    by 0x4051F7: main_loop (ovsdb-server.c:226)
==123484==    by 0x4051F7: main (ovsdb-server.c:438)

> diff --git a/ovsdb/ovsdb-util.c b/ovsdb/ovsdb-util.c
> index 5ee5e4ddaf8d..06d25af49a18 100644
> --- a/ovsdb/ovsdb-util.c
> +++ b/ovsdb/ovsdb-util.c
> @@ -22,6 +22,38 @@
> 
> VLOG_DEFINE_THIS_MODULE(ovsdb_util);
> 
> +static void
> +ovsdb_util_clear_column(struct ovsdb_row *row, const char *column_name)
> +{
> ...
> +    if (column->type.n_min) {
> +        if (!VLOG_DROP_DBG(&rl)) {
> +            char *type_name = ovsdb_type_to_english(&column->type);
> +            VLOG_DBG("Table `%s' column `%s' has type %s, which requires "
> +                     "a value, when it was expected to be optional",
> +                     schema->name, column_name, type_name);

This error message seemed a little unclear to me.  What about something along the lines of "Table x column y has type z, which requires a value, but an attempt was made to clear it"?

Acked-by: Justin Pettit <jpettit@ovn.org>

--Justin
Ben Pfaff Feb. 1, 2018, 6:59 p.m. | #2
On Tue, Jan 30, 2018 at 03:27:18PM -0800, Justin Pettit wrote:
> 
> > On Dec 31, 2017, at 9:16 PM, Ben Pfaff <blp@ovn.org> wrote:
> > 
> > @@ -5,6 +7,7 @@
> > /ovsdb-idlc
> > /ovsdb-server
> > /ovsdb-server.1
> > +/ovsdb-server.5
> 
> Do you need to add the new man page to the distributions?

Yes.  Thanks, fixed.

> 	debian/openvswitch-switch.manpages
> 	rhel/openvswitch-fedora.spec.in
> 	rhel/openvswitch.spec.in
> 	xenserver/openvswitch-xen.spec.in
> 
> Also, I think you may want to add a reference to "Documentation/ref/index.rst".

Yes.  Thanks, fixed.

> > +# _Server schema documentation
> > +EXTRA_DIST += ovsdb/_server.xml
> > +CLEANFILES += ovsdb/ovsdb-server.5
> > +man_MANS += ovsdb/ovsdb-server.5
> > +ovsdb/ovsdb-server.5: \
> > +	ovsdb/ovsdb-doc ovsdb/_server.xml ovsdb/_server.ovsschema \
> > +	$(VSWITCH_PIC)
> 
> Did you intend to include VSWITCH_PIC?

No.  Removed.

> > @@ -328,6 +333,7 @@ main(int argc, char *argv[])
> >             ovs_fatal(0, "%s", error);
> >         }
> >     }
> > +    add_server_db(&server_config);
> 
> I don't think there's anything that frees 'server_config'.  Not a huge
> deal since it would be on shutdown, but it's always nice to have a
> clean valgrind run.

server_config is a local variable, and not a pointer.  What kind of
freeing would you like to see?

> > +/* Add the internal _Server database to the server configuration. */
> > +static void
> > +add_server_db(struct server_config *config)
> > +{
> > +    struct json *schema_json = json_from_string(
> > +#include "ovsdb/_server.ovsschema.inc"
> > +        );
> > +    ovs_assert(schema_json->type == JSON_OBJECT);
> > +
> > +    struct ovsdb_schema *schema;
> > +    struct ovsdb_error *error OVS_UNUSED = ovsdb_schema_from_json(schema_json,
> > +                                                                  &schema);
> > +    ovs_assert(!error);
> > +    json_destroy(schema_json);
> > +
> > +    struct db *db = xzalloc(sizeof *db);
> > +    db->filename = xstrdup("<internal>");
> > +    db->db = ovsdb_create(schema);
> > +    add_db(config, db->db->schema->name, db);
> > +}
> 
> Probably not a huge deal, since there's a single instance that runs as
> long as the process, but I don't think there's anything that free the
> memory allocated from this internal database.

It's not really different from other databases, other than it being
updated from the ovsdb-server main loop rather than from JSON-RPC
clients, so I think that any freeing (or not freeing) applied to other
databases should work equally well (or badly) for this one.

> > +/* Updates the Database table in the _Server database. */
> > +static void
> > +update_server_status(struct shash *all_dbs)
> > +{
> > +    struct db *server_db = shash_find_data(all_dbs, "_Server");
> > +    struct ovsdb_table *database_table = shash_find_data(
> > +        &server_db->db->tables, "Database");
> > +    struct ovsdb_txn *txn = ovsdb_txn_create(server_db->db);
> > +
> > +    /* Update rows for databases that still exist.
> > +     * Delete rows for databases that no longer exist. */
> > +    const struct ovsdb_row *row, *next_row;
> > +    HMAP_FOR_EACH_SAFE (row, next_row, hmap_node, &database_table->rows) {
> > +        const char *name;
> > +        ovsdb_util_read_string_column(row, "name", &name);
> > +        struct db *db = shash_find_data(all_dbs, name);
> > +        if (!db || !db->db) {
> > +            ovsdb_txn_row_delete(txn, row);
> > +        } else {
> > +            update_database_status(ovsdb_txn_row_modify(txn, row), db);
> >         }
> >     }
> > +
> > +    /* Add rows for new databases.
> > +     *
> > +     * This is O(n**2) but usually there are only 2 or 3 databases. */
> > +    struct shash_node *node;
> > +    SHASH_FOR_EACH (node, all_dbs) {
> > +        struct db *db = node->data;
> > +
> > +        if (!db->db) {
> > +            continue;
> > +        }
> > +
> > +        HMAP_FOR_EACH (row, hmap_node, &database_table->rows) {
> > +            const char *name;
> > +            ovsdb_util_read_string_column(row, "name", &name);
> > +            if (!strcmp(name, node->name)) {
> > +                goto next;
> > +            }
> > +        }
> > +
> > +        /* Add row. */
> > +        struct ovsdb_row *row = ovsdb_row_create(database_table);
> > +        uuid_generate(ovsdb_row_get_uuid_rw(row));
> > +        update_database_status(row, db);
> > +        ovsdb_txn_row_insert(txn, row);
> > +
> > +    next:;
> > +    }
> > +
> > +    commit_txn(txn, "_Server");
> > }
> 
> I see a memory leak when I run unit tests (e.g., "testing database multiplexing implementation") under valgrind:
> 
> ==123484== 6 bytes in 6 blocks are definitely lost in loss record 4 of 115
> ==123484==    at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd6
> 4-linux.so)
> ==123484==    by 0x437084: xmalloc (util.c:120)
> ==123484==    by 0x4370B3: xmemdup (util.c:142)
> ==123484==    by 0x4257BE: ovsdb_atom_init_default (ovsdb-data.c:77)
> ==123484==    by 0x42580D: alloc_default_atoms (ovsdb-data.c:317)
> ==123484==    by 0x425F48: ovsdb_datum_init_default (ovsdb-data.c:913)
> ==123484==    by 0x412C12: ovsdb_row_create (row.c:56)
> ==123484==    by 0x4051F7: update_server_status (ovsdb-server.c:1030)
> ==123484==    by 0x4051F7: main_loop (ovsdb-server.c:226)
> ==123484==    by 0x4051F7: main (ovsdb-server.c:438)

Oops.  Fixed.

> > diff --git a/ovsdb/ovsdb-util.c b/ovsdb/ovsdb-util.c
> > index 5ee5e4ddaf8d..06d25af49a18 100644
> > --- a/ovsdb/ovsdb-util.c
> > +++ b/ovsdb/ovsdb-util.c
> > @@ -22,6 +22,38 @@
> > 
> > VLOG_DEFINE_THIS_MODULE(ovsdb_util);
> > 
> > +static void
> > +ovsdb_util_clear_column(struct ovsdb_row *row, const char *column_name)
> > +{
> > ...
> > +    if (column->type.n_min) {
> > +        if (!VLOG_DROP_DBG(&rl)) {
> > +            char *type_name = ovsdb_type_to_english(&column->type);
> > +            VLOG_DBG("Table `%s' column `%s' has type %s, which requires "
> > +                     "a value, when it was expected to be optional",
> > +                     schema->name, column_name, type_name);
> 
> This error message seemed a little unclear to me.  What about
> something along the lines of "Table x column y has type z, which
> requires a value, but an attempt was made to clear it"?

That is better.  Thank you.  Changed.

> Acked-by: Justin Pettit <jpettit@ovn.org>

Thanks.

I'm appending the incremental that I folded in.

--8<--------------------------cut here-------------------------->8--

diff --git a/Documentation/ref/index.rst b/Documentation/ref/index.rst
index d83b809f5e5b..5c851323009c 100644
--- a/Documentation/ref/index.rst
+++ b/Documentation/ref/index.rst
@@ -109,6 +109,10 @@ The remainder are still in roff format can be found below:
      - `(pdf) <http://openvswitch.org/support/dist-docs/ovsdb-server.1.pdf>`__
      - `(html) <http://openvswitch.org/support/dist-docs/ovsdb-server.1.html>`__
      - `(plain text) <http://openvswitch.org/support/dist-docs/ovsdb-server.1.txt>`__
+   * - ovsdb-server(5)
+     - `(pdf) <http://openvswitch.org/support/dist-docs/ovsdb-server.5.pdf>`__
+     - `(html) <http://openvswitch.org/support/dist-docs/ovsdb-server.5.html>`__
+     - `(plain text) <http://openvswitch.org/support/dist-docs/ovsdb-server.5.txt>`__
    * - ovsdb-tool(1)
      - `(pdf) <http://openvswitch.org/support/dist-docs/ovsdb-tool.1.pdf>`__
      - `(html) <http://openvswitch.org/support/dist-docs/ovsdb-tool.1.html>`__
diff --git a/debian/openvswitch-switch.manpages b/debian/openvswitch-switch.manpages
index a2f661a3e58c..c85cbfd30b8f 100644
--- a/debian/openvswitch-switch.manpages
+++ b/debian/openvswitch-switch.manpages
@@ -1,4 +1,5 @@
 ovsdb/ovsdb-server.1
+ovsdb/ovsdb-server.5
 utilities/ovs-ctl.8
 utilities/ovs-dpctl-top.8
 utilities/ovs-dpctl.8
diff --git a/ovsdb/automake.mk b/ovsdb/automake.mk
index c90e2e5b77f9..5760cba44681 100644
--- a/ovsdb/automake.mk
+++ b/ovsdb/automake.mk
@@ -128,8 +128,7 @@ EXTRA_DIST += ovsdb/_server.xml
 CLEANFILES += ovsdb/ovsdb-server.5
 man_MANS += ovsdb/ovsdb-server.5
 ovsdb/ovsdb-server.5: \
-	ovsdb/ovsdb-doc ovsdb/_server.xml ovsdb/_server.ovsschema \
-	$(VSWITCH_PIC)
+	ovsdb/ovsdb-doc ovsdb/_server.xml ovsdb/_server.ovsschema
 	$(AM_V_GEN)$(OVSDB_DOC) \
 		--version=$(VERSION) \
 		$(srcdir)/ovsdb/_server.ovsschema \
diff --git a/ovsdb/ovsdb-util.c b/ovsdb/ovsdb-util.c
index 06d25af49a18..4bda9782bc06 100644
--- a/ovsdb/ovsdb-util.c
+++ b/ovsdb/ovsdb-util.c
@@ -40,7 +40,7 @@ ovsdb_util_clear_column(struct ovsdb_row *row, const char *column_name)
         if (!VLOG_DROP_DBG(&rl)) {
             char *type_name = ovsdb_type_to_english(&column->type);
             VLOG_DBG("Table `%s' column `%s' has type %s, which requires "
-                     "a value, when it was expected to be optional",
+                     "a value, but an attempt was made to clear it",
                      schema->name, column_name, type_name);
             free(type_name);
         }
@@ -225,6 +225,7 @@ ovsdb_util_write_singleton(struct ovsdb_row *row, const char *column_name,
         if (ovsdb_atom_equals(&datum->keys[0], atom, type)) {
             return;
         }
+        ovsdb_atom_destroy(&datum->keys[0], type);
     } else {
         ovsdb_datum_destroy(datum, &column->type);
         datum->n = 1;
diff --git a/rhel/openvswitch-fedora.spec.in b/rhel/openvswitch-fedora.spec.in
index ede62c8442c3..1468bb5c4e3b 100644
--- a/rhel/openvswitch-fedora.spec.in
+++ b/rhel/openvswitch-fedora.spec.in
@@ -564,6 +564,7 @@ fi
 %{_mandir}/man1/ovsdb-client.1*
 %{_mandir}/man1/ovsdb-server.1*
 %{_mandir}/man1/ovsdb-tool.1*
+%{_mandir}/man5/ovsdb-server.5*
 %{_mandir}/man5/ovs-vswitchd.conf.db.5*
 %{_mandir}/man5/ovsdb.5*
 %{_mandir}/man5/vtep.5*
diff --git a/rhel/openvswitch.spec.in b/rhel/openvswitch.spec.in
index 104b2732956d..26eb73f3b44d 100644
--- a/rhel/openvswitch.spec.in
+++ b/rhel/openvswitch.spec.in
@@ -227,6 +227,7 @@ exit 0
 /usr/share/man/man1/ovsdb-client.1.gz
 /usr/share/man/man1/ovsdb-server.1.gz
 /usr/share/man/man1/ovsdb-tool.1.gz
+/usr/share/man/man5/ovsdb-server.5.gz
 /usr/share/man/man5/ovs-vswitchd.conf.db.5.gz
 %{_mandir}/man5/ovsdb.5*
 /usr/share/man/man5/vtep.5.gz
diff --git a/xenserver/openvswitch-xen.spec.in b/xenserver/openvswitch-xen.spec.in
index 564fad68488e..c1fbcc75ebea 100644
--- a/xenserver/openvswitch-xen.spec.in
+++ b/xenserver/openvswitch-xen.spec.in
@@ -481,6 +481,7 @@ exit 0
 /usr/share/man/man1/ovsdb-client.1.gz
 /usr/share/man/man1/ovsdb-server.1.gz
 /usr/share/man/man1/ovsdb-tool.1.gz
+/usr/share/man/man5/ovsdb-server.5.gz
 /usr/share/man/man5/ovs-vswitchd.conf.db.5.gz
 /usr/share/man/man5/vtep.5.gz
 /usr/share/man/man7/ovs-fields.7.gz
Justin Pettit Feb. 1, 2018, 7:05 p.m. | #3
> On Feb 1, 2018, at 10:59 AM, Ben Pfaff <blp@ovn.org> wrote:
> 
> On Tue, Jan 30, 2018 at 03:27:18PM -0800, Justin Pettit wrote:
>> 
>>> On Dec 31, 2017, at 9:16 PM, Ben Pfaff <blp@ovn.org> wrote:
>>> 
>>> @@ -328,6 +333,7 @@ main(int argc, char *argv[])
>>>            ovs_fatal(0, "%s", error);
>>>        }
>>>    }
>>> +    add_server_db(&server_config);
>> 
>> I don't think there's anything that frees 'server_config'.  Not a huge
>> deal since it would be on shutdown, but it's always nice to have a
>> clean valgrind run.
> 
> server_config is a local variable, and not a pointer.  What kind of
> freeing would you like to see?

I meant the things that were allocated in add_server_db().

--Justin
Ben Pfaff Feb. 1, 2018, 7:10 p.m. | #4
On Thu, Feb 01, 2018 at 11:05:35AM -0800, Justin Pettit wrote:
> 
> > On Feb 1, 2018, at 10:59 AM, Ben Pfaff <blp@ovn.org> wrote:
> > 
> > On Tue, Jan 30, 2018 at 03:27:18PM -0800, Justin Pettit wrote:
> >> 
> >>> On Dec 31, 2017, at 9:16 PM, Ben Pfaff <blp@ovn.org> wrote:
> >>> 
> >>> @@ -328,6 +333,7 @@ main(int argc, char *argv[])
> >>>            ovs_fatal(0, "%s", error);
> >>>        }
> >>>    }
> >>> +    add_server_db(&server_config);
> >> 
> >> I don't think there's anything that frees 'server_config'.  Not a huge
> >> deal since it would be on shutdown, but it's always nice to have a
> >> clean valgrind run.
> > 
> > server_config is a local variable, and not a pointer.  What kind of
> > freeing would you like to see?
> 
> I meant the things that were allocated in add_server_db().

OK.

I think that these are the same things that are getting allocated in
open_db() for "real" databases, so they should be getting freed in the
same way.  (Maybe there is a memory leak in that case too, but I do not
see it yet.)
Justin Pettit Feb. 1, 2018, 7:11 p.m. | #5
> On Feb 1, 2018, at 11:10 AM, Ben Pfaff <blp@ovn.org> wrote:
> 
> I think that these are the same things that are getting allocated in
> open_db() for "real" databases, so they should be getting freed in the
> same way.  (Maybe there is a memory leak in that case too, but I do not
> see it yet.)

That makes sense.  Thank you.

--Justin

Patch

diff --git a/Makefile.am b/Makefile.am
index d7cfdcd52a98..8eef4e3f5640 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -82,14 +82,6 @@  EXTRA_DIST = \
 	.travis/osx-prepare.sh \
 	appveyor.yml \
 	boot.sh \
-	build-aux/cccl \
-	build-aux/cksum-schema-check \
-	build-aux/calculate-schema-cksum \
-	build-aux/dist-docs \
-	build-aux/dpdkstrip.py \
-	build-aux/sodepends.py \
-	build-aux/soexpand.py \
-	build-aux/xml2nroff \
 	$(MAN_FRAGMENTS) \
 	$(MAN_ROOTS) \
 	Vagrantfile \
diff --git a/NEWS b/NEWS
index af98c2f80f5a..b697e4968072 100644
--- a/NEWS
+++ b/NEWS
@@ -5,6 +5,8 @@  Post-v2.8.0
      * New high-level documentation in ovsdb(7).
      * New file format documentation for developers in ovsdb(5).
      * Protocol documentation moved from ovsdb-server(1) to ovsdb-server(7).
+     * ovsdb-server now always hosts a built-in database named _Server.  See
+       ovsdb-server(5) for more details.
      * ovsdb-client: New "get-schema-cksum" and "query" commands.
      * ovsdb-client: New "backup" and "restore" commands.
      * ovsdb-tool: New "db-name" and "schema-name" commands.
diff --git a/build-aux/automake.mk b/build-aux/automake.mk
index 6baafab0e867..a1f2f856f939 100644
--- a/build-aux/automake.mk
+++ b/build-aux/automake.mk
@@ -1,4 +1,14 @@ 
-# This file is purely used for checking the style of the python build tools.
+EXTRA_DIST += \
+	build-aux/calculate-schema-cksum \
+	build-aux/cccl \
+	build-aux/cksum-schema-check \
+	build-aux/dist-docs \
+	build-aux/dpdkstrip.py \
+	build-aux/sodepends.py \
+	build-aux/soexpand.py \
+	build-aux/text2c \
+	build-aux/xml2nroff
+
 FLAKE8_PYFILES += \
     $(srcdir)/build-aux/xml2nroff \
     build-aux/dpdkstrip.py \
diff --git a/build-aux/text2c b/build-aux/text2c
new file mode 100755
index 000000000000..cb1f256f1775
--- /dev/null
+++ b/build-aux/text2c
@@ -0,0 +1,16 @@ 
+#! /usr/bin/python
+
+import re
+import sys
+
+"""This utility reads its input, which should be plain text, and
+prints it back transformed into quoted strings that may be #included
+into C source code."""
+
+while True:
+    line = sys.stdin.readline()
+    if not line:
+        break
+
+    s = line.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n")
+    print('"' + s + '"')
diff --git a/ovsdb/.gitignore b/ovsdb/.gitignore
index d715dee925b9..fbcefafc6e97 100644
--- a/ovsdb/.gitignore
+++ b/ovsdb/.gitignore
@@ -1,3 +1,5 @@ 
+/_server.ovsschema.inc
+/_server.ovsschema.stamp
 /ovsdb-client
 /ovsdb-client.1
 /ovsdb-doc
@@ -5,6 +7,7 @@ 
 /ovsdb-idlc
 /ovsdb-server
 /ovsdb-server.1
+/ovsdb-server.5
 /ovsdb-tool
 /ovsdb-tool.1
 /libovsdb.pc
diff --git a/ovsdb/_server.ovsschema b/ovsdb/_server.ovsschema
new file mode 100644
index 000000000000..8997bae5fa36
--- /dev/null
+++ b/ovsdb/_server.ovsschema
@@ -0,0 +1,9 @@ 
+{"name": "_Server",
+ "version": "1.0.0",
+ "cksum": "3931859656 185",
+ "tables": {
+   "Database": {
+     "columns": {
+       "name": {"type": "string"},
+       "schema": {"type": "string"}},
+     "isRoot": true}}}
diff --git a/ovsdb/_server.xml b/ovsdb/_server.xml
new file mode 100644
index 000000000000..a55beb9bd6de
--- /dev/null
+++ b/ovsdb/_server.xml
@@ -0,0 +1,31 @@ 
+<?xml version="1.0" encoding="utf-8"?>
+<database name="ovsdb-server" title="ovsdb-server _Server schema">
+  <p>
+    Every <code>ovsdb-server</code> (version 2.9 or later) always hosts an
+    instance of this schema, which holds information on the status and
+    configuration of the server itself.  This database is read-only.  This
+    manpage describes the schema for this database.
+  </p>
+
+  <table name="Database" title="Databases.">
+    <p>
+      This table describes the databases hosted by the database server, with
+      one row per database.  As its database configuration and status changes,
+      the server automatically and immediately updates the table to match.
+    </p>
+    <p>
+      Clients can use the <code>_uuid</code> column in this table as a
+      generation number.  The server generates a fresh <code>_uuid</code> every
+      time it adds a database, so that removing and then re-adding a database
+      to the server causes its row <code>_uuid</code> to change.
+    </p>
+
+    <column name="name">
+      The database's name, as specified in its schema.
+    </column>
+
+    <column name="schema">
+      The database schema, as a JSON string.
+    </column>
+  </table>
+</database>
diff --git a/ovsdb/automake.mk b/ovsdb/automake.mk
index d040fc6de886..c90e2e5b77f9 100644
--- a/ovsdb/automake.mk
+++ b/ovsdb/automake.mk
@@ -109,3 +109,29 @@  EXTRA_DIST += ovsdb/ovsdb-dot.in ovsdb/dot2pic
 noinst_SCRIPTS += ovsdb/ovsdb-dot
 CLEANFILES += ovsdb/ovsdb-dot
 OVSDB_DOT = $(run_python) $(srcdir)/ovsdb/ovsdb-dot.in
+
+EXTRA_DIST += ovsdb/_server.ovsschema
+CLEANFILES += ovsdb/_server.ovsschema.inc
+ovsdb/ovsdb-server.o: ovsdb/_server.ovsschema.inc
+ovsdb/_server.ovsschema.inc: ovsdb/_server.ovsschema $(srcdir)/build-aux/text2c
+	$(AM_V_GEN)$(run_python) $(srcdir)/build-aux/text2c < $< > $@.tmp
+	$(AM_V_at)mv $@.tmp $@
+
+# Version checking for _server.ovsschema.
+ALL_LOCAL += ovsdb/_server.ovsschema.stamp
+ovsdb/_server.ovsschema.stamp: ovsdb/_server.ovsschema
+	$(srcdir)/build-aux/cksum-schema-check $? $@
+CLEANFILES += ovsdb/_server.ovsschema.stamp
+
+# _Server schema documentation
+EXTRA_DIST += ovsdb/_server.xml
+CLEANFILES += ovsdb/ovsdb-server.5
+man_MANS += ovsdb/ovsdb-server.5
+ovsdb/ovsdb-server.5: \
+	ovsdb/ovsdb-doc ovsdb/_server.xml ovsdb/_server.ovsschema \
+	$(VSWITCH_PIC)
+	$(AM_V_GEN)$(OVSDB_DOC) \
+		--version=$(VERSION) \
+		$(srcdir)/ovsdb/_server.ovsschema \
+		$(srcdir)/ovsdb/_server.xml > $@.tmp && \
+	mv $@.tmp $@
diff --git a/ovsdb/ovsdb-server.1.in b/ovsdb/ovsdb-server.1.in
index 15ff77fd28aa..dfca40d4ef79 100644
--- a/ovsdb/ovsdb-server.1.in
+++ b/ovsdb/ovsdb-server.1.in
@@ -45,6 +45,12 @@  example, \fBovsdb\-tool create\fR.
 This OVSDB implementation supports standalone and active-backup
 databases, as well as database replication.
 See the Service Models section of \fBovsdb\fR(7) for more information.
+.PP
+In addition to user-specified databases, \fBovsdb\-server\fR version
+2.9 and later also always hosts a built-in database named
+\fB_Server\fR.  Please see \fBovsdb\-server\fR(5) for documentation on
+this database's schema.
+.
 .SH OPTIONS
 .
 .IP "\fB\-\-remote=\fIremote\fR"
diff --git a/ovsdb/ovsdb-server.c b/ovsdb/ovsdb-server.c
index 1efb5552da5a..dd0cdfe6a38b 100644
--- a/ovsdb/ovsdb-server.c
+++ b/ovsdb/ovsdb-server.c
@@ -65,6 +65,7 @@  struct db {
     char *filename;
     struct ovsdb_file *file;
     struct ovsdb *db;
+    struct uuid row_uuid;
 };
 
 /* SSL configuration. */
@@ -107,6 +108,7 @@  static unixctl_cb_func ovsdb_server_remove_database;
 static unixctl_cb_func ovsdb_server_list_databases;
 
 static char *open_db(struct server_config *config, const char *filename);
+static void add_server_db(struct server_config *);
 static void close_db(struct db *db);
 
 static void parse_options(int *argc, char **argvp[],
@@ -124,6 +126,7 @@  static void report_error_if_changed(char *error, char **last_errorp);
 static void update_remote_status(const struct ovsdb_jsonrpc_server *jsonrpc,
                                  const struct sset *remotes,
                                  struct shash *all_dbs);
+static void update_server_status(struct shash *all_dbs);
 
 static void save_config__(FILE *config_file, const struct sset *remotes,
                           const struct sset *db_filenames,
@@ -214,6 +217,8 @@  main_loop(struct ovsdb_jsonrpc_server *jsonrpc, struct shash *all_dbs,
             update_remote_status(jsonrpc, remotes, all_dbs);
         }
 
+        update_server_status(all_dbs);
+
         memory_wait();
         if (*is_backup) {
             replication_wait();
@@ -328,6 +333,7 @@  main(int argc, char *argv[])
             ovs_fatal(0, "%s", error);
         }
     }
+    add_server_db(&server_config);
 
     error = reconfigure_remotes(jsonrpc, &all_dbs, &remotes);
     if (!error) {
@@ -490,6 +496,16 @@  close_db(struct db *db)
     free(db);
 }
 
+static void
+add_db(struct server_config *config, const char *name, struct db *db)
+{
+    db->row_uuid = UUID_ZERO;
+    shash_add_assert(config->all_dbs, name, db);
+    bool ok OVS_UNUSED = ovsdb_jsonrpc_server_add_db(config->jsonrpc,
+                                                     db->db);
+    ovs_assert(ok);
+}
+
 static char *
 open_db(struct server_config *config, const char *filename)
 {
@@ -524,6 +540,27 @@  open_db(struct server_config *config, const char *filename)
     return error;
 }
 
+/* Add the internal _Server database to the server configuration. */
+static void
+add_server_db(struct server_config *config)
+{
+    struct json *schema_json = json_from_string(
+#include "ovsdb/_server.ovsschema.inc"
+        );
+    ovs_assert(schema_json->type == JSON_OBJECT);
+
+    struct ovsdb_schema *schema;
+    struct ovsdb_error *error OVS_UNUSED = ovsdb_schema_from_json(schema_json,
+                                                                  &schema);
+    ovs_assert(!error);
+    json_destroy(schema_json);
+
+    struct db *db = xzalloc(sizeof *db);
+    db->filename = xstrdup("<internal>");
+    db->db = ovsdb_create(schema);
+    add_db(config, db->db->schema->name, db);
+}
+
 static char * OVS_WARN_UNUSED_RESULT
 parse_db_column__(const struct shash *all_dbs,
                   const char *name_, char *name,
@@ -888,6 +925,18 @@  update_remote_rows(const struct shash *all_dbs, const struct db *db_,
 }
 
 static void
+commit_txn(struct ovsdb_txn *txn, const char *name)
+{
+    struct ovsdb_error *error = ovsdb_txn_commit(txn, false);
+    if (error) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+        char *msg = ovsdb_error_to_string_free(error);
+        VLOG_ERR_RL(&rl, "Failed to update %s: %s", name, msg);
+        free(msg);
+    }
+}
+
+static void
 update_remote_status(const struct ovsdb_jsonrpc_server *jsonrpc,
                      const struct sset *remotes,
                      struct shash *all_dbs)
@@ -903,14 +952,84 @@  update_remote_status(const struct ovsdb_jsonrpc_server *jsonrpc,
             update_remote_rows(all_dbs, db, remote, jsonrpc, txn);
         }
 
-        struct ovsdb_error *error = ovsdb_txn_commit(txn, false);
-        if (error) {
-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-            char *msg = ovsdb_error_to_string_free(error);
-            VLOG_ERR_RL(&rl, "Failed to update remote status: %s", msg);
-            free(msg);
+        commit_txn(txn, node->name);
+    }
+}
+
+/* Updates 'row', a row in the _Server database's Database table, to match
+ * 'db'. */
+static void
+update_database_status(struct ovsdb_row *row, struct db *db)
+{
+    ovsdb_util_write_string_column(row, "name", db->db->schema->name);
+
+    const struct uuid *row_uuid = ovsdb_row_get_uuid(row);
+    if (!uuid_equals(row_uuid, &db->row_uuid)) {
+        db->row_uuid = *row_uuid;
+
+        /* The schema can only change if the row UUID changes, so only update
+         * it in that case.  Presumably, this is worth optimizing because
+         * schemas are often kilobytes in size and nontrivial to serialize. */
+        struct json *json_schema = ovsdb_schema_to_json(db->db->schema);
+        char *schema = json_to_string(json_schema, JSSF_SORT);
+        ovsdb_util_write_string_column(row, "schema", schema);
+        free(schema);
+        json_destroy(json_schema);
+    }
+}
+
+/* Updates the Database table in the _Server database. */
+static void
+update_server_status(struct shash *all_dbs)
+{
+    struct db *server_db = shash_find_data(all_dbs, "_Server");
+    struct ovsdb_table *database_table = shash_find_data(
+        &server_db->db->tables, "Database");
+    struct ovsdb_txn *txn = ovsdb_txn_create(server_db->db);
+
+    /* Update rows for databases that still exist.
+     * Delete rows for databases that no longer exist. */
+    const struct ovsdb_row *row, *next_row;
+    HMAP_FOR_EACH_SAFE (row, next_row, hmap_node, &database_table->rows) {
+        const char *name;
+        ovsdb_util_read_string_column(row, "name", &name);
+        struct db *db = shash_find_data(all_dbs, name);
+        if (!db || !db->db) {
+            ovsdb_txn_row_delete(txn, row);
+        } else {
+            update_database_status(ovsdb_txn_row_modify(txn, row), db);
         }
     }
+
+    /* Add rows for new databases.
+     *
+     * This is O(n**2) but usually there are only 2 or 3 databases. */
+    struct shash_node *node;
+    SHASH_FOR_EACH (node, all_dbs) {
+        struct db *db = node->data;
+
+        if (!db->db) {
+            continue;
+        }
+
+        HMAP_FOR_EACH (row, hmap_node, &database_table->rows) {
+            const char *name;
+            ovsdb_util_read_string_column(row, "name", &name);
+            if (!strcmp(name, node->name)) {
+                goto next;
+            }
+        }
+
+        /* Add row. */
+        struct ovsdb_row *row = ovsdb_row_create(database_table);
+        uuid_generate(ovsdb_row_get_uuid_rw(row));
+        update_database_status(row, db);
+        ovsdb_txn_row_insert(txn, row);
+
+    next:;
+    }
+
+    commit_txn(txn, "_Server");
 }
 
 /* Reconfigures ovsdb-server's remotes based on information in the database. */
diff --git a/ovsdb/ovsdb-util.c b/ovsdb/ovsdb-util.c
index 5ee5e4ddaf8d..06d25af49a18 100644
--- a/ovsdb/ovsdb-util.c
+++ b/ovsdb/ovsdb-util.c
@@ -22,6 +22,38 @@ 
 
 VLOG_DEFINE_THIS_MODULE(ovsdb_util);
 
+static void
+ovsdb_util_clear_column(struct ovsdb_row *row, const char *column_name)
+{
+    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+    const struct ovsdb_table_schema *schema = row->table->schema;
+    const struct ovsdb_column *column;
+
+    column = ovsdb_table_schema_get_column(schema, column_name);
+    if (!column) {
+        VLOG_DBG_RL(&rl, "Table `%s' has no `%s' column",
+                    schema->name, column_name);
+        return;
+    }
+
+    if (column->type.n_min) {
+        if (!VLOG_DROP_DBG(&rl)) {
+            char *type_name = ovsdb_type_to_english(&column->type);
+            VLOG_DBG("Table `%s' column `%s' has type %s, which requires "
+                     "a value, when it was expected to be optional",
+                     schema->name, column_name, type_name);
+            free(type_name);
+        }
+        return;
+    }
+
+    struct ovsdb_datum *datum = &row->fields[column->index];
+    if (datum->n) {
+        ovsdb_datum_destroy(datum, &column->type);
+        ovsdb_datum_init_empty(datum);
+    }
+}
+
 struct ovsdb_datum *
 ovsdb_util_get_datum(struct ovsdb_row *row, const char *column_name,
                     const enum ovsdb_atomic_type key_type,
@@ -164,29 +196,74 @@  ovsdb_util_read_bool_column(const struct ovsdb_row *row,
     return atom != NULL;
 }
 
-void
-ovsdb_util_write_bool_column(struct ovsdb_row *row, const char *column_name,
-                             bool value)
+bool
+ovsdb_util_read_uuid_column(const struct ovsdb_row *row,
+                            const char *column_name, struct uuid *uuid)
+{
+    const union ovsdb_atom *atom;
+
+    atom = ovsdb_util_read_column(row, column_name, OVSDB_TYPE_UUID);
+    *uuid = atom ? atom->uuid : UUID_ZERO;
+    return atom != NULL;
+}
+
+static void
+ovsdb_util_write_singleton(struct ovsdb_row *row, const char *column_name,
+                           const union ovsdb_atom *atom,
+                           enum ovsdb_atomic_type type)
 {
     const struct ovsdb_column *column;
     struct ovsdb_datum *datum;
 
     column = ovsdb_table_schema_get_column(row->table->schema, column_name);
-    datum = ovsdb_util_get_datum(row, column_name, OVSDB_TYPE_BOOLEAN,
-                                 OVSDB_TYPE_VOID, 1);
+    datum = ovsdb_util_get_datum(row, column_name, type, OVSDB_TYPE_VOID, 1);
     if (!datum) {
         return;
     }
 
-    if (datum->n != 1) {
+    if (datum->n == 1) {
+        if (ovsdb_atom_equals(&datum->keys[0], atom, type)) {
+            return;
+        }
+    } else {
         ovsdb_datum_destroy(datum, &column->type);
-
         datum->n = 1;
         datum->keys = xmalloc(sizeof *datum->keys);
         datum->values = NULL;
     }
+    ovsdb_atom_clone(&datum->keys[0], atom, type);
+}
 
-    datum->keys[0].boolean = value;
+void
+ovsdb_util_write_bool_column(struct ovsdb_row *row, const char *column_name,
+                             bool value)
+{
+    const union ovsdb_atom atom = { .boolean = value };
+    ovsdb_util_write_singleton(row, column_name, &atom, OVSDB_TYPE_BOOLEAN);
+}
+
+void
+ovsdb_util_write_uuid_column(struct ovsdb_row *row, const char *column_name,
+                             const struct uuid *uuid)
+{
+    if (uuid) {
+        const union ovsdb_atom atom = { .uuid = *uuid };
+        ovsdb_util_write_singleton(row, column_name, &atom, OVSDB_TYPE_UUID);
+    } else {
+        ovsdb_util_clear_column(row, column_name);
+    }
+}
+
+void
+ovsdb_util_write_string_column(struct ovsdb_row *row, const char *column_name,
+                               const char *string)
+{
+    if (string) {
+        const union ovsdb_atom atom = { .string = CONST_CAST(char *, string) };
+        ovsdb_util_write_singleton(row, column_name, &atom, OVSDB_TYPE_STRING);
+    } else {
+        ovsdb_util_clear_column(row, column_name);
+    }
 }
 
 void
diff --git a/ovsdb/ovsdb-util.h b/ovsdb/ovsdb-util.h
index abd81ff38cd2..a0404a3a7ff0 100644
--- a/ovsdb/ovsdb-util.h
+++ b/ovsdb/ovsdb-util.h
@@ -38,6 +38,9 @@  bool ovsdb_util_read_integer_column(const struct ovsdb_row *row,
 bool ovsdb_util_read_string_column(const struct ovsdb_row *row,
                                    const char *column_name,
                                    const char **stringp);
+void ovsdb_util_write_string_column(struct ovsdb_row *row,
+                                    const char *column_name,
+                                    const char *string);
 void ovsdb_util_write_string_string_column(struct ovsdb_row *row,
                                            const char *column_name,
                                            char **keys, char **values,
@@ -48,5 +51,11 @@  bool ovsdb_util_read_bool_column(const struct ovsdb_row *row,
 void ovsdb_util_write_bool_column(struct ovsdb_row *row,
                                   const char *column_name,
                                   bool value);
+bool ovsdb_util_read_uuid_column(const struct ovsdb_row *row,
+                                 const char *column_name,
+                                 struct uuid *);
+void ovsdb_util_write_uuid_column(struct ovsdb_row *row,
+                                  const char *column_name,
+                                  const struct uuid *);
 
 #endif /* ovsdb/util.h */
diff --git a/tests/ovsdb-server.at b/tests/ovsdb-server.at
index 968356781604..07ceda92496d 100644
--- a/tests/ovsdb-server.at
+++ b/tests/ovsdb-server.at
@@ -137,20 +137,31 @@  AT_CHECK([uuidfilt output], [0],
          [test ! -e pid || kill `cat pid`])
 AT_CLEANUP
 
+dnl CHECK_DBS([databases])
+dnl
+dnl Checks that ovsdb-server hosts the given 'databases', each of which
+dnl needs to be followed by a newline.
+m4_define([CHECK_DBS],
+  [AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/list-dbs],
+  [0], [_Server
+$1])
+AT_CHECK([ovsdb-client --no-headings dump _Server Database name | sort], [0], [dnl
+Database table
+_Server
+$1])])
+
 AT_SETUP([database multiplexing implementation])
 AT_KEYWORDS([ovsdb server positive])
 ordinal_schema > schema1
 constraint_schema > schema2
 AT_CHECK([ovsdb-tool create db1 schema1], [0], [ignore], [ignore])
 AT_CHECK([ovsdb-tool create db2 schema2], [0], [ignore], [ignore])
-AT_CHECK([ovsdb-server --detach --no-chdir --pidfile --remote=punix:socket db1 db2], [0], [ignore], [ignore])
-AT_CHECK(
-  [[ovsdb-client list-dbs unix:socket]], 
-  [0], [constraints
+AT_CHECK([ovsdb-server --detach --no-chdir --pidfile --remote=punix:db.sock db1 db2], [0], [ignore], [ignore])
+CHECK_DBS([constraints
 ordinals
-], [ignore], [test ! -e pid || kill `cat pid`])
+])
 AT_CHECK(
-  [[ovstest test-jsonrpc request unix:socket get_schema [\"nonexistent\"]]], [0],
+  [[ovstest test-jsonrpc request unix:db.sock get_schema [\"nonexistent\"]]], [0],
   [[{"error":{"details":"get_schema request specifies unknown database nonexistent","error":"unknown database","syntax":"[\"nonexistent\"]"},"id":0,"result":null}
 ]], [], [test ! -e pid || kill `cat pid`])
 OVSDB_SERVER_SHUTDOWN
@@ -165,21 +176,19 @@  AT_CHECK([ovsdb-tool create db1 schema1], [0], [ignore], [ignore])
 AT_CHECK([ovsdb-tool create db2 schema2], [0], [ignore], [ignore])
 
 # Start ovsdb-server with just a single database - db1.
-AT_CHECK([ovsdb-server --detach --no-chdir --pidfile --remote=punix:socket db1], [0])
-AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/list-dbs],
-  [0], [ordinals
+AT_CHECK([ovsdb-server --detach --no-chdir --pidfile --remote=punix:db.sock db1], [0])
+CHECK_DBS([ordinals
 ])
 
 # Add the second database.
 AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/add-db db2], [0])
-AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/list-dbs],
-  [0], [constraints
+CHECK_DBS([constraints
 ordinals
 ])
 
 # The databases are responsive.
-AT_CHECK([ovsdb-client list-tables unix:socket constraints], [0], [ignore], [ignore])
-AT_CHECK([ovsdb-client list-tables unix:socket ordinals], [0], [ignore], [ignore])
+AT_CHECK([ovsdb-client list-tables unix:db.sock constraints], [0], [ignore], [ignore])
+AT_CHECK([ovsdb-client list-tables unix:db.sock ordinals], [0], [ignore], [ignore])
 
 # Add an already added database.
 if test $IS_WIN32 = "yes"; then
@@ -205,25 +214,23 @@  ovs-appctl: ovsdb-server: server returned an error
 AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/add-remote db:ordinals,ordinals,name], [0])
 AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/list-remotes],
   [0], [db:ordinals,ordinals,name
-punix:socket
+punix:db.sock
 ])
 
 # Removing db1 has no effect on its remote.
 AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/remove-db ordinals], [0])
-AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/list-dbs],
-  [0], [constraints
+CHECK_DBS([constraints
 ])
 AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/list-remotes],
   [0], [db:ordinals,ordinals,name
-punix:socket
+punix:db.sock
 ])
-AT_CHECK([ovsdb-client list-tables unix:socket ordinals], [1], [ignore], [ignore])
+AT_CHECK([ovsdb-client list-tables unix:db.sock ordinals], [1], [ignore], [ignore])
 
 # Remove db2.
 AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/remove-db constraints], [0])
-AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/list-dbs],
-  [0], [])
-AT_CHECK([ovsdb-client list-tables unix:socket constraints], [1], [ignore], [ignore])
+CHECK_DBS()
+AT_CHECK([ovsdb-client list-tables unix:db.sock constraints], [1], [ignore], [ignore])
 
 # Remove a non-existent database.
 AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/remove-db ordinals], [2],
@@ -233,10 +240,9 @@  ovs-appctl: ovsdb-server: server returned an error
 
 # Add a removed database.
 AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/add-db db2], [0])
-AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/list-dbs],
-  [0], [constraints
+CHECK_DBS([constraints
 ])
-AT_CHECK([ovsdb-client list-tables unix:socket constraints], [0], [ignore], [ignore])
+AT_CHECK([ovsdb-client list-tables unix:db.sock constraints], [0], [ignore], [ignore])
 OVS_APP_EXIT_AND_WAIT([ovsdb-server])
 AT_CLEANUP
 
@@ -247,14 +253,13 @@  AT_SKIP_IF([test "$IS_WIN32" = "yes"])
 ordinal_schema > schema
 AT_CHECK([ovsdb-tool create db1 schema], [0], [ignore], [ignore])
 on_exit 'kill `cat *.pid`'
-AT_CHECK([ovsdb-server -v -vvlog:off --monitor --detach --no-chdir --pidfile --log-file db1])
+AT_CHECK([ovsdb-server -v -vvlog:off --monitor --detach --no-chdir --pidfile --log-file --remote=punix:db.sock db1])
 
 # Add the second database.
 constraint_schema > schema2
 AT_CHECK([ovsdb-tool create db2 schema2], [0], [ignore], [ignore])
 AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/add-db db2], [0])
-AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/list-dbs],
-  [0], [constraints
+CHECK_DBS([constraints
 ordinals
 ])
 
@@ -266,8 +271,7 @@  OVS_WAIT_WHILE([kill -0 `cat old.pid`])
 OVS_WAIT_UNTIL(
   [test -s ovsdb-server.pid && test `cat ovsdb-server.pid` != `cat old.pid`])
 OVS_WAIT_UNTIL([ovs-appctl -t ovsdb-server version])
-AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/list-dbs],
-  [0], [constraints
+CHECK_DBS([constraints
 ordinals
 ])
 OVS_APP_EXIT_AND_WAIT([ovsdb-server])
@@ -282,12 +286,11 @@  AT_CHECK([ovsdb-tool create db1 schema], [0], [ignore], [ignore])
 constraint_schema > schema2
 AT_CHECK([ovsdb-tool create db2 schema2], [0], [ignore], [ignore])
 on_exit 'kill `cat *.pid`'
-AT_CHECK([ovsdb-server -v -vvlog:off --monitor --detach --no-chdir --pidfile --log-file db1 db2])
+AT_CHECK([ovsdb-server -v -vvlog:off --monitor --detach --no-chdir --pidfile --log-file --remote=punix:db.sock db1 db2])
 
 # Remove the second database.
 AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/remove-db constraints])
-AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/list-dbs],
-  [0], [ordinals
+CHECK_DBS([ordinals
 ])
 
 # Kill the daemon process, making it look like a segfault,
@@ -298,8 +301,7 @@  OVS_WAIT_WHILE([kill -0 `cat old.pid`])
 OVS_WAIT_UNTIL(
   [test -s ovsdb-server.pid && test `cat ovsdb-server.pid` != `cat old.pid`])
 OVS_WAIT_UNTIL([ovs-appctl -t ovsdb-server version])
-AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/list-dbs],
-  [0], [ordinals
+CHECK_DBS([ordinals
 ])
 OVS_APP_EXIT_AND_WAIT([ovsdb-server])
 AT_CLEANUP