diff mbox series

[ovs-dev,RFC,44/52] ovsdb-server: Add new RPC "set_db_change_aware".

Message ID 20170919220125.32535-45-blp@ovn.org
State RFC
Headers show
Series clustering implementation | expand

Commit Message

Ben Pfaff Sept. 19, 2017, 10:01 p.m. UTC
The _Server database recently added to ovsdb-server can be used to dump out
information about databases, but monitoring updates to _Server is not yet
very useful because for historical reasons ovsdb-server drops all of its
OVSDB connections whenever databases are added or removed or otherwise
change in some major way.  It is not a good idea to change this behavior
for all clients, because some of them rely on it, but this commit
introduces a new RPC that allows clients that understand _Server to
suppress the connection-closing behavior.

Signed-off-by: Ben Pfaff <blp@ovn.org>
---
 ovsdb/_server.xml       |  34 +++++++++
 ovsdb/jsonrpc-server.c  | 195 +++++++++++++++++++++++++++++++++++++-----------
 ovsdb/jsonrpc-server.h  |   4 +-
 ovsdb/ovsdb-client.c    |  31 +++++++-
 ovsdb/ovsdb-server.1.in |  57 ++++++++++++++
 ovsdb/ovsdb-server.c    |  11 +--
 tests/ovsdb-server.at   |  34 ++++++++-
 7 files changed, 311 insertions(+), 55 deletions(-)
diff mbox series

Patch

diff --git a/ovsdb/_server.xml b/ovsdb/_server.xml
index a55beb9bd6de..8ef782fb97b2 100644
--- a/ovsdb/_server.xml
+++ b/ovsdb/_server.xml
@@ -13,6 +13,40 @@ 
       one row per database.  As its database configuration and status changes,
       the server automatically and immediately updates the table to match.
     </p>
+
+    <p>
+      The OVSDB protocol specified in RFC 7047 does not provide a way for an
+      OVSDB client to find out about some kinds of configuration changes, such
+      as about databases added or removed while a client is connected to the
+      server, or databases changing between read/write and read-only due to a
+      transition between active and backup roles.  This table provides a
+      solution: clients can monitor the table's contents to find out about
+      important changes.
+    </p>
+
+    <p>
+      Traditionally, <code>ovsdb-server</code> disconnects all of its clients
+      when a significant configuration change occurs, because this prompts a
+      well-written client to reassess what is available from the server when it
+      reconnects.  Because this table provides an alternative and more
+      efficient way to find out about those changes, OVS 2.9 also introduces
+      the <code>set_db_change_aware</code> RPC, documented in
+      <code>ovsdb-server</code>(1), to allow clients to suppress this
+      disconnection behavior.
+    </p>
+
+    <p>
+      When a database is removed from the server, in addition to
+      <code>Database</code> table updates, the server sends <code>cancel</code>
+      messages, as described in RFC 7047 section 4.1.4, in reply to outstanding
+      transactions for the removed database.  The server also cancels any
+      outstanding monitoring initiated by <code>monitor</code> or
+      <code>monitor_cond</code> requested on the removed database, sending the
+      <code>monitor_canceled</code> RPC described in
+      <code>ovsdb-server</code>(5).  Only clients that disable disconnection
+      with <code>set_db_change_aware</code> receive these messages.
+    </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
diff --git a/ovsdb/jsonrpc-server.c b/ovsdb/jsonrpc-server.c
index 6e5f75498fc1..a667dbe67f5f 100644
--- a/ovsdb/jsonrpc-server.c
+++ b/ovsdb/jsonrpc-server.c
@@ -57,12 +57,15 @@  static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
 /* Sessions. */
 static struct ovsdb_jsonrpc_session *ovsdb_jsonrpc_session_create(
     struct ovsdb_jsonrpc_remote *, struct jsonrpc_session *, bool);
