[ovs-dev,10/15] ovsdb-server: Add new RPC "set_db_change_aware".

Message ID 20180101051640.13043-10-blp@ovn.org
State New
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 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>
---
 Documentation/ref/ovsdb-server.7.rst |  43 ++++++++
 ovsdb/_server.xml                    |  34 ++++++
 ovsdb/jsonrpc-server.c               | 195 +++++++++++++++++++++++++++--------
 ovsdb/jsonrpc-server.h               |   4 +-
 ovsdb/ovsdb-client.c                 |  28 ++++-
 ovsdb/ovsdb-server.c                 |  11 +-
 tests/ovsdb-server.at                |  40 ++++++-
 7 files changed, 299 insertions(+), 56 deletions(-)

Comments

Justin Pettit Feb. 1, 2018, 2:24 a.m. | #1
> On Dec 31, 2017, at 9:16 PM, Ben Pfaff <blp@ovn.org> wrote:
> 
> +      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.

Is that in section 1 or 7 of the ovsdb-server man pages?

> +      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.

I believe this file is section 5 of the ovsdb-server man page.  Should it also be section 7?

> @@ -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.).

I think you should drop "to".

> @@ -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;

I think it might be worth being explicit that setting this flag to true indicates that the client has requested this suppression to occur.

> +/* 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)

The argument looks to be 'db', not 's', which may contain more than one session.

> +/* 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,
> +                                    bool force)

The comment should be 'remote' instead of 'remove'.

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

Thanks,

--Justin
Ben Pfaff Feb. 1, 2018, 7:14 p.m. | #2
Thanks for the review and for your valuable corrections!

On Wed, Jan 31, 2018 at 06:24:14PM -0800, Justin Pettit wrote:
> > On Dec 31, 2017, at 9:16 PM, Ben Pfaff <blp@ovn.org> wrote:
> > 
> > +      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.
> 
> Is that in section 1 or 7 of the ovsdb-server man pages?

7.  Fixed.

> > +      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.
> 
> I believe this file is section 5 of the ovsdb-server man page.  Should
> it also be section 7?

Yes.  Fixed.

> > @@ -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.).
> 
> I think you should drop "to".

Done, thanks.

> > @@ -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;
> 
> I think it might be worth being explicit that setting this flag to
> true indicates that the client has requested this suppression to
> occur.

Thanks, I added a sentence.

> > +/* 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)
> 
> The argument looks to be 'db', not 's', which may contain more than
> one session.

Thanks, fixed.

> > +/* 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,
> > +                                    bool force)
> 
> The comment should be 'remote' instead of 'remove'.

Thanks, fixed.

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

Thanks for the review!

Patch

diff --git a/Documentation/ref/ovsdb-server.7.rst b/Documentation/ref/ovsdb-server.7.rst
index cc625f6016fb..2ed392feed1f 100644
--- a/Documentation/ref/ovsdb-server.7.rst
+++ b/Documentation/ref/ovsdb-server.7.rst
@@ -146,6 +146,19 @@  notifications (see below) to the request, it must be unique among all active
 monitors.  ``ovsdb-server`` rejects attempt to create two monitors with the
 same identifier.
 
+4.1.7 Monitor Cancellation
+--------------------------
+
+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::
+
+    "method": "monitor_canceled"
+    "params": [<json-value>]
+    "id": null
+
 4.1.12 Monitor_cond
 -------------------
 
@@ -371,6 +384,36 @@  The response object contains the following members::
 running OVSDB server process.  A fresh UUID is generated when the process
 restarts.
 
+4.1.16 Database Change Awareness
+--------------------------------
+
+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,
+``ovsdb-server`` 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.
+
+OVS 2.9 provides a way for clients to keep track of these kinds of changes, by
+monitoring the ``Database`` table in the ``_Server`` database introduced in
+this release (see ``ovsdb-server(5)`` for details).  By itself, this does not
+suppress ``ovsdb-server`` disconnection behavior, because a client might
+monitor this database without understanding its special semantics.  Instead,
+``ovsdb-server`` provides a special request::
+
+    "method": "set_db_change_aware"
+    "params": [<boolean>]
+    "id": <nonnull-json-value>
+
+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::
+
+    "result": {}
+    "error": null
+    "id": same "id" as request
+
 5.1 Notation
 ------------
 
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 27586cddd8b3..d51a56854517 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)
 {
@@ -1525,15 +1605,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 0ab4d66f1b29..600c5070db78 100644
--- a/ovsdb/ovsdb-client.c
+++ b/ovsdb/ovsdb-client.c
@@ -74,6 +74,13 @@  struct ovsdb_client_command {
 /* --timestamp: Print a timestamp before each update on "monitor" command? */
 static bool timestamp;
 
+/* --db-change-aware, --no-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 int db_change_aware;
+
 /* --force: Ignore schema differences for "restore" command? */
 static bool force;
 
@@ -199,6 +206,8 @@  parse_options(int argc, char *argv[])
         {"version", no_argument, NULL, 'V'},
         {"timestamp", no_argument, NULL, OPT_TIMESTAMP},
         {"force", no_argument, NULL, OPT_FORCE},
+        {"db-change-aware", no_argument, &db_change_aware, 1},
+        {"no-db-change-aware", no_argument, &db_change_aware, 0},
         VLOG_LONG_OPTIONS,
         DAEMON_LONG_OPTIONS,
 #ifdef HAVE_OPENSSL
@@ -1021,7 +1030,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;
@@ -1089,11 +1097,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.c b/ovsdb/ovsdb-server.c
index dd0cdfe6a38b..1e36b27958f8 100644
--- a/ovsdb/ovsdb-server.c
+++ b/ovsdb/ovsdb-server.c
@@ -1240,7 +1240,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);
 }
 
@@ -1298,7 +1298,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);
 }
 
@@ -1400,12 +1400,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 07ceda92496d..2e3d8ad14636 100644
--- a/tests/ovsdb-server.at
+++ b/tests/ovsdb-server.at
@@ -169,14 +169,31 @@  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])
 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:db.sock db1], [0])
+AT_CHECK([ovsdb-server -vfile -vvlog:off --log-file --detach --no-chdir --pidfile --remote=punix:db.sock db1], [0])
+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-db-change-aware --no-headings monitor _Server Database name > db-change-unaware.stdout 2> db-change-unaware.stderr])
+AT_CHECK([ovsdb-client --detach --pidfile=ovsdb-client-2.pid --db-change-aware --no-headings monitor _Server Database name > db-change-aware.stdout 2> db-change-aware.stderr])
+AT_CAPTURE_FILE([db-change-unaware.stdout])
+AT_CAPTURE_FILE([db-change-unaware.stderr])
+AT_CAPTURE_FILE([db-change-aware.stdout])
+AT_CAPTURE_FILE([db-change-aware.stderr])
+
+# Add the first database back.
+AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/add-db db1], [0])
 CHECK_DBS([ordinals
 ])
 
@@ -243,6 +260,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([uuidfilt db-change-aware.stdout], [0], [dnl
+<0> initial _Server
+
+<1> insert ordinals
+
+<2> insert constraints
+
+<1> delete ordinals
+
+<2> delete constraints
+
+<3> insert constraints
+])
+AT_CHECK([uuidfilt db-change-unaware.stdout], [0], [dnl
+<0> initial _Server
+])
+
 OVS_APP_EXIT_AND_WAIT([ovsdb-server])
 AT_CLEANUP