+static void ovsdb_jsonrpc_session_preremove_db(struct ovsdb_jsonrpc_remote *,
+                                               struct ovsdb *);
 static void ovsdb_jsonrpc_session_run_all(struct ovsdb_jsonrpc_remote *);
 static void ovsdb_jsonrpc_session_wait_all(struct ovsdb_jsonrpc_remote *);
 static void ovsdb_jsonrpc_session_get_memory_usage_all(
     const struct ovsdb_jsonrpc_remote *, struct simap *usage);
 static void ovsdb_jsonrpc_session_close_all(struct ovsdb_jsonrpc_remote *);
-static void ovsdb_jsonrpc_session_reconnect_all(struct ovsdb_jsonrpc_remote *);
+static void ovsdb_jsonrpc_session_reconnect_all(struct ovsdb_jsonrpc_remote *,
+                                                bool force);
 static void ovsdb_jsonrpc_session_set_all_options(
     struct ovsdb_jsonrpc_remote *, const struct ovsdb_jsonrpc_options *);
 static bool ovsdb_jsonrpc_active_session_get_status(
@@ -83,6 +86,8 @@  static void ovsdb_jsonrpc_trigger_create(struct ovsdb_jsonrpc_session *,
 static struct ovsdb_jsonrpc_trigger *ovsdb_jsonrpc_trigger_find(
     struct ovsdb_jsonrpc_session *, const struct json *id, size_t hash);
 static void ovsdb_jsonrpc_trigger_complete(struct ovsdb_jsonrpc_trigger *);
+static void ovsdb_jsonrpc_trigger_preremove_db(struct ovsdb_jsonrpc_session *,
+                                               struct ovsdb *);
 static void ovsdb_jsonrpc_trigger_complete_all(struct ovsdb_jsonrpc_session *);
 static void ovsdb_jsonrpc_trigger_complete_done(
     struct ovsdb_jsonrpc_session *);
@@ -99,6 +104,8 @@  static struct jsonrpc_msg *ovsdb_jsonrpc_monitor_cancel(
     struct ovsdb_jsonrpc_session *,
     struct json_array *params,
     const struct json *request_id);
+static void ovsdb_jsonrpc_monitor_preremove_db(struct ovsdb_jsonrpc_session *,
+                                               struct ovsdb *);
 static void ovsdb_jsonrpc_monitor_remove_all(struct ovsdb_jsonrpc_session *);
 static void ovsdb_jsonrpc_monitor_flush_all(struct ovsdb_jsonrpc_session *);
 static bool ovsdb_jsonrpc_monitor_needs_flush(struct ovsdb_jsonrpc_session *);
@@ -157,34 +164,25 @@  ovsdb_jsonrpc_server_create(bool read_only)
 bool
 ovsdb_jsonrpc_server_add_db(struct ovsdb_jsonrpc_server *svr, struct ovsdb *db)
 {
-    /* The OVSDB protocol doesn't have a way to notify a client that a
-     * database has been added.  If some client tried to use the database
-     * that we're adding and failed, then forcing it to reconnect seems like
-     * a reasonable way to make it try again.
-     *
-     * If this is too big of a hammer in practice, we could be more selective,
-     * e.g. disconnect only connections that actually tried to use a database
-     * with 'db''s name. */
-    ovsdb_jsonrpc_server_reconnect(svr);
-
+    ovsdb_jsonrpc_server_reconnect(svr, false);
     return ovsdb_server_add_db(&svr->up, db);
 }
 
-/* Removes 'db' from the set of databases served out by 'svr'.  Returns
- * true if successful, false if there is no database associated with 'db'. */
-bool
+/* Removes 'db' from the set of databases served out by 'svr'. */
+void
 ovsdb_jsonrpc_server_remove_db(struct ovsdb_jsonrpc_server *svr,
                                struct ovsdb *db)
 {
-    /* There might be pointers to 'db' from 'svr', such as monitors or
-     * outstanding transactions.  Disconnect all JSON-RPC connections to avoid
-     * accesses to freed memory.
-     *
-     * If this is too big of a hammer in practice, we could be more selective,
-     * e.g. disconnect only connections that actually reference 'db'. */
-    ovsdb_jsonrpc_server_reconnect(svr);
+    struct shash_node *node;
+    SHASH_FOR_EACH (node, &svr->remotes) {
+        struct ovsdb_jsonrpc_remote *remote = node->data;
+
+        ovsdb_jsonrpc_session_preremove_db(remote, db);
+    }
+
+    ovsdb_jsonrpc_server_reconnect(svr, false);
 
-    return ovsdb_server_remove_db(&svr->up, db);
+    ovsdb_server_remove_db(&svr->up, db);
 }
 
 void
@@ -333,17 +331,20 @@  ovsdb_jsonrpc_server_free_remote_status(
     free(status->locks_lost);
 }
 
-/* Forces all of the JSON-RPC sessions managed by 'svr' to disconnect and
- * reconnect. */
+/* Makes all of the JSON-RPC sessions managed by 'svr' to disconnect.  (They
+ * will then generally reconnect.).
+ *
+ * If 'force' is true, disconnects all sessions.  Otherwise, disconnects only
+ * sesions that aren't database change aware. */
 void
-ovsdb_jsonrpc_server_reconnect(struct ovsdb_jsonrpc_server *svr)
+ovsdb_jsonrpc_server_reconnect(struct ovsdb_jsonrpc_server *svr, bool force)
 {
     struct shash_node *node;
 
     SHASH_FOR_EACH (node, &svr->remotes) {
         struct ovsdb_jsonrpc_remote *remote = node->data;
 
-        ovsdb_jsonrpc_session_reconnect_all(remote);
+        ovsdb_jsonrpc_session_reconnect_all(remote, force);
     }
 }
 
@@ -359,7 +360,7 @@  ovsdb_jsonrpc_server_set_read_only(struct ovsdb_jsonrpc_server *svr,
 {
     if (svr->read_only != read_only) {
         svr->read_only = read_only;
-        ovsdb_jsonrpc_server_reconnect(svr);
+        ovsdb_jsonrpc_server_reconnect(svr, false);
     }
 }
 
@@ -432,6 +433,20 @@  struct ovsdb_jsonrpc_session {
     struct ovsdb_session up;
     struct ovsdb_jsonrpc_remote *remote;
 
+    /* RFC 7047 does not contemplate how to alert clients to changes to the set
+     * of databases, e.g. databases that are added or removed while the
+     * database server is running.  Traditionally, ovsdb-server disconnects all
+     * of its clients when this happens; a well-written client will reassess
+     * what is available from the server upon reconnection.
+     *
+     * OVS 2.9 introduces a way for clients to monitor changes to the databases
+     * being served, through the Database table in the _Server database that
+     * OVSDB adds in this version.  ovsdb-server suppresses the connection
+     * close for clients that identify themselves as taking advantage of this
+     * mechanism.
+     */
+    bool db_change_aware;
+
     /* Triggers. */
     struct hmap triggers;       /* Hmap of "struct ovsdb_jsonrpc_trigger"s. */
 
@@ -478,6 +493,20 @@  ovsdb_jsonrpc_session_create(struct ovsdb_jsonrpc_remote *remote,
     return s;
 }
 
+/* Database 'db' is about to be removed from the database server.  To prepare,
+ * this function removes all references to 'db' from session 's'. */
+static void
+ovsdb_jsonrpc_session_preremove_db(struct ovsdb_jsonrpc_remote *remote,
+                                   struct ovsdb *db)
+{
+    struct ovsdb_jsonrpc_session *s;
+
+    LIST_FOR_EACH (s, node, &remote->sessions) {
+        ovsdb_jsonrpc_monitor_preremove_db(s, db);
+        ovsdb_jsonrpc_trigger_preremove_db(s, db);
+    }
+}
+
 static void
 ovsdb_jsonrpc_session_close(struct ovsdb_jsonrpc_session *s)
 {
@@ -606,17 +635,23 @@  ovsdb_jsonrpc_session_close_all(struct ovsdb_jsonrpc_remote *remote)
     }
 }
 
-/* Forces all of the JSON-RPC sessions managed by 'remote' to disconnect and
- * reconnect. */
+/* Makes all of the JSON-RPC sessions managed by 'remove' to disconnect.  (They
+ * will then generally reconnect.).
+ *
+ * If 'force' is true, disconnects all sessions.  Otherwise, disconnects only
+ * sesions that aren't database change aware. */
 static void
-ovsdb_jsonrpc_session_reconnect_all(struct ovsdb_jsonrpc_remote *remote)
+ovsdb_jsonrpc_session_reconnect_all(struct ovsdb_jsonrpc_remote *remote,
+                                    bool force)
 {
     struct ovsdb_jsonrpc_session *s, *next;
 
     LIST_FOR_EACH_SAFE (s, next, node, &remote->sessions) {
-        jsonrpc_session_force_reconnect(s->js);
-        if (!jsonrpc_session_is_alive(s->js)) {
-            ovsdb_jsonrpc_session_close(s);
+        if (force || !s->db_change_aware) {
+            jsonrpc_session_force_reconnect(s->js);
+            if (!jsonrpc_session_is_alive(s->js)) {
+                ovsdb_jsonrpc_session_close(s);
+            }
         }
     }
 }
@@ -859,6 +894,17 @@  ovsdb_jsonrpc_session_unlock__(struct ovsdb_lock_waiter *waiter)
 }
 
 static struct jsonrpc_msg *
+syntax_error_reply(const struct jsonrpc_msg *request, const char *details)
+{
+    struct ovsdb_error *error = ovsdb_syntax_error(
+        request->params, NULL, "%s: %s", request->method, details);
+    struct jsonrpc_msg *msg = jsonrpc_create_error(ovsdb_error_to_json(error),
+                                                   request->id);
+    ovsdb_error_destroy(error);
+    return msg;
+}
+
+static struct jsonrpc_msg *
 ovsdb_jsonrpc_session_unlock(struct ovsdb_jsonrpc_session *s,
                              struct jsonrpc_msg *request)
 {
@@ -872,24 +918,21 @@  ovsdb_jsonrpc_session_unlock(struct ovsdb_jsonrpc_session *s,
 
     error = ovsdb_jsonrpc_session_parse_lock_name(request, &lock_name);
     if (error) {
-        goto error;
+        return jsonrpc_create_error(ovsdb_error_to_json_free(error),
+                                    request->id);
     }
 
     /* Report error if this session has not issued a "lock" or "steal" for this
      * lock. */
     waiter = ovsdb_session_get_lock_waiter(&s->up, lock_name);
     if (!waiter) {
-        error = ovsdb_syntax_error(
-            request->params, NULL, "\"unlock\" without \"lock\" or \"steal\"");
-        goto error;
+        return syntax_error_reply(request,
+                                  "\"unlock\" without \"lock\" or \"steal\"");
     }
 
     ovsdb_jsonrpc_session_unlock__(waiter);
 
     return jsonrpc_create_reply(json_object_create(), request->id);
-
-error:
-    return jsonrpc_create_error(ovsdb_error_to_json_free(error), request->id);
 }
 
 static struct jsonrpc_msg *
@@ -903,6 +946,21 @@  execute_transaction(struct ovsdb_jsonrpc_session *s, struct ovsdb *db,
     return NULL;
 }
 
+static struct jsonrpc_msg *
+ovsdb_jsonrpc_session_set_db_change_aware(struct ovsdb_jsonrpc_session *s,
+                                          const struct jsonrpc_msg *request)
+{
+    const struct json_array *params = json_array(request->params);
+    if (params->n != 1
+        || (params->elems[0]->type != JSON_TRUE &&
+            params->elems[0]->type != JSON_FALSE)) {
+        return syntax_error_reply(request, "true or false parameter expected");
+    }
+
+    s->db_change_aware = json_boolean(params->elems[0]);
+    return jsonrpc_create_reply(json_object_create(), request->id);
+}
+
 static void
 ovsdb_jsonrpc_session_got_request(struct ovsdb_jsonrpc_session *s,
                                   struct jsonrpc_msg *request)
@@ -963,6 +1021,8 @@  ovsdb_jsonrpc_session_got_request(struct ovsdb_jsonrpc_session *s,
         reply = ovsdb_jsonrpc_session_lock(s, request, OVSDB_LOCK_STEAL);
     } else if (!strcmp(request->method, "unlock")) {
         reply = ovsdb_jsonrpc_session_unlock(s, request);
+    } else if (!strcmp(request->method, "set_db_change_aware")) {
+        reply = ovsdb_jsonrpc_session_set_db_change_aware(s, request);
     } else if (!strcmp(request->method, "echo")) {
         reply = jsonrpc_create_reply(json_clone(request->params), request->id);
     } else {
@@ -1098,14 +1158,34 @@  ovsdb_jsonrpc_trigger_complete(struct ovsdb_jsonrpc_trigger *t)
 }
 
 static void
-ovsdb_jsonrpc_trigger_complete_all(struct ovsdb_jsonrpc_session *s)
+ovsdb_jsonrpc_trigger_remove__(struct ovsdb_jsonrpc_session *s,
+                                   struct ovsdb *db)
 {
     struct ovsdb_jsonrpc_trigger *t, *next;
     HMAP_FOR_EACH_SAFE (t, next, hmap_node, &s->triggers) {
-        ovsdb_jsonrpc_trigger_complete(t);
+        if (!db || t->trigger.db == db) {
+            ovsdb_jsonrpc_trigger_complete(t);
+        }
     }
 }
 
+/* Database 'db' is about to be removed from the database server.  To prepare,
+ * this function removes all references from triggers in 's' to 'db'. */
+static void
+ovsdb_jsonrpc_trigger_preremove_db(struct ovsdb_jsonrpc_session *s,
+                                   struct ovsdb *db)
+{
+    ovs_assert(db);
+    ovsdb_jsonrpc_trigger_remove__(s, db);
+}
+
+/* Removes all triggers from 's'. */
+static void
+ovsdb_jsonrpc_trigger_complete_all(struct ovsdb_jsonrpc_session *s)
+{
+    ovsdb_jsonrpc_trigger_remove__(s, NULL);
+}
+
 static void
 ovsdb_jsonrpc_trigger_complete_done(struct ovsdb_jsonrpc_session *s)
 {
@@ -1526,15 +1606,42 @@  ovsdb_jsonrpc_monitor_cancel(struct ovsdb_jsonrpc_session *s,
 }
 
 static void
-ovsdb_jsonrpc_monitor_remove_all(struct ovsdb_jsonrpc_session *s)
+ovsdb_jsonrpc_monitor_remove__(struct ovsdb_jsonrpc_session *s,
+                               struct ovsdb *db)
 {
     struct ovsdb_jsonrpc_monitor *m, *next;
 
     HMAP_FOR_EACH_SAFE (m, next, node, &s->monitors) {
-        ovsdb_jsonrpc_monitor_destroy(m);
+        if (!db || m->db == db) {
+            if (db && jsonrpc_session_is_connected(s->js)
+                && s->db_change_aware) {
+                struct jsonrpc_msg *notify = jsonrpc_create_notify(
+                    "monitor_canceled",
+                    json_array_create_1(json_clone(m->monitor_id)));
+                ovsdb_jsonrpc_session_send(s, notify);
+            }
+            ovsdb_jsonrpc_monitor_destroy(m);
+        }
     }
 }
 
+/* Database 'db' is about to be removed from the database server.  To prepare,
+ * this function removes all references from monitors in 's' to 'db'. */
+static void
+ovsdb_jsonrpc_monitor_preremove_db(struct ovsdb_jsonrpc_session *s,
+                                   struct ovsdb *db)
+{
+    ovs_assert(db);
+    ovsdb_jsonrpc_monitor_remove__(s, db);
+}
+
+/* Cancels all monitors in 's'. */
+static void
+ovsdb_jsonrpc_monitor_remove_all(struct ovsdb_jsonrpc_session *s)
+{
+    ovsdb_jsonrpc_monitor_remove__(s, NULL);
+}
+
 static struct json *
 ovsdb_jsonrpc_monitor_compose_update(struct ovsdb_jsonrpc_monitor *m,
                                      bool initial)
diff --git a/ovsdb/jsonrpc-server.h b/ovsdb/jsonrpc-server.h
index a3acc75f8d4f..50a8b879c5a9 100644
--- a/ovsdb/jsonrpc-server.h
+++ b/ovsdb/jsonrpc-server.h
@@ -27,7 +27,7 @@  struct uuid;
 struct ovsdb_jsonrpc_server *ovsdb_jsonrpc_server_create(bool read_only);
 bool ovsdb_jsonrpc_server_add_db(struct ovsdb_jsonrpc_server *,
                                  struct ovsdb *);
-bool ovsdb_jsonrpc_server_remove_db(struct ovsdb_jsonrpc_server *,
+void ovsdb_jsonrpc_server_remove_db(struct ovsdb_jsonrpc_server *,
                                      struct ovsdb *);
 void ovsdb_jsonrpc_server_destroy(struct ovsdb_jsonrpc_server *);
 
@@ -64,7 +64,7 @@  bool ovsdb_jsonrpc_server_get_remote_status(
 void ovsdb_jsonrpc_server_free_remote_status(
     struct ovsdb_jsonrpc_remote_status *);
 
-void ovsdb_jsonrpc_server_reconnect(struct ovsdb_jsonrpc_server *);
+void ovsdb_jsonrpc_server_reconnect(struct ovsdb_jsonrpc_server *, bool force);
 
 void ovsdb_jsonrpc_server_run(struct ovsdb_jsonrpc_server *);
 void ovsdb_jsonrpc_server_wait(struct ovsdb_jsonrpc_server *);
diff --git a/ovsdb/ovsdb-client.c b/ovsdb/ovsdb-client.c
index 194ff47593f3..cecc0346eda1 100644
--- a/ovsdb/ovsdb-client.c
+++ b/ovsdb/ovsdb-client.c
@@ -71,6 +71,12 @@  struct ovsdb_client_command {
 /* --timestamp: Print a timestamp before each update on "monitor" command? */
 static bool timestamp;
 
+/* --db-change-aware: Enable db_change_aware feature for "monitor" command?
+ *
+ * (This option is undocumented because it is expected to be useful only for
+ * testing that the db_change_aware feature actually works.) */
+static bool db_change_aware;
+
 /* Format for table output. */
 static struct table_style table_style = TABLE_STYLE_DEFAULT;
 
@@ -182,6 +188,7 @@  parse_options(int argc, char *argv[])
     enum {
         OPT_BOOTSTRAP_CA_CERT = UCHAR_MAX + 1,
         OPT_TIMESTAMP,
+        OPT_DB_CHANGE_AWARE,
         VLOG_OPTION_ENUMS,
         DAEMON_OPTION_ENUMS,
         TABLE_OPTION_ENUMS,
@@ -191,6 +198,7 @@  parse_options(int argc, char *argv[])
         {"help", no_argument, NULL, 'h'},
         {"version", no_argument, NULL, 'V'},
         {"timestamp", no_argument, NULL, OPT_TIMESTAMP},
+        {"db-change-aware", no_argument, NULL, OPT_DB_CHANGE_AWARE},
         VLOG_LONG_OPTIONS,
         DAEMON_LONG_OPTIONS,
 #ifdef HAVE_OPENSSL
@@ -233,6 +241,10 @@  parse_options(int argc, char *argv[])
             timestamp = true;
             break;
 
+        case OPT_DB_CHANGE_AWARE:
+            db_change_aware = true;
+            break;
+
         case '?':
             exit(EXIT_FAILURE);
 
@@ -949,7 +961,6 @@  do_monitor__(struct jsonrpc *rpc, const char *database,
     const char *table_name = argv[0];
     struct unixctl_server *unixctl;
     struct ovsdb_schema *schema;
-    struct jsonrpc_msg *request;
     struct json *monitor, *monitor_requests, *request_id;
     bool exiting = false;
     bool blocked = false;
@@ -1017,11 +1028,29 @@  do_monitor__(struct jsonrpc *rpc, const char *database,
         free(nodes);
     }
 
+    if (db_change_aware) {
+        struct jsonrpc_msg *request = jsonrpc_create_request(
+            "set_db_change_aware",
+            json_array_create_1(json_boolean_create(true)),
+            NULL);
+        struct jsonrpc_msg *reply;
+        int error = jsonrpc_transact_block(rpc, request, &reply);
+        if (error) {
+            ovs_fatal(error, "%s: error setting db_change_aware", server);
+        }
+        if (reply->type == JSONRPC_ERROR) {
+            ovs_fatal(0, "%s: set_db_change_aware failed (%s)",
+                      server, json_to_string(reply->error, 0));
+        }
+        jsonrpc_msg_destroy(reply);
+    }
+
     monitor = json_array_create_3(json_string_create(database),
                                   json_null_create(), monitor_requests);
     const char *method = version == OVSDB_MONITOR_V2 ? "monitor_cond"
                                                      : "monitor";
 
+    struct jsonrpc_msg *request;
     request = jsonrpc_create_request(method, monitor, NULL);
     request_id = json_clone(request->id);
     jsonrpc_send(rpc, request);
diff --git a/ovsdb/ovsdb-server.1.in b/ovsdb/ovsdb-server.1.in
index 02144125e67c..9bfd36edd351 100644
--- a/ovsdb/ovsdb-server.1.in
+++ b/ovsdb/ovsdb-server.1.in
@@ -406,6 +406,24 @@  notifications (see below) to the request, it must be unique among all
 active monitors.  \fBovsdb\-server\fR rejects attempt to create two
 monitors with the same identifier.
 .
+.IP "4.1.7. Monitor Cancellation"
+.IP
+When a database monitored by a session is removed, and database change
+awareness is enabled for the session (see Section 4.1.16), the
+database server spontaneously cancels all monitors (including
+conditional monitors described in Section 4.1.12) for the removed
+database.  For each canceled monitor, it issues a notification in the
+following form:
+.
+.PP
+.RS
+.nf
+"method": "monitor_canceled"
+"params": [<json-value>]
+"id": null
+.fi
+.RE
+.
 .IP "4.1.12. Monitor_cond"
 A new monitor method added in Open vSwitch version 2.6. The monitor_cond
 request enables a client to replicate subsets of tables within an OVSDB
@@ -704,6 +722,45 @@  The response object contains the following members:
 the running OVSDB server process. A fresh UUID is generated when the
 process restarts.
 .
+.IP "4.1.16. Database Change Awareness"
+.IP
+RFC 7047 does not provide a way for a client to find out about some
+kinds of configuration changes, such as about databases added or
+removed while a client is connected to the server, or databases
+changing between read/write and read-only due to a transition between
+active and backup roles.  Traditionally, \fBovsdb\-server\fR
+disconnects all of its clients when this happens, because this prompts
+a well-written client to reassess what is available from the server
+when it reconnects.
+.IP
+OVS 2.9 provides a way for clients to keep track of these kinds of
+changes, by monitoring the \fBDatabase\fR table in the \fB_Server\fR
+database introduced in this release.  See \fBovsdb-server\fR(5) for
+details.  By itself, this does not suppress \fBovsdb\-server\fR
+disconnection behavior, because a client might monitor this database
+without understanding its special semantics.  Instead,
+\fBovsdb\-server\fR provides a special request:
+.PP
+.RS
+.nf
+"method": "set_db_change_aware"
+"params": [<boolean>]
+"id": <nonnull-json-value>
+.fi
+.RE
+.IP
+If the boolean in the request is true, it suppresses the
+connection-closing behavior for the current connection, and false
+restores the default behavior.  The reply is always the same:
+.PP
+.RS
+.nf
+"result": {}
+"error": null
+"id": same "id" as request
+.fi
+.RE
+.
 .IP "5.1. Notation"
 For <condition>, RFC 7047 only allows the use of \fB!=\fR, \fB==\fR,
 \fBincludes\fR, and \fBexcludes\fR operators with set types.  Open
diff --git a/ovsdb/ovsdb-server.c b/ovsdb/ovsdb-server.c
index 52eb21f89aa0..e28fca5989fc 100644
--- a/ovsdb/ovsdb-server.c
+++ b/ovsdb/ovsdb-server.c
@@ -1227,7 +1227,7 @@  ovsdb_server_disable_monitor_cond(struct unixctl_conn *conn,
     struct ovsdb_jsonrpc_server *jsonrpc = jsonrpc_;
 
     ovsdb_jsonrpc_disable_monitor_cond();
-    ovsdb_jsonrpc_server_reconnect(jsonrpc);
+    ovsdb_jsonrpc_server_reconnect(jsonrpc, true);
     unixctl_command_reply(conn, NULL);
 }
 
@@ -1284,7 +1284,7 @@  ovsdb_server_reconnect(struct unixctl_conn *conn, int argc OVS_UNUSED,
                        const char *argv[] OVS_UNUSED, void *jsonrpc_)
 {
     struct ovsdb_jsonrpc_server *jsonrpc = jsonrpc_;
-    ovsdb_jsonrpc_server_reconnect(jsonrpc);
+    ovsdb_jsonrpc_server_reconnect(jsonrpc, true);
     unixctl_command_reply(conn, NULL);
 }
 
@@ -1386,12 +1386,9 @@  ovsdb_server_add_database(struct unixctl_conn *conn, int argc OVS_UNUSED,
 static void
 remove_db(struct server_config *config, struct shash_node *node)
 {
-    struct db *db;
-    bool ok;
+    struct db *db = node->data;
 
-    db = node->data;
-    ok = ovsdb_jsonrpc_server_remove_db(config->jsonrpc, db->db);
-    ovs_assert(ok);
+    ovsdb_jsonrpc_server_remove_db(config->jsonrpc, db->db);
 
     close_db(db);
     shash_delete(config->all_dbs, node);
diff --git a/tests/ovsdb-server.at b/tests/ovsdb-server.at
index ccbc1ac0b717..1ad49e64139f 100644
--- a/tests/ovsdb-server.at
+++ b/tests/ovsdb-server.at
@@ -179,7 +179,7 @@  AT_CLEANUP
 
 AT_SETUP([ovsdb-server/add-db and remove-db])
 AT_KEYWORDS([ovsdb server positive])
-on_exit 'kill `cat ovsdb-server.pid`'
+on_exit 'kill `cat *.pid`'
 ordinal_schema > schema1
 constraint_schema > schema2
 AT_CHECK([ovsdb-tool create db1 schema1], [0], [ignore], [ignore])
@@ -190,6 +190,19 @@  AT_CHECK([ovsdb-server --detach --no-chdir --pidfile --remote=punix:db.sock db1]
 CHECK_DBS([ordinals
 ])
 
+# Remove the database.
+AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/remove-db ordinals], [0])
+CHECK_DBS([])
+
+# Start monitoring processes.
+AT_CHECK([ovsdb-client --detach --pidfile=ovsdb-client-1.pid --no-headings monitor _Server Database name > db-changes-unaware])
+AT_CHECK([ovsdb-client --detach --pidfile=ovsdb-client-2.pid --db-change-aware --no-headings monitor _Server Database name > db-changes-aware])
+
+# Add the first database back.
+AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/add-db db1], [0])
+CHECK_DBS([ordinals
+])
+
 # Add the second database.
 AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/add-db db2], [0])
 CHECK_DBS([constraints
@@ -253,6 +266,25 @@  AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/add-db db2], [0])
 CHECK_DBS([constraints
 ])
 AT_CHECK([ovsdb-client list-tables unix:db.sock constraints], [0], [ignore], [ignore])
+
+# Check the monitoring results.
+AT_CHECK([${PERL} $srcdir/uuidfilt.pl db-changes-aware], [0], [dnl
+<0> initial _Server
+
+<1> insert ordinals
+
+<2> insert constraints
+
+<1> delete ordinals
+
+<2> delete constraints
+
+<3> insert constraints
+])
+AT_CHECK([${PERL} $srcdir/uuidfilt.pl db-changes-unaware], [0], [dnl
+<0> initial _Server
+])
+
 OVS_APP_EXIT_AND_WAIT([ovsdb-server])
 AT_CLEANUP