get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

GET /api/patches/815900/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 815900,
    "url": "http://patchwork.ozlabs.org/api/patches/815900/?format=api",
    "web_url": "http://patchwork.ozlabs.org/project/openvswitch/patch/20170919220125.32535-47-blp@ovn.org/",
    "project": {
        "id": 47,
        "url": "http://patchwork.ozlabs.org/api/projects/47/?format=api",
        "name": "Open vSwitch",
        "link_name": "openvswitch",
        "list_id": "ovs-dev.openvswitch.org",
        "list_email": "ovs-dev@openvswitch.org",
        "web_url": "http://openvswitch.org/",
        "scm_url": "git@github.com:openvswitch/ovs.git",
        "webscm_url": "https://github.com/openvswitch/ovs",
        "list_archive_url": "",
        "list_archive_url_format": "",
        "commit_url_format": ""
    },
    "msgid": "<20170919220125.32535-47-blp@ovn.org>",
    "list_archive_url": null,
    "date": "2017-09-19T22:01:19",
    "name": "[ovs-dev,RFC,46/52] ovsdb: Add support for online schema conversion.",
    "commit_ref": null,
    "pull_url": null,
    "state": "rfc",
    "archived": false,
    "hash": "6f878010faedf0a821e6cadf351b99f1dcb871fa",
    "submitter": {
        "id": 67603,
        "url": "http://patchwork.ozlabs.org/api/people/67603/?format=api",
        "name": "Ben Pfaff",
        "email": "blp@ovn.org"
    },
    "delegate": null,
    "mbox": "http://patchwork.ozlabs.org/project/openvswitch/patch/20170919220125.32535-47-blp@ovn.org/mbox/",
    "series": [
        {
            "id": 3975,
            "url": "http://patchwork.ozlabs.org/api/series/3975/?format=api",
            "web_url": "http://patchwork.ozlabs.org/project/openvswitch/list/?series=3975",
            "date": "2017-09-19T22:00:34",
            "name": "clustering implementation",
            "version": 1,
            "mbox": "http://patchwork.ozlabs.org/series/3975/mbox/"
        }
    ],
    "comments": "http://patchwork.ozlabs.org/api/patches/815900/comments/",
    "check": "pending",
    "checks": "http://patchwork.ozlabs.org/api/patches/815900/checks/",
    "tags": {},
    "related": [],
    "headers": {
        "Return-Path": "<ovs-dev-bounces@openvswitch.org>",
        "X-Original-To": [
            "incoming@patchwork.ozlabs.org",
            "dev@openvswitch.org"
        ],
        "Delivered-To": [
            "patchwork-incoming@bilbo.ozlabs.org",
            "ovs-dev@mail.linuxfoundation.org"
        ],
        "Authentication-Results": "ozlabs.org;\n\tspf=pass (mailfrom) smtp.mailfrom=openvswitch.org\n\t(client-ip=140.211.169.12; helo=mail.linuxfoundation.org;\n\tenvelope-from=ovs-dev-bounces@openvswitch.org;\n\treceiver=<UNKNOWN>)",
        "Received": [
            "from mail.linuxfoundation.org (mail.linuxfoundation.org\n\t[140.211.169.12])\n\t(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256\n\tbits)) (No client certificate requested)\n\tby ozlabs.org (Postfix) with ESMTPS id 3xxcrc4nvnz9sBW\n\tfor <incoming@patchwork.ozlabs.org>;\n\tWed, 20 Sep 2017 08:25:20 +1000 (AEST)",
            "from mail.linux-foundation.org (localhost [127.0.0.1])\n\tby mail.linuxfoundation.org (Postfix) with ESMTP id 2B95BD48;\n\tTue, 19 Sep 2017 22:02:57 +0000 (UTC)",
            "from smtp1.linuxfoundation.org (smtp1.linux-foundation.org\n\t[172.17.192.35])\n\tby mail.linuxfoundation.org (Postfix) with ESMTPS id E02FBD3A\n\tfor <dev@openvswitch.org>; Tue, 19 Sep 2017 22:02:55 +0000 (UTC)",
            "from relay4-d.mail.gandi.net (relay4-d.mail.gandi.net\n\t[217.70.183.196])\n\tby smtp1.linuxfoundation.org (Postfix) with ESMTPS id CE4F620D\n\tfor <dev@openvswitch.org>; Tue, 19 Sep 2017 22:02:51 +0000 (UTC)",
            "from sigabrt.benpfaff.org (unknown [208.91.2.3])\n\t(Authenticated sender: blp@ovn.org)\n\tby relay4-d.mail.gandi.net (Postfix) with ESMTPSA id 926121720A4;\n\tWed, 20 Sep 2017 00:02:48 +0200 (CEST)"
        ],
        "X-Greylist": "domain auto-whitelisted by SQLgrey-1.7.6",
        "X-Originating-IP": "208.91.2.3",
        "From": "Ben Pfaff <blp@ovn.org>",
        "To": "dev@openvswitch.org",
        "Date": "Tue, 19 Sep 2017 15:01:19 -0700",
        "Message-Id": "<20170919220125.32535-47-blp@ovn.org>",
        "X-Mailer": "git-send-email 2.10.2",
        "In-Reply-To": "<20170919220125.32535-1-blp@ovn.org>",
        "References": "<20170919220125.32535-1-blp@ovn.org>",
        "X-Spam-Status": "No, score=-0.7 required=5.0 tests=RCVD_IN_DNSWL_LOW\n\tautolearn=disabled version=3.3.1",
        "X-Spam-Checker-Version": "SpamAssassin 3.3.1 (2010-03-16) on\n\tsmtp1.linux-foundation.org",
        "Cc": "Ben Pfaff <blp@ovn.org>",
        "Subject": "[ovs-dev] [PATCH RFC 46/52] ovsdb: Add support for online schema\n\tconversion.",
        "X-BeenThere": "ovs-dev@openvswitch.org",
        "X-Mailman-Version": "2.1.12",
        "Precedence": "list",
        "List-Id": "<ovs-dev.openvswitch.org>",
        "List-Unsubscribe": "<https://mail.openvswitch.org/mailman/options/ovs-dev>,\n\t<mailto:ovs-dev-request@openvswitch.org?subject=unsubscribe>",
        "List-Archive": "<http://mail.openvswitch.org/pipermail/ovs-dev/>",
        "List-Post": "<mailto:ovs-dev@openvswitch.org>",
        "List-Help": "<mailto:ovs-dev-request@openvswitch.org?subject=help>",
        "List-Subscribe": "<https://mail.openvswitch.org/mailman/listinfo/ovs-dev>,\n\t<mailto:ovs-dev-request@openvswitch.org?subject=subscribe>",
        "MIME-Version": "1.0",
        "Content-Type": "text/plain; charset=\"us-ascii\"",
        "Content-Transfer-Encoding": "7bit",
        "Sender": "ovs-dev-bounces@openvswitch.org",
        "Errors-To": "ovs-dev-bounces@openvswitch.org"
    },
    "content": "With this change, \"ovsdb-client convert\" can be used to convert a database\nfrom one schema to another without taking the database offline.\n\nSigned-off-by: Ben Pfaff <blp@ovn.org>\n---\n NEWS                    |   2 +\n lib/ovsdb-data.c        |  13 +++\n lib/ovsdb-data.h        |   6 ++\n ovsdb/file.c            | 135 +++++++++++++++++++++++++--\n ovsdb/file.h            |   4 +\n ovsdb/jsonrpc-server.c  | 120 +++++++++++-------------\n ovsdb/jsonrpc-server.h  |   3 +-\n ovsdb/log.c             | 144 ++++++++++++++++++++++++----\n ovsdb/log.h             |  14 ++-\n ovsdb/monitor.c         |  19 +++-\n ovsdb/monitor.h         |   2 +\n ovsdb/ovsdb-client.1.in |  39 ++++++++\n ovsdb/ovsdb-client.c    | 108 +++++++++++++++------\n ovsdb/ovsdb-server.1.in |  43 +++++++++\n ovsdb/ovsdb-server.c    |  56 ++++++-----\n ovsdb/ovsdb.7.xml       |   9 +-\n ovsdb/ovsdb.c           |  26 +++++-\n ovsdb/ovsdb.h           |   3 +-\n ovsdb/transaction.c     |  37 +++++---\n ovsdb/transaction.h     |   5 +\n ovsdb/trigger.c         | 132 ++++++++++++++++++++------\n ovsdb/trigger.h         |  17 ++--\n tests/ovsdb-log.at      |   2 +-\n tests/ovsdb-monitor.at  |   4 +-\n tests/ovsdb-server.at   | 243 +++++++++++++++++++++++++++++++++++++++++++++++-\n tests/test-ovsdb.c      |  16 ++--\n 26 files changed, 984 insertions(+), 218 deletions(-)",
    "diff": "diff --git a/NEWS b/NEWS\nindex 31f4b884bf55..f678e7caedb9 100644\n--- a/NEWS\n+++ b/NEWS\n@@ -3,6 +3,8 @@ Post-v2.8.0\n    - OVSDB:\n      * New high-level documentation in ovsdb(7).\n      * New file format documentation for developers in ovsdb(5).\n+     * ovsdb-server now supports online schema conversion via\n+       \"ovsdb-client convert\".\n      * ovsdb-server now always hosts a built-in database named _Server.  See\n        ovsdb-server(5) for more details.\n    - ovs-vsctl and other commands that display data in tables now support a\ndiff --git a/lib/ovsdb-data.c b/lib/ovsdb-data.c\nindex 711d84d9badc..b4ea3dcac227 100644\n--- a/lib/ovsdb-data.c\n+++ b/lib/ovsdb-data.c\n@@ -1650,6 +1650,19 @@ ovsdb_datum_from_smap(struct ovsdb_datum *datum, const struct smap *smap)\n     ovsdb_datum_sort_unique(datum, OVSDB_TYPE_STRING, OVSDB_TYPE_STRING);\n }\n \n+struct ovsdb_error * OVS_WARN_UNUSED_RESULT\n+ovsdb_datum_convert(struct ovsdb_datum *dst,\n+                    const struct ovsdb_type *dst_type,\n+                    const struct ovsdb_datum *src,\n+                    const struct ovsdb_type *src_type)\n+{\n+    struct json *json = ovsdb_datum_to_json(src, src_type);\n+    struct ovsdb_error *error = ovsdb_datum_from_json(dst, dst_type, json,\n+                                                      NULL);\n+    json_destroy(json);\n+    return error;\n+}\n+\n static uint32_t\n hash_atoms(enum ovsdb_atomic_type type, const union ovsdb_atom *atoms,\n            unsigned int n, uint32_t basis)\ndiff --git a/lib/ovsdb-data.h b/lib/ovsdb-data.h\nindex 257e58e2a653..835d0a8a14e7 100644\n--- a/lib/ovsdb-data.h\n+++ b/lib/ovsdb-data.h\n@@ -188,6 +188,12 @@ void ovsdb_datum_to_bare(const struct ovsdb_datum *,\n \n void ovsdb_datum_from_smap(struct ovsdb_datum *, const struct smap *);\n \n+struct ovsdb_error *ovsdb_datum_convert(struct ovsdb_datum *dst,\n+                                        const struct ovsdb_type *dst_type,\n+                                        const struct ovsdb_datum *src,\n+                                        const struct ovsdb_type *src_type)\n+    OVS_WARN_UNUSED_RESULT;\n+\n /* Comparison. */\n uint32_t ovsdb_datum_hash(const struct ovsdb_datum *,\n                           const struct ovsdb_type *, uint32_t basis);\ndiff --git a/ovsdb/file.c b/ovsdb/file.c\nindex 6b2adad2759c..54e5df15a2cd 100644\n--- a/ovsdb/file.c\n+++ b/ovsdb/file.c\n@@ -559,22 +559,31 @@ ovsdb_file_change_cb(const struct ovsdb_row *old,\n     return true;\n }\n \n-struct ovsdb_error *\n-ovsdb_file_commit(struct ovsdb_file *file,\n-                  const struct ovsdb_txn *txn, bool durable)\n+/* Returns 'txn' transformed into the JSON format that is used in OVSDB files.\n+ * (But the caller must use ovsdb_file_txn_annotate() to add the _comment the\n+ * _date members.)  If 'txn' doesn't actually change anything, returns NULL */\n+static struct json *\n+ovsdb_file_txn_to_json(const struct ovsdb_txn *txn)\n {\n     struct ovsdb_file_txn ftxn;\n-    struct ovsdb_error *error;\n \n     ovsdb_file_txn_init(&ftxn);\n     ovsdb_txn_for_each_change(txn, ovsdb_file_change_cb, &ftxn);\n-    if (!ftxn.json) {\n+    return ftxn.json;\n+}\n+\n+struct ovsdb_error *\n+ovsdb_file_commit(struct ovsdb_file *file,\n+                  const struct ovsdb_txn *txn, bool durable)\n+{\n+    struct json *txn_json = ovsdb_file_txn_to_json(txn);\n+    if (!txn_json) {\n         /* Nothing to commit. */\n         return NULL;\n     }\n \n-    error = ovsdb_file_txn_commit(ftxn.json, ovsdb_txn_get_comment(txn),\n-                                  durable, file->log);\n+    struct ovsdb_error *error = ovsdb_file_txn_commit(\n+        txn_json, ovsdb_txn_get_comment(txn), durable, file->log);\n     if (error) {\n         return error;\n     }\n@@ -846,3 +855,115 @@ ovsdb_file_txn_commit(struct json *json, const char *comment,\n \n     return NULL;\n }\n+\f\n+static struct ovsdb_error * OVS_WARN_UNUSED_RESULT\n+ovsdb_convert_table(struct ovsdb_txn *txn,\n+                    const struct ovsdb_table *src_table,\n+                    struct ovsdb_table *dst_table)\n+{\n+    const struct ovsdb_row *src_row;\n+    HMAP_FOR_EACH (src_row, hmap_node, &src_table->rows) {\n+        struct ovsdb_row *dst_row = ovsdb_row_create(dst_table);\n+        *ovsdb_row_get_uuid_rw(dst_row) = *ovsdb_row_get_uuid(src_row);\n+\n+        struct shash_node *node;\n+        SHASH_FOR_EACH (node, &src_table->schema->columns) {\n+            const struct ovsdb_column *src_column = node->data;\n+            if (src_column->index == OVSDB_COL_UUID ||\n+                src_column->index == OVSDB_COL_VERSION) {\n+                continue;\n+            }\n+\n+            const struct ovsdb_column *dst_column\n+                = shash_find_data(&dst_table->schema->columns,\n+                                  src_column->name);\n+            if (!dst_column) {\n+                continue;\n+            }\n+\n+            struct ovsdb_error *error = ovsdb_datum_convert(\n+                &dst_row->fields[dst_column->index], &dst_column->type,\n+                &src_row->fields[src_column->index], &src_column->type);\n+            if (error) {\n+                ovsdb_row_destroy(dst_row);\n+                return error;\n+            }\n+        }\n+\n+        ovsdb_txn_row_insert(txn, dst_row);\n+    }\n+    return NULL;\n+}\n+\n+struct ovsdb_error * OVS_WARN_UNUSED_RESULT\n+ovsdb_file_convert(const struct ovsdb_file *file,\n+                   const struct ovsdb_schema *new_schema)\n+{\n+    struct ovsdb *new_db = ovsdb_create(ovsdb_schema_clone(new_schema));\n+    struct ovsdb_txn *txn = ovsdb_txn_create(new_db);\n+    struct ovsdb_error *error = NULL;\n+\n+    struct shash_node *node;\n+    SHASH_FOR_EACH (node, &file->db->tables) {\n+        const char *table_name = node->name;\n+        const struct ovsdb_table *src_table = node->data;\n+        struct ovsdb_table *dst_table = shash_find_data(&new_db->tables,\n+                                                        table_name);\n+        if (!dst_table) {\n+            continue;\n+        }\n+\n+        error = ovsdb_convert_table(txn, src_table, dst_table);\n+        if (error) {\n+            goto error;\n+        }\n+    }\n+\n+    error = ovsdb_txn_start_commit(txn);\n+    if (error) {\n+        goto error;\n+    }\n+\n+    struct ovsdb_log *new;\n+    error = ovsdb_log_replace_start(file->log, &new);\n+    if (error) {\n+        goto error;\n+    }\n+\n+    /* Write schema. */\n+    struct json *schema_json = ovsdb_schema_to_json(new_schema);\n+    error = ovsdb_log_write(new, schema_json);\n+    json_destroy(schema_json);\n+    if (error) {\n+        goto error;\n+    }\n+\n+    /* Write data. */\n+    struct json *txn_json = ovsdb_file_txn_to_json(txn);\n+    if (txn_json) {\n+        error = ovsdb_log_write(new, txn_json);\n+        json_destroy(txn_json);\n+        if (error) {\n+            goto error;\n+        }\n+    }\n+\n+    error = ovsdb_log_replace_commit(file->log, new);\n+    if (error) {\n+        goto error;\n+    }\n+\n+    error = ovsdb_txn_finish_commit(txn, true);\n+    ovs_assert(!error);         /* Can't happen. */\n+\n+    ovsdb_replace(file->db, new_db);\n+\n+    return NULL;\n+\n+error:\n+    ovsdb_destroy(new_db);\n+    if (txn) {\n+        ovsdb_txn_abort(txn);\n+    }\n+    return error;\n+}\ndiff --git a/ovsdb/file.h b/ovsdb/file.h\nindex d4b8c2015815..30f211c431dc 100644\n--- a/ovsdb/file.h\n+++ b/ovsdb/file.h\n@@ -49,4 +49,8 @@ struct ovsdb_error *ovsdb_file_commit(struct ovsdb_file *,\n                                       const struct ovsdb_txn *, bool durable);\n void ovsdb_file_destroy(struct ovsdb_file *);\n \n+struct ovsdb_error *ovsdb_file_convert(const struct ovsdb_file *,\n+                                       const struct ovsdb_schema *)\n+    OVS_WARN_UNUSED_RESULT;\n+\n #endif /* ovsdb/file.h */\ndiff --git a/ovsdb/jsonrpc-server.c b/ovsdb/jsonrpc-server.c\nindex a667dbe67f5f..8b2a4648bb1d 100644\n--- a/ovsdb/jsonrpc-server.c\n+++ b/ovsdb/jsonrpc-server.c\n@@ -82,7 +82,7 @@ static void ovsdb_jsonrpc_session_send(struct ovsdb_jsonrpc_session *,\n /* Triggers. */\n static void ovsdb_jsonrpc_trigger_create(struct ovsdb_jsonrpc_session *,\n                                          struct ovsdb *,\n-                                         struct json *id, struct json *params);\n+                                         struct jsonrpc_msg *request);\n static struct ovsdb_jsonrpc_trigger *ovsdb_jsonrpc_trigger_find(\n     struct ovsdb_jsonrpc_session *, const struct json *id, size_t hash);\n static void ovsdb_jsonrpc_trigger_complete(struct ovsdb_jsonrpc_trigger *);\n@@ -936,17 +936,6 @@ ovsdb_jsonrpc_session_unlock(struct ovsdb_jsonrpc_session *s,\n }\n \n static struct jsonrpc_msg *\n-execute_transaction(struct ovsdb_jsonrpc_session *s, struct ovsdb *db,\n-                    struct jsonrpc_msg *request)\n-{\n-    ovsdb_jsonrpc_trigger_create(s, db, request->id, request->params);\n-    request->id = NULL;\n-    request->params = NULL;\n-    jsonrpc_msg_destroy(request);\n-    return NULL;\n-}\n-\n-static struct jsonrpc_msg *\n ovsdb_jsonrpc_session_set_db_change_aware(struct ovsdb_jsonrpc_session *s,\n                                           const struct jsonrpc_msg *request)\n {\n@@ -967,10 +956,11 @@ ovsdb_jsonrpc_session_got_request(struct ovsdb_jsonrpc_session *s,\n {\n     struct jsonrpc_msg *reply;\n \n-    if (!strcmp(request->method, \"transact\")) {\n+    if (!strcmp(request->method, \"transact\") ||\n+        !strcmp(request->method, \"convert_db\")) {\n         struct ovsdb *db = ovsdb_jsonrpc_lookup_db(s, request, &reply);\n         if (!reply) {\n-            reply = execute_transaction(s, db, request);\n+            ovsdb_jsonrpc_trigger_create(s, db, request);\n         }\n     } else if (!strcmp(request->method, \"monitor\") ||\n                (monitor_cond_enable__ && !strcmp(request->method,\n@@ -1082,37 +1072,35 @@ struct ovsdb_jsonrpc_trigger {\n \n static void\n ovsdb_jsonrpc_trigger_create(struct ovsdb_jsonrpc_session *s, struct ovsdb *db,\n-                             struct json *id, struct json *params)\n+                             struct jsonrpc_msg *request)\n {\n-    struct ovsdb_jsonrpc_trigger *t;\n-    size_t hash;\n-\n     /* Check for duplicate ID. */\n-    hash = json_hash(id, 0);\n-    t = ovsdb_jsonrpc_trigger_find(s, id, hash);\n+    size_t hash = json_hash(request->id, 0);\n+    struct ovsdb_jsonrpc_trigger *t\n+        = ovsdb_jsonrpc_trigger_find(s, request->id, hash);\n     if (t) {\n-        struct jsonrpc_msg *msg;\n-\n-        msg = jsonrpc_create_error(json_string_create(\"duplicate request ID\"),\n-                                   id);\n-        ovsdb_jsonrpc_session_send(s, msg);\n-        json_destroy(id);\n-        json_destroy(params);\n+        ovsdb_jsonrpc_session_send(\n+            s, syntax_error_reply(request, \"duplicate request ID\"));\n+        jsonrpc_msg_destroy(request);\n         return;\n     }\n \n     /* Insert into trigger table. */\n     t = xmalloc(sizeof *t);\n-    ovsdb_trigger_init(&s->up, db, &t->trigger, params, time_msec(),\n-                       s->read_only, s->remote->role,\n-                       jsonrpc_session_get_id(s->js));\n-    t->id = id;\n+    bool disconnect_all = ovsdb_trigger_init(\n+        &s->up, db, &t->trigger, request, time_msec(), s->read_only,\n+        s->remote->role, jsonrpc_session_get_id(s->js));\n+    t->id = json_clone(request->id);\n     hmap_insert(&s->triggers, &t->hmap_node, hash);\n \n     /* Complete early if possible. */\n     if (ovsdb_trigger_is_complete(&t->trigger)) {\n         ovsdb_jsonrpc_trigger_complete(t);\n     }\n+\n+    if (disconnect_all) {\n+        ovsdb_jsonrpc_server_reconnect(s->remote->server, false);\n+    }\n }\n \n static struct ovsdb_jsonrpc_trigger *\n@@ -1139,12 +1127,9 @@ ovsdb_jsonrpc_trigger_complete(struct ovsdb_jsonrpc_trigger *t)\n \n     if (jsonrpc_session_is_connected(s->js)) {\n         struct jsonrpc_msg *reply;\n-        struct json *result;\n \n-        result = ovsdb_trigger_steal_result(&t->trigger);\n-        if (result) {\n-            reply = jsonrpc_create_reply(result, t->id);\n-        } else {\n+        reply = ovsdb_trigger_steal_reply(&t->trigger);\n+        if (!reply) {\n             reply = jsonrpc_create_error(json_string_create(\"canceled\"),\n                                          t->id);\n         }\n@@ -1159,7 +1144,7 @@ ovsdb_jsonrpc_trigger_complete(struct ovsdb_jsonrpc_trigger *t)\n \n static void\n ovsdb_jsonrpc_trigger_remove__(struct ovsdb_jsonrpc_session *s,\n-                                   struct ovsdb *db)\n+                               struct ovsdb *db)\n {\n     struct ovsdb_jsonrpc_trigger *t, *next;\n     HMAP_FOR_EACH_SAFE (t, next, hmap_node, &s->triggers) {\n@@ -1189,11 +1174,9 @@ ovsdb_jsonrpc_trigger_complete_all(struct ovsdb_jsonrpc_session *s)\n static void\n ovsdb_jsonrpc_trigger_complete_done(struct ovsdb_jsonrpc_session *s)\n {\n-    while (!ovs_list_is_empty(&s->up.completions)) {\n-        struct ovsdb_jsonrpc_trigger *t\n-            = CONTAINER_OF(s->up.completions.next,\n-                           struct ovsdb_jsonrpc_trigger, trigger.node);\n-        ovsdb_jsonrpc_trigger_complete(t);\n+    struct ovsdb_jsonrpc_trigger *trigger, *next;\n+    LIST_FOR_EACH_SAFE (trigger, next, trigger.node, &s->up.completions) {\n+        ovsdb_jsonrpc_trigger_complete(trigger);\n     }\n }\n \f\n@@ -1441,7 +1424,7 @@ ovsdb_jsonrpc_monitor_create(struct ovsdb_jsonrpc_session *s, struct ovsdb *db,\n \n error:\n     if (m) {\n-        ovsdb_jsonrpc_monitor_destroy(m);\n+        ovsdb_jsonrpc_monitor_destroy(m, false);\n     }\n \n     return jsonrpc_create_error(ovsdb_error_to_json_free(error), request_id);\n@@ -1599,32 +1582,12 @@ ovsdb_jsonrpc_monitor_cancel(struct ovsdb_jsonrpc_session *s,\n             return jsonrpc_create_error(json_string_create(\"unknown monitor\"),\n                                         request_id);\n         } else {\n-            ovsdb_jsonrpc_monitor_destroy(m);\n+            ovsdb_jsonrpc_monitor_destroy(m, false);\n             return jsonrpc_create_reply(json_object_create(), request_id);\n         }\n     }\n }\n \n-static void\n-ovsdb_jsonrpc_monitor_remove__(struct ovsdb_jsonrpc_session *s,\n-                               struct ovsdb *db)\n-{\n-    struct ovsdb_jsonrpc_monitor *m, *next;\n-\n-    HMAP_FOR_EACH_SAFE (m, next, node, &s->monitors) {\n-        if (!db || m->db == db) {\n-            if (db && jsonrpc_session_is_connected(s->js)\n-                && s->db_change_aware) {\n-                struct jsonrpc_msg *notify = jsonrpc_create_notify(\n-                    \"monitor_canceled\",\n-                    json_array_create_1(json_clone(m->monitor_id)));\n-                ovsdb_jsonrpc_session_send(s, notify);\n-            }\n-            ovsdb_jsonrpc_monitor_destroy(m);\n-        }\n-    }\n-}\n-\n /* Database 'db' is about to be removed from the database server.  To prepare,\n  * this function removes all references from monitors in 's' to 'db'. */\n static void\n@@ -1632,14 +1595,24 @@ ovsdb_jsonrpc_monitor_preremove_db(struct ovsdb_jsonrpc_session *s,\n                                    struct ovsdb *db)\n {\n     ovs_assert(db);\n-    ovsdb_jsonrpc_monitor_remove__(s, db);\n+\n+    struct ovsdb_jsonrpc_monitor *m, *next;\n+    HMAP_FOR_EACH_SAFE (m, next, node, &s->monitors) {\n+        if (m->db == db) {\n+            ovsdb_jsonrpc_monitor_destroy(m, true);\n+        }\n+    }\n }\n \n /* Cancels all monitors in 's'. */\n static void\n ovsdb_jsonrpc_monitor_remove_all(struct ovsdb_jsonrpc_session *s)\n {\n-    ovsdb_jsonrpc_monitor_remove__(s, NULL);\n+    struct ovsdb_jsonrpc_monitor *m, *next;\n+\n+    HMAP_FOR_EACH_SAFE (m, next, node, &s->monitors) {\n+        ovsdb_jsonrpc_monitor_destroy(m, false);\n+    }\n }\n \n static struct json *\n@@ -1670,8 +1643,19 @@ ovsdb_jsonrpc_monitor_needs_flush(struct ovsdb_jsonrpc_session *s)\n }\n \n void\n-ovsdb_jsonrpc_monitor_destroy(struct ovsdb_jsonrpc_monitor *m)\n-{\n+ovsdb_jsonrpc_monitor_destroy(struct ovsdb_jsonrpc_monitor *m,\n+                              bool notify_cancellation)\n+{\n+    if (notify_cancellation) {\n+        struct ovsdb_jsonrpc_session *s = m->session;\n+        if (jsonrpc_session_is_connected(s->js) && s->db_change_aware) {\n+            struct jsonrpc_msg *notify = jsonrpc_create_notify(\n+                \"monitor_canceled\",\n+                json_array_create_1(json_clone(m->monitor_id)));\n+            ovsdb_jsonrpc_session_send(s, notify);\n+        }\n+    }\n+\n     json_destroy(m->monitor_id);\n     hmap_remove(&m->session->monitors, &m->node);\n     ovsdb_monitor_remove_jsonrpc_monitor(m->dbmon, m, m->unflushed);\ndiff --git a/ovsdb/jsonrpc-server.h b/ovsdb/jsonrpc-server.h\nindex 50a8b879c5a9..0fc16f21b2d9 100644\n--- a/ovsdb/jsonrpc-server.h\n+++ b/ovsdb/jsonrpc-server.h\n@@ -80,7 +80,8 @@ const struct uuid *ovsdb_jsonrpc_server_get_uuid(\n     const struct ovsdb_jsonrpc_server *);\n \n struct ovsdb_jsonrpc_monitor;\n-void ovsdb_jsonrpc_monitor_destroy(struct ovsdb_jsonrpc_monitor *);\n+void ovsdb_jsonrpc_monitor_destroy(struct ovsdb_jsonrpc_monitor *,\n+                                   bool notify_cancellation);\n void ovsdb_jsonrpc_disable_monitor_cond(void);\n \n #endif /* ovsdb/jsonrpc-server.h */\ndiff --git a/ovsdb/log.c b/ovsdb/log.c\nindex 4b0d9640cafd..a223d30ac28b 100644\n--- a/ovsdb/log.c\n+++ b/ovsdb/log.c\n@@ -92,26 +92,24 @@ ovsdb_log_open(const char *name, const char *magic,\n         lockfile = NULL;\n     }\n \n-    if (open_mode == OVSDB_LOG_READ_ONLY) {\n+    switch (open_mode) {\n+    case OVSDB_LOG_READ_ONLY:\n         flags = O_RDONLY;\n-    } else if (open_mode == OVSDB_LOG_READ_WRITE) {\n+        break;\n+\n+    case OVSDB_LOG_READ_WRITE:\n         flags = O_RDWR;\n-    } else if (open_mode == OVSDB_LOG_CREATE) {\n-#ifndef _WIN32\n-        if (stat(name, &s) == -1 && errno == ENOENT\n-            && lstat(name, &s) == 0 && S_ISLNK(s.st_mode)) {\n-            /* 'name' is a dangling symlink.  We want to create the file that\n-             * the symlink points to, but POSIX says that open() with O_EXCL\n-             * must fail with EEXIST if the named file is a symlink.  So, we\n-             * have to leave off O_EXCL and accept the race. */\n-            flags = O_RDWR | O_CREAT;\n-        } else {\n-            flags = O_RDWR | O_CREAT | O_EXCL;\n-        }\n-#else\n+        break;\n+\n+    case OVSDB_LOG_CREATE_EXCL:\n         flags = O_RDWR | O_CREAT | O_EXCL;\n-#endif\n-    } else {\n+        break;\n+\n+    case OVSDB_LOG_CREATE:\n+        flags = O_RDWR | O_CREAT;\n+        break;\n+\n+    default:\n         OVS_NOT_REACHED();\n     }\n #ifdef _WIN32\n@@ -119,7 +117,9 @@ ovsdb_log_open(const char *name, const char *magic,\n #endif\n     fd = open(name, flags, 0666);\n     if (fd < 0) {\n-        const char *op = open_mode == OVSDB_LOG_CREATE ? \"create\" : \"open\";\n+        const char *op = (open_mode == OVSDB_LOG_CREATE_EXCL ? \"create\"\n+            : open_mode == OVSDB_LOG_CREATE ? \"create or open\"\n+            : \"open\");\n         error = ovsdb_io_error(errno, \"%s: %s failed\", name, op);\n         goto error_unlock;\n     }\n@@ -182,7 +182,9 @@ ovsdb_log_close(struct ovsdb_log *file)\n {\n     if (file) {\n         free(file->name);\n-        fclose(file->stream);\n+        if (file->stream) {\n+            fclose(file->stream);\n+        }\n         lockfile_unlock(file->lockfile);\n         ovsdb_error_destroy(file->read_error);\n         free(file);\n@@ -433,3 +435,107 @@ ovsdb_log_get_offset(const struct ovsdb_log *log)\n {\n     return log->offset;\n }\n+\f\n+struct ovsdb_error * OVS_WARN_UNUSED_RESULT\n+ovsdb_log_replace(struct ovsdb_log *log, struct json **entries, size_t n)\n+{\n+    struct ovsdb_error *error;\n+    struct ovsdb_log *new;\n+\n+    error = ovsdb_log_replace_start(log, &new);\n+    if (error) {\n+        return error;\n+    }\n+\n+    for (size_t i = 0; i < n; i++) {\n+        error = ovsdb_log_write(new, entries[i]);\n+        if (error) {\n+            ovsdb_log_replace_abort(new);\n+            return error;\n+        }\n+    }\n+\n+    return ovsdb_log_replace_commit(log, new);\n+}\n+\n+struct ovsdb_error * OVS_WARN_UNUSED_RESULT\n+ovsdb_log_replace_start(struct ovsdb_log *old,\n+                        struct ovsdb_log **newp)\n+{\n+    char *tmp_name = xasprintf(\"%s.tmp\", old->name);\n+    struct ovsdb_error *error;\n+\n+    ovs_assert(old->lockfile);\n+\n+    /* Remove temporary file.  (It might not exist.) */\n+    if (unlink(tmp_name) < 0 && errno != ENOENT) {\n+        error = ovsdb_io_error(errno, \"failed to remove %s\", tmp_name);\n+        free(tmp_name);\n+        *newp = NULL;\n+        return error;\n+    }\n+\n+    /* Create temporary file. */\n+    error = ovsdb_log_open(tmp_name, old->magic, OVSDB_LOG_CREATE_EXCL,\n+                           false, newp);\n+    if (error) {\n+        free(tmp_name);\n+        *newp = NULL;\n+    }\n+    return error;\n+}\n+\n+struct ovsdb_error * OVS_WARN_UNUSED_RESULT\n+ovsdb_log_replace_commit(struct ovsdb_log *old, struct ovsdb_log *new)\n+{\n+    struct ovsdb_error *error = ovsdb_log_commit(new);\n+    if (error) {\n+        ovsdb_log_close(new);\n+        return error;\n+    }\n+\n+    /* Replace old file by new file on-disk. */\n+    if (rename(new->name, old->name)) {\n+        error = ovsdb_io_error(errno, \"failed to rename \\\"%s\\\" to \\\"%s\\\"\",\n+                               new->name, old->name);\n+        ovsdb_log_close(new);\n+        return error;\n+    }\n+    fsync_parent_dir(old->name);\n+\n+    /* Replace 'old' by 'new' in memory.\n+     *\n+     * 'old' transitions to OVSDB_LOG_WRITE (it was probably in that mode\n+     * anyway). */\n+    /* prev_offset only matters for OVSDB_LOG_READ. */\n+    old->offset = new->offset;\n+    /* Keep old->name and old->rel_name. */\n+    free(old->magic);\n+    old->magic = new->magic;\n+    new->magic = NULL;\n+    /* Keep old->lockfile. */\n+    fclose(old->stream);\n+    old->stream = new->stream;\n+    new->stream = NULL;\n+    /* read_error only matters for OVSDB_LOG_READ. */\n+    old->write_error = new->write_error;\n+    old->mode = OVSDB_LOG_WRITE;\n+\n+    /* Free 'new'. */\n+    ovsdb_log_close(new);\n+\n+    return NULL;\n+}\n+\n+void\n+ovsdb_log_replace_abort(struct ovsdb_log *new)\n+{\n+    if (new) {\n+        /* Unlink the new file, but only after we close it (for Windows\n+         * compatibility). */\n+        char *name = xstrdup(new->name);\n+        ovsdb_log_close(new);\n+        unlink(name);\n+        free(name);\n+    }\n+}\ndiff --git a/ovsdb/log.h b/ovsdb/log.h\nindex fd495e518dd0..439487ade12e 100644\n--- a/ovsdb/log.h\n+++ b/ovsdb/log.h\n@@ -26,7 +26,8 @@ struct ovsdb_log;\n enum ovsdb_log_open_mode {\n     OVSDB_LOG_READ_ONLY,        /* Open existing file, read-only. */\n     OVSDB_LOG_READ_WRITE,       /* Open existing file, read/write. */\n-    OVSDB_LOG_CREATE            /* Create new file, read/write. */\n+    OVSDB_LOG_CREATE_EXCL,      /* Create new file, read/write. */\n+    OVSDB_LOG_CREATE            /* Create or open file, read/write. */\n };\n \n #define OVSDB_MAGIC \"OVSDB JSON\"\n@@ -48,4 +49,15 @@ struct ovsdb_error *ovsdb_log_commit(struct ovsdb_log *)\n \n off_t ovsdb_log_get_offset(const struct ovsdb_log *);\n \n+struct ovsdb_error *ovsdb_log_replace(struct ovsdb_log *,\n+                                      struct json **entries, size_t n)\n+    OVS_WARN_UNUSED_RESULT;\n+struct ovsdb_error *ovsdb_log_replace_start(struct ovsdb_log *old,\n+                                            struct ovsdb_log **newp)\n+    OVS_WARN_UNUSED_RESULT;\n+struct ovsdb_error *ovsdb_log_replace_commit(struct ovsdb_log *old,\n+                                             struct ovsdb_log *new)\n+    OVS_WARN_UNUSED_RESULT;\n+void ovsdb_log_replace_abort(struct ovsdb_log *new);\n+\n #endif /* ovsdb/log.h */\ndiff --git a/ovsdb/monitor.c b/ovsdb/monitor.c\nindex 3e58c3fbd274..97706932614c 100644\n--- a/ovsdb/monitor.c\n+++ b/ovsdb/monitor.c\n@@ -1613,7 +1613,7 @@ ovsdb_monitors_remove(struct ovsdb *db)\n          * end monitor will also destroy the corresponding 'ovsdb_monitor'.\n          * ovsdb monitor will also be destroied.  */\n         LIST_FOR_EACH_SAFE (jm, next_jm, node, &m->jsonrpc_monitors) {\n-            ovsdb_jsonrpc_monitor_destroy(jm->jsonrpc_monitor);\n+            ovsdb_jsonrpc_monitor_destroy(jm->jsonrpc_monitor, false);\n         }\n     }\n }\n@@ -1630,3 +1630,20 @@ ovsdb_monitor_get_memory_usage(struct simap *usage)\n         simap_increase(usage, \"json-caches\", hmap_count(&dbmon->json_cache));\n     }\n }\n+\n+void\n+ovsdb_monitor_prereplace_db(struct ovsdb *db)\n+{\n+    struct ovsdb_monitor *m, *next_m;\n+\n+    LIST_FOR_EACH_SAFE (m, next_m, list_node, &db->monitors) {\n+        struct jsonrpc_monitor_node *jm, *next_jm;\n+\n+        /* Delete all front end monitors. Removing the last front\n+         * end monitor will also destroy the corresponding 'ovsdb_monitor'.\n+         * ovsdb monitor will also be destroied.  */\n+        LIST_FOR_EACH_SAFE (jm, next_jm, node, &m->jsonrpc_monitors) {\n+            ovsdb_jsonrpc_monitor_destroy(jm->jsonrpc_monitor, true);\n+        }\n+    }\n+}\ndiff --git a/ovsdb/monitor.h b/ovsdb/monitor.h\nindex 99d43c45dff9..eb3ff270c9f3 100644\n--- a/ovsdb/monitor.h\n+++ b/ovsdb/monitor.h\n@@ -49,6 +49,8 @@ struct ovsdb_monitor *ovsdb_monitor_create(struct ovsdb *db,\n void ovsdb_monitors_remove(struct ovsdb *);\n void ovsdb_monitors_commit(struct ovsdb *, const struct ovsdb_txn *);\n \n+void ovsdb_monitor_prereplace_db(struct ovsdb *);\n+\n struct ovsdb_monitor *ovsdb_monitor_add(struct ovsdb_monitor *dbmon);\n \n void ovsdb_monitor_add_jsonrpc_monitor(struct ovsdb_monitor *dbmon,\ndiff --git a/ovsdb/ovsdb-client.1.in b/ovsdb/ovsdb-client.1.in\nindex 4b543d96e552..f8630ba40f25 100644\n--- a/ovsdb/ovsdb-client.1.in\n+++ b/ovsdb/ovsdb-client.1.in\n@@ -22,6 +22,9 @@ ovsdb\\-client \\- command-line interface to \\fBovsdb-server\\fR(1)\n .br\n \\fBovsdb\\-client \\fR[\\fIoptions\\fR] \\fBlist\\-columns\\fI \\fR[\\fIserver\\fR] \\fR[\\fIdatabase\\fR] [\\fItable\\fR]\n .IP \"Database Version Management Commands:\"\n+\\fBovsdb\\-client \\fR[\\fIoptions\\fR] \\fBconvert \\fR[\\fIserver\\fR] \\fIschema\\fR\n+.br\n+\\fBovsdb\\-client \\fR[\\fIoptions\\fR] \\fBneeds\\-conversion \\fR[\\fIserver\\fR] \\fIschema\\fR\n .br\n \\fBovsdb\\-client \\fR[\\fIoptions\\fR] \\fBget\\-schema\\-version\\fI \\fR[\\fIserver\\fR] \\fR[\\fIdatabase\\fR]\n .IP \"Data Management Commands:\"\n@@ -110,6 +113,42 @@ listed; otherwise, the tables include columns in all tables.\n These commands work with different versions of OVSDB schemas and\n databases.\n .\n+.IP \"\\fBconvert \\fR[\\fIserver\\fR] \\fIschema\\fR\"\n+Reads an OVSDB schema in JSON format, as specified in the OVSDB\n+specification, from \\fIschema\\fR, then connects to \\fIserver\\fR and\n+requests the server to convert the database whose name is specified in\n+\\fIschema\\fR to the schema also specified in \\fIschema\\fR.\n+.IP\n+The conversion is atomic, consistent, isolated, and durable.\n+Following the schema change, the server notifies clients that use the\n+\\fBset_db_change_aware\\fR RPC introduced in Open vSwitch 2.9 and\n+cancel their outstanding transactions and monitors.  The server\n+disconnects other clients, enabling them to notify the change when\n+they reconnect.\n+.IP\n+This command can do simple ``upgrades'' and ``downgrades'' on a\n+database's schema.  The data in the database must be valid when\n+interpreted under \\fIschema\\fR, with only one exception: data for\n+tables and columns that do not exist in \\fIschema\\fR are ignored.\n+Columns that exist in \\fIschema\\fR but not in the database are set to\n+their default values.  All of \\fIschema\\fR's constraints apply in\n+full.\n+.IP\n+Some uses of this command can cause unrecoverable data loss.  For\n+example, converting a database from a schema that has a given column\n+or table to one that does not will delete all data in that column or\n+table.  Back up critical databases before converting them.\n+.IP\n+This command works with clustered and standalone databases.\n+Standalone databases may also be converted (offline) with\n+\\fBovsdb\\-tool\\fR's \\fBconvert\\fR command.\n+.\n+.IP \"\\fBneeds\\-conversion \\fR[\\fIserver\\fR] \\fIschema\\fR\"\n+Reads the schema from \\fIschema\\fR, then connects to \\fIserver\\fR and\n+requests the schema from the database whose name is specified in\n+\\fIschema\\fR.  If the two schemas are the same, prints \\fBno\\fR on\n+stdout; if they differ, prints \\fByes\\fR.\n+.\n .IP \"\\fBget\\-schema\\-version\\fI \\fR[\\fIserver\\fR] \\fR[\\fIdatabase\\fR]\"\n Connects to \\fIserver\\fR, retrieves the schema for \\fIdatabase\\fR, and\n prints its version number on stdout.\ndiff --git a/ovsdb/ovsdb-client.c b/ovsdb/ovsdb-client.c\nindex cecc0346eda1..e12702200616 100644\n--- a/ovsdb/ovsdb-client.c\n+++ b/ovsdb/ovsdb-client.c\n@@ -71,11 +71,17 @@ struct ovsdb_client_command {\n /* --timestamp: Print a timestamp before each update on \"monitor\" command? */\n static bool timestamp;\n \n-/* --db-change-aware: Enable db_change_aware feature for \"monitor\" command?\n+/* --db-change-aware, --no-db-change-aware: Enable db_change_aware feature for\n+ * \"monitor\" command?\n  *\n- * (This option is undocumented because it is expected to be useful only for\n- * testing that the db_change_aware feature actually works.) */\n-static bool db_change_aware;\n+ * -1 (default): Use db_change_aware if available.\n+ * 0: Disable db_change_aware.\n+ * 1: Require db_change_aware.\n+ *\n+ * (This option is undocumented because anything other than the default is\n+ * expected to be useful only for testing that the db_change_aware feature\n+ * actually works.) */\n+static int db_change_aware = -1;\n \n /* Format for table output. */\n static struct table_style table_style = TABLE_STYLE_DEFAULT;\n@@ -188,7 +194,6 @@ parse_options(int argc, char *argv[])\n     enum {\n         OPT_BOOTSTRAP_CA_CERT = UCHAR_MAX + 1,\n         OPT_TIMESTAMP,\n-        OPT_DB_CHANGE_AWARE,\n         VLOG_OPTION_ENUMS,\n         DAEMON_OPTION_ENUMS,\n         TABLE_OPTION_ENUMS,\n@@ -198,7 +203,8 @@ parse_options(int argc, char *argv[])\n         {\"help\", no_argument, NULL, 'h'},\n         {\"version\", no_argument, NULL, 'V'},\n         {\"timestamp\", no_argument, NULL, OPT_TIMESTAMP},\n-        {\"db-change-aware\", no_argument, NULL, OPT_DB_CHANGE_AWARE},\n+        {\"db-change-aware\", no_argument, &db_change_aware, 1},\n+        {\"no-db-change-aware\", no_argument, &db_change_aware, 0},\n         VLOG_LONG_OPTIONS,\n         DAEMON_LONG_OPTIONS,\n #ifdef HAVE_OPENSSL\n@@ -241,10 +247,6 @@ parse_options(int argc, char *argv[])\n             timestamp = true;\n             break;\n \n-        case OPT_DB_CHANGE_AWARE:\n-            db_change_aware = true;\n-            break;\n-\n         case '?':\n             exit(EXIT_FAILURE);\n \n@@ -288,6 +290,8 @@ usage(void)\n            \"    DATABASE on SERVER.\\n\"\n            \"    COLUMNs may include !initial, !insert, !delete, !modify\\n\"\n            \"    to avoid seeing the specified kinds of changes.\\n\"\n+           \"\\n  convert [SERVER] SCHEMA\\n\"\n+           \"    convert database on SERVER named in SCHEMA to SCHEMA.\\n\"\n            \"\\n  monitor [SERVER] [DATABASE] ALL\\n\"\n            \"    monitor all changes to all columns in all tables\\n\"\n            \"    in DATBASE on SERVER.\\n\"\n@@ -530,6 +534,28 @@ do_list_columns(struct jsonrpc *rpc, const char *database,\n }\n \n static void\n+send_db_change_aware(struct jsonrpc *rpc)\n+{\n+    if (db_change_aware != 0) {\n+        struct jsonrpc_msg *request = jsonrpc_create_request(\n+            \"set_db_change_aware\",\n+            json_array_create_1(json_boolean_create(true)),\n+            NULL);\n+        struct jsonrpc_msg *reply;\n+        int error = jsonrpc_transact_block(rpc, request, &reply);\n+        if (error) {\n+            ovs_fatal(error, \"%s: error setting db_change_aware\",\n+                      jsonrpc_get_name(rpc));\n+        }\n+        if (reply->type == JSONRPC_ERROR && db_change_aware == 1) {\n+            ovs_fatal(0, \"%s: set_db_change_aware failed (%s)\",\n+                      jsonrpc_get_name(rpc), json_to_string(reply->error, 0));\n+        }\n+        jsonrpc_msg_destroy(reply);\n+    }\n+}\n+\n+static void\n do_transact(struct jsonrpc *rpc, const char *database OVS_UNUSED,\n             int argc OVS_UNUSED, char *argv[])\n {\n@@ -538,6 +564,13 @@ do_transact(struct jsonrpc *rpc, const char *database OVS_UNUSED,\n \n     transaction = parse_json(argv[0]);\n \n+    if (db_change_aware == 1) {\n+        send_db_change_aware(rpc);\n+    }\n+    daemon_save_fd(STDOUT_FILENO);\n+    daemon_save_fd(STDERR_FILENO);\n+    daemonize();\n+\n     request = jsonrpc_create_request(\"transact\", transaction, NULL);\n     check_txn(jsonrpc_transact_block(rpc, request, &reply), &reply);\n     print_json(reply->result);\n@@ -971,6 +1004,7 @@ do_monitor__(struct jsonrpc *rpc, const char *database,\n     ovs_assert(version < OVSDB_MONITOR_VERSION_MAX);\n \n     daemon_save_fd(STDOUT_FILENO);\n+    daemon_save_fd(STDERR_FILENO);\n     daemonize_start(false);\n     if (get_detach()) {\n         int error;\n@@ -1028,22 +1062,7 @@ do_monitor__(struct jsonrpc *rpc, const char *database,\n         free(nodes);\n     }\n \n-    if (db_change_aware) {\n-        struct jsonrpc_msg *request = jsonrpc_create_request(\n-            \"set_db_change_aware\",\n-            json_array_create_1(json_boolean_create(true)),\n-            NULL);\n-        struct jsonrpc_msg *reply;\n-        int error = jsonrpc_transact_block(rpc, request, &reply);\n-        if (error) {\n-            ovs_fatal(error, \"%s: error setting db_change_aware\", server);\n-        }\n-        if (reply->type == JSONRPC_ERROR) {\n-            ovs_fatal(0, \"%s: set_db_change_aware failed (%s)\",\n-                      server, json_to_string(reply->error, 0));\n-        }\n-        jsonrpc_msg_destroy(reply);\n-    }\n+    send_db_change_aware(rpc);\n \n     monitor = json_array_create_3(json_string_create(database),\n                                   json_null_create(), monitor_requests);\n@@ -1105,6 +1124,10 @@ do_monitor__(struct jsonrpc *rpc, const char *database,\n                     monitor2_print(params->u.array.elems[1], mts, n_mts);\n                     fflush(stdout);\n                 }\n+            } else if (msg->type == JSONRPC_NOTIFY\n+                       && !strcmp(msg->method, \"monitor_canceled\")) {\n+                ovs_fatal(0, \"%s: %s database was removed\",\n+                          server, database);\n             }\n             jsonrpc_msg_destroy(msg);\n         }\n@@ -1160,6 +1183,37 @@ do_monitor_cond(struct jsonrpc *rpc, const char *database,\n     ovsdb_schema_destroy(schema);\n }\n \n+static void\n+do_convert(struct jsonrpc *rpc, const char *database OVS_UNUSED,\n+           int argc OVS_UNUSED, char *argv[])\n+{\n+    struct ovsdb_schema *new_schema;\n+    check_ovsdb_error(ovsdb_schema_from_file(argv[0], &new_schema));\n+\n+    struct jsonrpc_msg *request, *reply;\n+    request = jsonrpc_create_request(\n+        \"convert_db\",\n+        json_array_create_2(json_string_create(new_schema->name),\n+                            ovsdb_schema_to_json(new_schema)), NULL);\n+    check_txn(jsonrpc_transact_block(rpc, request, &reply), &reply);\n+    jsonrpc_msg_destroy(reply);\n+}\n+\n+static void\n+do_needs_conversion(struct jsonrpc *rpc, const char *database OVS_UNUSED,\n+                    int argc OVS_UNUSED, char *argv[])\n+{\n+    struct ovsdb_schema *schema1;\n+    check_ovsdb_error(ovsdb_schema_from_file(argv[0], &schema1));\n+\n+    struct ovsdb_schema *schema2 = fetch_schema(rpc, schema1->name);\n+    if (ovsdb_schema_equal(schema1, schema2)) {\n+        puts(ovsdb_schema_equal(schema1, schema2) ? \"no\" : \"yes\");\n+    }\n+    ovsdb_schema_destroy(schema1);\n+    ovsdb_schema_destroy(schema2);\n+}\n+\n struct dump_table_aux {\n     struct ovsdb_datum **data;\n     const struct ovsdb_column **columns;\n@@ -1617,6 +1671,8 @@ static const struct ovsdb_client_command all_commands[] = {\n     { \"transact\",           NEED_RPC,      1, 1,       do_transact },\n     { \"monitor\",            NEED_DATABASE, 1, INT_MAX, do_monitor },\n     { \"monitor-cond\",       NEED_DATABASE, 2, 3,       do_monitor_cond },\n+    { \"convert\",            NEED_RPC,      1, 1,       do_convert },\n+    { \"needs-conversion\",   NEED_RPC,      1, 1,       do_needs_conversion },\n     { \"dump\",               NEED_DATABASE, 0, INT_MAX, do_dump },\n     { \"lock\",               NEED_RPC,      1, 1,       do_lock_create },\n     { \"steal\",              NEED_RPC,      1, 1,       do_lock_steal },\ndiff --git a/ovsdb/ovsdb-server.1.in b/ovsdb/ovsdb-server.1.in\nindex 9bfd36edd351..02a887b94935 100644\n--- a/ovsdb/ovsdb-server.1.in\n+++ b/ovsdb/ovsdb-server.1.in\n@@ -761,6 +761,49 @@ restores the default behavior.  The reply is always the same:\n .fi\n .RE\n .\n+.IP \"4.1.17. Online Database Schema Conversion\"\n+.IP\n+Before Open vSwitch 2.9, a database's schema could be changed only by\n+taking its server offline, converting it using \\fBovsdb\\-tool\n+convert\\fR, and then bringing it back online.  Open vSwitch 2.9\n+introduces the following RPC to convert the schema of a database\n+without taking it offline.\n+.PP\n+.RS\n+.nf\n+\"method\": \"convert_db\"\n+\"params\": [<database-schema>]\n+\"id\": <nonnull-json-value>\n+.fi\n+.RE\n+.IP\n+If the server successfully converts the database, the server replies\n+with:\n+.PP\n+.RS\n+.nf\n+\"result\": {}\n+\"error\": null\n+\"id\": same \"id\" as request\n+.fi\n+.RE\n+.IP\n+If the server cannot convert the database, it replies with an error:\n+.PP\n+.RS\n+.nf\n+\"result\": null\n+\"error\": <error>\n+\"id\": same \"id\" as request\n+.fi\n+.RE\n+.IP\n+If the new schema and the old schema are identical, the server does\n+not disrupt clients.  Otherwise, when a database's schema changes, the\n+server disconnects all clients that are not database change aware and\n+sends \\fBcancel\\fR RPCs for outstanding transactions and\n+\\fBmonitor_cancel\\fR RPCs for ongoing monitors against the database\n+that changed.\n .IP \"5.1. Notation\"\n For <condition>, RFC 7047 only allows the use of \\fB!=\\fR, \\fB==\\fR,\n \\fBincludes\\fR, and \\fBexcludes\\fR operators with set types.  Open\ndiff --git a/ovsdb/ovsdb-server.c b/ovsdb/ovsdb-server.c\nindex e28fca5989fc..65cf61ade954 100644\n--- a/ovsdb/ovsdb-server.c\n+++ b/ovsdb/ovsdb-server.c\n@@ -111,10 +111,11 @@ static char *open_db(struct server_config *config, const char *filename);\n static void add_server_db(struct server_config *);\n static void close_db(struct db *db);\n \n-static void parse_options(int *argc, char **argvp[],\n-                          struct sset *remotes, char **unixctl_pathp,\n-                          char **run_command, char **sync_from,\n-                          char **sync_exclude, bool *is_backup);\n+static void parse_options(int argc, char *argvp[],\n+                          struct sset *db_filenames, struct sset *remotes,\n+                          char **unixctl_pathp, char **run_command,\n+                          char **sync_from, char **sync_exclude,\n+                          bool *is_backup);\n OVS_NO_RETURN static void usage(void);\n \n static char *reconfigure_remotes(struct ovsdb_jsonrpc_server *,\n@@ -202,7 +203,9 @@ main_loop(struct ovsdb_jsonrpc_server *jsonrpc, struct shash *all_dbs,\n \n         SHASH_FOR_EACH(node, all_dbs) {\n             struct db *db = node->data;\n-            ovsdb_trigger_run(db->db, time_msec());\n+            if (ovsdb_trigger_run(db->db, time_msec())) {\n+                ovsdb_jsonrpc_server_reconnect(jsonrpc, false);\n+            }\n         }\n         if (run_process) {\n             process_run();\n@@ -265,7 +268,6 @@ main(int argc, char *argv[])\n     struct shash all_dbs;\n     struct shash_node *node, *next;\n     char *error;\n-    int i;\n \n     ovs_cmdl_proctitle_init(argc, argv);\n     set_program_name(argv[0]);\n@@ -274,8 +276,8 @@ main(int argc, char *argv[])\n     process_init();\n \n     bool active = false;\n-    parse_options(&argc, &argv, &remotes, &unixctl_path, &run_command,\n-                  &sync_from, &sync_exclude, &active);\n+    parse_options(argc, argv, &db_filenames, &remotes, &unixctl_path,\n+                  &run_command, &sync_from, &sync_exclude, &active);\n     is_backup = sync_from && !active;\n \n     daemon_become_new_user(false);\n@@ -290,17 +292,6 @@ main(int argc, char *argv[])\n         ovs_fatal(errno, \"failed to create temporary file\");\n     }\n \n-    sset_init(&db_filenames);\n-    if (argc > 0) {\n-        for (i = 0; i < argc; i++) {\n-            sset_add(&db_filenames, argv[i]);\n-         }\n-    } else {\n-        char *default_db = xasprintf(\"%s/conf.db\", ovs_dbdir());\n-        sset_add(&db_filenames, default_db);\n-        free(default_db);\n-    }\n-\n     server_config.remotes = &remotes;\n     server_config.config_tmpfile = config_tmpfile;\n \n@@ -1463,8 +1454,9 @@ ovsdb_server_get_sync_status(struct unixctl_conn *conn, int argc OVS_UNUSED,\n }\n \n static void\n-parse_options(int *argcp, char **argvp[],\n-              struct sset *remotes, char **unixctl_pathp, char **run_command,\n+parse_options(int argc, char *argv[],\n+              struct sset *db_filenames, struct sset *remotes,\n+              char **unixctl_pathp, char **run_command,\n               char **sync_from, char **sync_exclude, bool *active)\n {\n     enum {\n@@ -1476,10 +1468,12 @@ parse_options(int *argcp, char **argvp[],\n         OPT_SYNC_FROM,\n         OPT_SYNC_EXCLUDE,\n         OPT_ACTIVE,\n+        OPT_NO_DBS,\n         VLOG_OPTION_ENUMS,\n         DAEMON_OPTION_ENUMS,\n         SSL_OPTION_ENUMS,\n     };\n+\n     static const struct option long_options[] = {\n         {\"remote\",      required_argument, NULL, OPT_REMOTE},\n         {\"unixctl\",     required_argument, NULL, OPT_UNIXCTL},\n@@ -1496,14 +1490,15 @@ parse_options(int *argcp, char **argvp[],\n         {\"sync-from\",   required_argument, NULL, OPT_SYNC_FROM},\n         {\"sync-exclude-tables\", required_argument, NULL, OPT_SYNC_EXCLUDE},\n         {\"active\", no_argument, NULL, OPT_ACTIVE},\n+        {\"no-dbs\", no_argument, NULL, OPT_NO_DBS},\n         {NULL, 0, NULL, 0},\n     };\n     char *short_options = ovs_cmdl_long_options_to_short_options(long_options);\n-    int argc = *argcp;\n-    char **argv = *argvp;\n+    bool add_default_db = true;\n \n     *sync_from = NULL;\n     *sync_exclude = NULL;\n+    sset_init(db_filenames);\n     sset_init(remotes);\n     for (;;) {\n         int c;\n@@ -1582,6 +1577,10 @@ parse_options(int *argcp, char **argvp[],\n             *active = true;\n             break;\n \n+        case OPT_NO_DBS:\n+            add_default_db = false;\n+            break;\n+\n         case '?':\n             exit(EXIT_FAILURE);\n \n@@ -1591,8 +1590,15 @@ parse_options(int *argcp, char **argvp[],\n     }\n     free(short_options);\n \n-    *argcp -= optind;\n-    *argvp += optind;\n+    argc -= optind;\n+    argv += optind;\n+    if (argc > 0) {\n+        for (int i = 0; i < argc; i++) {\n+            sset_add(db_filenames, argv[i]);\n+        }\n+    } else if (add_default_db) {\n+        sset_add_and_free(db_filenames, xasprintf(\"%s/conf.db\", ovs_dbdir()));\n+    }\n }\n \n static void\ndiff --git a/ovsdb/ovsdb.7.xml b/ovsdb/ovsdb.7.xml\nindex 8d9a6ec97caf..efd7a622e2e0 100644\n--- a/ovsdb/ovsdb.7.xml\n+++ b/ovsdb/ovsdb.7.xml\n@@ -461,11 +461,10 @@\n     OVSDB also supports online database schema conversion, for any of its\n     database service models.  To convert a database online, use\n     <code>ovsdb-client convert</code>.  The conversion is atomic, consistent,\n-    isolated, and durable.  The OVSDB protocol does not provide a way for the\n-    server to notify a client that a database's schema has changed, so\n-    currently <code>ovsdb-server</code> disconnects any clients connected when\n-    the conversion takes place.  Upon reconnction, clients will discover that\n-    the schema has changed.\n+    isolated, and durable.  <code>ovsdb-server</code> disconnects any clients\n+    connected when the conversion takes place (except clients that use the\n+    <code>set_db_change_aware</code> Open vSwitch extension RPC).  Upon\n+    reconnection, clients will discover that the schema has changed.\n   </p>\n \n   <p>\ndiff --git a/ovsdb/ovsdb.c b/ovsdb/ovsdb.c\nindex 19755e673861..89f530bcccfb 100644\n--- a/ovsdb/ovsdb.c\n+++ b/ovsdb/ovsdb.c\n@@ -27,6 +27,7 @@\n #include \"simap.h\"\n #include \"table.h\"\n #include \"transaction.h\"\n+#include \"trigger.h\"\n \n struct ovsdb_schema *\n ovsdb_schema_create(const char *name, const char *version, const char *cksum)\n@@ -162,7 +163,7 @@ root_set_size(const struct ovsdb_schema *schema)\n }\n \n struct ovsdb_error *\n-ovsdb_schema_from_json(struct json *json, struct ovsdb_schema **schemap)\n+ovsdb_schema_from_json(const struct json *json, struct ovsdb_schema **schemap)\n {\n     struct ovsdb_schema *schema;\n     const struct json *name, *tables, *version_json, *cksum;\n@@ -361,6 +362,29 @@ ovsdb_create(struct ovsdb_schema *schema)\n }\n \n void\n+ovsdb_replace(struct ovsdb *dst, struct ovsdb *src)\n+{\n+    /* Cancel monitors. */\n+    ovsdb_monitor_prereplace_db(dst);\n+\n+    /* Cancel triggers. */\n+    struct ovsdb_trigger *trigger, *next;\n+    LIST_FOR_EACH_SAFE (trigger, next, node, &dst->triggers) {\n+        ovsdb_trigger_prereplace_db(trigger);\n+    }\n+\n+    struct ovsdb_schema *tmp_schema = dst->schema;\n+    dst->schema = src->schema;\n+    src->schema = tmp_schema;\n+\n+    shash_swap(&dst->tables, &src->tables);\n+\n+    dst->rbac_role = ovsdb_get_table(dst, \"RBAC_Role\");\n+\n+    ovsdb_destroy(src);\n+}\n+\n+void\n ovsdb_destroy(struct ovsdb *db)\n {\n     if (db) {\ndiff --git a/ovsdb/ovsdb.h b/ovsdb/ovsdb.h\nindex 9d915f0f15ae..c3e8f2091e35 100644\n--- a/ovsdb/ovsdb.h\n+++ b/ovsdb/ovsdb.h\n@@ -45,7 +45,7 @@ void ovsdb_schema_destroy(struct ovsdb_schema *);\n struct ovsdb_error *ovsdb_schema_from_file(const char *file_name,\n                                            struct ovsdb_schema **)\n     OVS_WARN_UNUSED_RESULT;\n-struct ovsdb_error *ovsdb_schema_from_json(struct json *,\n+struct ovsdb_error *ovsdb_schema_from_json(const struct json *,\n                                            struct ovsdb_schema **)\n     OVS_WARN_UNUSED_RESULT;\n struct json *ovsdb_schema_to_json(const struct ovsdb_schema *);\n@@ -68,6 +68,7 @@ struct ovsdb {\n };\n \n struct ovsdb *ovsdb_create(struct ovsdb_schema *);\n+void ovsdb_replace(struct ovsdb *dst, struct ovsdb *src);\n void ovsdb_destroy(struct ovsdb *);\n \n void ovsdb_get_memory_usage(const struct ovsdb *, struct simap *usage);\ndiff --git a/ovsdb/transaction.c b/ovsdb/transaction.c\nindex e6e13f650070..e38a174109bb 100644\n--- a/ovsdb/transaction.c\n+++ b/ovsdb/transaction.c\n@@ -806,8 +806,14 @@ update_version(struct ovsdb_txn *txn OVS_UNUSED, struct ovsdb_txn_row *txn_row)\n     return NULL;\n }\n \n-static struct ovsdb_error *\n-ovsdb_txn_commit_(struct ovsdb_txn *txn, bool durable)\n+static bool\n+ovsdb_txn_is_empty(const struct ovsdb_txn *txn)\n+{\n+    return ovs_list_is_empty(&txn->txn_tables);\n+}\n+\n+struct ovsdb_error * OVS_WARN_UNUSED_RESULT\n+ovsdb_txn_start_commit(struct ovsdb_txn *txn)\n {\n     struct ovsdb_error *error;\n \n@@ -817,29 +823,25 @@ ovsdb_txn_commit_(struct ovsdb_txn *txn, bool durable)\n     if (error) {\n         return OVSDB_WRAP_BUG(\"can't happen\", error);\n     }\n-    if (ovs_list_is_empty(&txn->txn_tables)) {\n-        ovsdb_txn_abort(txn);\n+    if (ovsdb_txn_is_empty(txn)) {\n         return NULL;\n     }\n \n     /* Update reference counts and check referential integrity. */\n     error = update_ref_counts(txn);\n     if (error) {\n-        ovsdb_txn_abort(txn);\n         return error;\n     }\n \n     /* Delete unreferenced, non-root rows. */\n     error = for_each_txn_row(txn, collect_garbage);\n     if (error) {\n-        ovsdb_txn_abort(txn);\n         return OVSDB_WRAP_BUG(\"can't happen\", error);\n     }\n \n     /* Check maximum rows table constraints. */\n     error = check_max_rows(txn);\n     if (error) {\n-        ovsdb_txn_abort(txn);\n         return error;\n     }\n \n@@ -847,14 +849,12 @@ ovsdb_txn_commit_(struct ovsdb_txn *txn, bool durable)\n      * integrity. */\n     error = for_each_txn_row(txn, assess_weak_refs);\n     if (error) {\n-        ovsdb_txn_abort(txn);\n         return error;\n     }\n \n     /* Verify that the indexes will still be unique post-transaction. */\n     error = for_each_txn_row(txn, check_index_uniqueness);\n     if (error) {\n-        ovsdb_txn_abort(txn);\n         return error;\n     }\n \n@@ -864,9 +864,16 @@ ovsdb_txn_commit_(struct ovsdb_txn *txn, bool durable)\n         return OVSDB_WRAP_BUG(\"can't happen\", error);\n     }\n \n+    return NULL;\n+}\n+\n+struct ovsdb_error *\n+ovsdb_txn_finish_commit(struct ovsdb_txn *txn, bool durable)\n+{\n     /* Send the commit to each replica. */\n     if (txn->db->file) {\n-        error = ovsdb_file_commit(txn->db->file, txn, durable);\n+        struct ovsdb_error *error = ovsdb_file_commit(txn->db->file, txn,\n+                                                      durable);\n         if (error) {\n             ovsdb_txn_abort(txn);\n             return error;\n@@ -886,10 +893,12 @@ ovsdb_txn_commit_(struct ovsdb_txn *txn, bool durable)\n struct ovsdb_error *\n ovsdb_txn_commit(struct ovsdb_txn *txn, bool durable)\n {\n-   struct ovsdb_error *err;\n-\n-   PERF(__func__, err = ovsdb_txn_commit_(txn, durable));\n-   return err;\n+    struct ovsdb_error *error = ovsdb_txn_start_commit(txn);\n+    if (error || ovsdb_txn_is_empty(txn)) {\n+        ovsdb_txn_abort(txn);\n+        return error;\n+    }\n+    return ovsdb_txn_finish_commit(txn, durable);\n }\n \n void\ndiff --git a/ovsdb/transaction.h b/ovsdb/transaction.h\nindex 1ecd15a56a8d..f9b886411bf4 100644\n--- a/ovsdb/transaction.h\n+++ b/ovsdb/transaction.h\n@@ -26,6 +26,11 @@ struct uuid;\n \n struct ovsdb_txn *ovsdb_txn_create(struct ovsdb *);\n void ovsdb_txn_abort(struct ovsdb_txn *);\n+\n+struct ovsdb_error *ovsdb_txn_start_commit(struct ovsdb_txn *)\n+    OVS_WARN_UNUSED_RESULT;\n+struct ovsdb_error *ovsdb_txn_finish_commit(struct ovsdb_txn *, bool durable)\n+    OVS_WARN_UNUSED_RESULT;\n struct ovsdb_error *ovsdb_txn_commit(struct ovsdb_txn *, bool durable)\n     OVS_WARN_UNUSED_RESULT;\n \ndiff --git a/ovsdb/trigger.c b/ovsdb/trigger.c\nindex 1b960f5723ec..10b155f8bcb4 100644\n--- a/ovsdb/trigger.c\n+++ b/ovsdb/trigger.c\n@@ -19,41 +19,47 @@\n \n #include <limits.h>\n \n+#include \"file.h\"\n+#include \"log.h\"\n #include \"openvswitch/json.h\"\n #include \"jsonrpc.h\"\n #include \"ovsdb.h\"\n+#include \"ovsdb-error.h\"\n #include \"poll-loop.h\"\n #include \"server.h\"\n \n+\n static bool ovsdb_trigger_try(struct ovsdb_trigger *, long long int now);\n-static void ovsdb_trigger_complete(struct ovsdb_trigger *);\n+static void trigger_error(struct ovsdb_trigger *, struct ovsdb_error *);\n+static void trigger_success(struct ovsdb_trigger *, struct json *result);\n \n-void\n+bool\n ovsdb_trigger_init(struct ovsdb_session *session, struct ovsdb *db,\n                    struct ovsdb_trigger *trigger,\n-                   struct json *request, long long int now,\n-                   bool read_only, const char *role,\n-                   const char *id)\n+                   struct jsonrpc_msg *request, long long int now,\n+                   bool read_only, const char *role, const char *id)\n {\n+    ovs_assert(!strcmp(request->method, \"transact\") ||\n+               !strcmp(request->method, \"convert_db\"));\n     trigger->session = session;\n     trigger->db = db;\n     ovs_list_push_back(&trigger->db->triggers, &trigger->node);\n     trigger->request = request;\n-    trigger->result = NULL;\n+    trigger->reply = NULL;\n     trigger->created = now;\n     trigger->timeout_msec = LLONG_MAX;\n     trigger->read_only = read_only;\n     trigger->role = nullable_xstrdup(role);\n     trigger->id = nullable_xstrdup(id);\n-    ovsdb_trigger_try(trigger, now);\n+    return ovsdb_trigger_try(trigger, now);\n }\n \n void\n ovsdb_trigger_destroy(struct ovsdb_trigger *trigger)\n {\n     ovs_list_remove(&trigger->node);\n-    json_destroy(trigger->request);\n-    json_destroy(trigger->result);\n+    jsonrpc_msg_destroy(trigger->request);\n+    jsonrpc_msg_destroy(trigger->reply);\n     free(trigger->role);\n     free(trigger->id);\n }\n@@ -61,30 +67,53 @@ ovsdb_trigger_destroy(struct ovsdb_trigger *trigger)\n bool\n ovsdb_trigger_is_complete(const struct ovsdb_trigger *trigger)\n {\n-    return trigger->result != NULL;\n+    return trigger->reply != NULL;\n }\n \n-struct json *\n-ovsdb_trigger_steal_result(struct ovsdb_trigger *trigger)\n+struct jsonrpc_msg *\n+ovsdb_trigger_steal_reply(struct ovsdb_trigger *trigger)\n {\n-    struct json *result = trigger->result;\n-    trigger->result = NULL;\n-    return result;\n+    struct jsonrpc_msg *reply = trigger->reply;\n+    trigger->reply = NULL;\n+    return reply;\n }\n \n void\n+ovsdb_trigger_prereplace_db(struct ovsdb_trigger *trigger)\n+{\n+    if (!strcmp(trigger->request->method, \"transact\")) {\n+        trigger_error(trigger, ovsdb_error(\"canceled\", NULL));\n+    } else if (!strcmp(trigger->request->method, \"convert_db\")) {\n+        /* We don't cancel convert_db requests when a database is being\n+         * replaced for two reasons.  First, we expect the administrator to do\n+         * some kind of sensible synchronization on conversion requests, that\n+         * is, it only really makes sense for the admin to do a single\n+         * conversion at a time at a scheduled point.  Second, if we did then\n+         * every convert_db request would end up getting canceled since\n+         * convert_db itself causes the database to be replaced. */\n+    } else {\n+        OVS_NOT_REACHED();\n+    }\n+}\n+\n+bool\n ovsdb_trigger_run(struct ovsdb *db, long long int now)\n {\n     struct ovsdb_trigger *t, *next;\n-    bool run_triggers;\n \n-    run_triggers = db->run_triggers;\n+    bool run_triggers = db->run_triggers;\n     db->run_triggers = false;\n+\n+    bool disconnect_all = false;\n+\n     LIST_FOR_EACH_SAFE (t, next, node, &db->triggers) {\n         if (run_triggers || now - t->created >= t->timeout_msec) {\n-            ovsdb_trigger_try(t, now);\n+            if (ovsdb_trigger_try(t, now)) {\n+                disconnect_all = true;\n+            }\n         }\n     }\n+    return disconnect_all;\n }\n \n void\n@@ -117,22 +146,71 @@ ovsdb_trigger_wait(struct ovsdb *db, long long int now)\n static bool\n ovsdb_trigger_try(struct ovsdb_trigger *t, long long int now)\n {\n-    t->result = ovsdb_execute(t->db, t->session,\n-                              t->request, t->read_only,\n-                              t->role, t->id,\n-                              now - t->created, &t->timeout_msec);\n-    if (t->result) {\n-        ovsdb_trigger_complete(t);\n+    if (!strcmp(t->request->method, \"transact\")) {\n+        struct json *result = ovsdb_execute(t->db, t->session,\n+                                            t->request->params, t->read_only,\n+                                            t->role, t->id, now - t->created,\n+                                            &t->timeout_msec);\n+        if (result) {\n+            trigger_success(t, result);\n+        }\n+        return false;\n+    } else if (!strcmp(t->request->method, \"convert_db\")) {\n+        /* Validate parameters. */\n+        const struct json *params = t->request->params;\n+        if (params->type != JSON_ARRAY || params->u.array.n != 2) {\n+            trigger_error(t, ovsdb_syntax_error(params, NULL,\n+                                                \"array expected\"));\n+            return false;\n+        }\n+\n+        /* Parse new schema and make a converted copy. */\n+        const struct json *new_schema_json = params->u.array.elems[1];\n+        struct ovsdb_schema *new_schema;\n+        struct ovsdb_error *error = ovsdb_schema_from_json(new_schema_json,\n+                                                           &new_schema);\n+        if (!error && strcmp(new_schema->name, t->db->schema->name)) {\n+            error = ovsdb_error(\n+                \"invalid parameters\",\n+                \"new schema name (%s) does not match database name (%s)\",\n+                new_schema->name, t->db->schema->name);\n+        }\n+        if (!error) {\n+            error = ovsdb_file_convert(t->db->file, new_schema);\n+        }\n+        ovsdb_schema_destroy(new_schema);\n+        if (error) {\n+            trigger_error(t, error);\n+            return false;\n+        }\n+\n+        trigger_success(t, json_object_create());\n         return true;\n     } else {\n-        return false;\n+        OVS_NOT_REACHED();\n     }\n }\n \n static void\n-ovsdb_trigger_complete(struct ovsdb_trigger *t)\n+ovsdb_trigger_complete(struct ovsdb_trigger *t, struct jsonrpc_msg *reply)\n {\n-    ovs_assert(t->result != NULL);\n+    ovs_assert(reply && !t->reply);\n+    t->reply = reply;\n     ovs_list_remove(&t->node);\n     ovs_list_push_back(&t->session->completions, &t->node);\n }\n+\n+static void\n+trigger_error(struct ovsdb_trigger *t, struct ovsdb_error *error)\n+{\n+    struct jsonrpc_msg *reply = jsonrpc_create_error(\n+        ovsdb_error_to_json_free(error), t->request->id);\n+    ovsdb_trigger_complete(t, reply);\n+}\n+\n+static void\n+trigger_success(struct ovsdb_trigger *t, struct json *result)\n+{\n+    struct jsonrpc_msg *reply = jsonrpc_create_reply(result, t->request->id);\n+    ovsdb_trigger_complete(t, reply);\n+}\ndiff --git a/ovsdb/trigger.h b/ovsdb/trigger.h\nindex 90246a4a42bd..d9df97f31222 100644\n--- a/ovsdb/trigger.h\n+++ b/ovsdb/trigger.h\n@@ -25,8 +25,8 @@ struct ovsdb_trigger {\n     struct ovsdb *db;           /* Database on which trigger acts. */\n     struct ovs_list node;       /* !result: in db->triggers;\n                                  * result: in session->completions. */\n-    struct json *request;       /* Database request. */\n-    struct json *result;        /* Result (null if none yet). */\n+    struct jsonrpc_msg *request; /* Database request. */\n+    struct jsonrpc_msg *reply;   /* Result (null if none yet).. */\n     long long int created;      /* Time created. */\n     long long int timeout_msec; /* Max wait duration. */\n     bool read_only;             /* Database is in read only mode. */\n@@ -34,17 +34,18 @@ struct ovsdb_trigger {\n     char *id;                   /* ID, for role-based access controls. */\n };\n \n-void ovsdb_trigger_init(struct ovsdb_session *, struct ovsdb *,\n+bool ovsdb_trigger_init(struct ovsdb_session *, struct ovsdb *,\n                         struct ovsdb_trigger *,\n-                        struct json *request, long long int now,\n-                        bool read_only, const char *role,\n-                        const char *id);\n+                        struct jsonrpc_msg *request, long long int now,\n+                        bool read_only, const char *role, const char *id);\n void ovsdb_trigger_destroy(struct ovsdb_trigger *);\n \n bool ovsdb_trigger_is_complete(const struct ovsdb_trigger *);\n-struct json *ovsdb_trigger_steal_result(struct ovsdb_trigger *);\n+struct jsonrpc_msg *ovsdb_trigger_steal_reply(struct ovsdb_trigger *);\n \n-void ovsdb_trigger_run(struct ovsdb *, long long int now);\n+void ovsdb_trigger_prereplace_db(struct ovsdb_trigger *);\n+\n+bool ovsdb_trigger_run(struct ovsdb *, long long int now);\n void ovsdb_trigger_wait(struct ovsdb *, long long int now);\n \n #endif /* ovsdb/trigger.h */\ndiff --git a/tests/ovsdb-log.at b/tests/ovsdb-log.at\nindex fb9af12c657d..d295cedd639f 100644\n--- a/tests/ovsdb-log.at\n+++ b/tests/ovsdb-log.at\n@@ -46,7 +46,7 @@ AT_CHECK(\n file: read: {\"x\":1}\n ]], [ignore])\n AT_CHECK(\n-  [test-ovsdb log-io file create read], [1],\n+  [test-ovsdb log-io file create-excl read], [1],\n   [], [test-ovsdb: I/O error: file: create failed (File exists)\n ])\n AT_CHECK([test -f .file.~lock~])\ndiff --git a/tests/ovsdb-monitor.at b/tests/ovsdb-monitor.at\nindex 256868b0248f..e12f4dc326db 100644\n--- a/tests/ovsdb-monitor.at\n+++ b/tests/ovsdb-monitor.at\n@@ -29,11 +29,11 @@ m4_define([OVSDB_CHECK_MONITOR],\n    on_exit 'kill `cat ovsdb-server.pid`'\n    AT_CAPTURE_FILE([ovsdb-client-log])\n    if test \"$IS_WIN32\" = \"yes\"; then\n-     AT_CHECK([ovsdb-client -vjsonrpc --pidfile --log-file=\"`pwd`\"/ovsdb-client-log -d json monitor --format=csv unix:socket $4 $5 $8 > output 2>/dev/null &],\n+     AT_CHECK([ovsdb-client -vjsonrpc --detach --pidfile --log-file=\"`pwd`\"/ovsdb-client-log -d json monitor --format=csv unix:socket $4 $5 $8 > output],\n               [0], [ignore], [ignore])\n      sleep 1\n    else\n-     AT_CHECK([ovsdb-client -vjsonrpc --detach --no-chdir --pidfile --log-file=\"`pwd`\"/ovsdb-client-log -d json monitor --format=csv unix:socket $4 $5 $8 > output],\n+     AT_CHECK([ovsdb-client -vjsonrpc --detach --no-chdir --pidfile --log-file=\"`pwd`\"/ovsdb-client-log -d json monitor --format=csv unix:socket $4 $5 $8 > output 2>/dev/null],\n             [0], [ignore], [ignore])\n    fi\n    on_exit 'kill `cat ovsdb-client.pid`'\ndiff --git a/tests/ovsdb-server.at b/tests/ovsdb-server.at\nindex 795427c5b1e5..280e37fd8525 100644\n--- a/tests/ovsdb-server.at\n+++ b/tests/ovsdb-server.at\n@@ -176,7 +176,7 @@ AT_CHECK([ovsdb-tool create db1 schema1], [0], [ignore], [ignore])\n AT_CHECK([ovsdb-tool create db2 schema2], [0], [ignore], [ignore])\n \n # Start ovsdb-server with just a single database - db1.\n-AT_CHECK([ovsdb-server --detach --no-chdir --pidfile --remote=punix:db.sock db1], [0])\n+AT_CHECK([ovsdb-server -vfile -vvlog:off --log-file --detach --no-chdir --pidfile --remote=punix:db.sock db1], [0])\n CHECK_DBS([ordinals\n ])\n \n@@ -185,8 +185,8 @@ AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/remove-db ordinals], [0])\n CHECK_DBS([])\n \n # Start monitoring processes.\n-AT_CHECK([ovsdb-client --detach --pidfile=ovsdb-client-1.pid --no-headings monitor _Server Database name > db-changes-unaware])\n-AT_CHECK([ovsdb-client --detach --pidfile=ovsdb-client-2.pid --db-change-aware --no-headings monitor _Server Database name > db-changes-aware])\n+AT_CHECK([ovsdb-client --detach --pidfile=ovsdb-client-1.pid --no-db-change-aware --no-headings monitor _Server Database name > db-change-unaware 2>/dev/null])\n+AT_CHECK([ovsdb-client --detach --pidfile=ovsdb-client-2.pid --db-change-aware --no-headings monitor _Server Database name > db-change-aware 2>/dev/null])\n \n # Add the first database back.\n AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/add-db db1], [0])\n@@ -258,7 +258,7 @@ CHECK_DBS([constraints\n AT_CHECK([ovsdb-client list-tables unix:db.sock constraints], [0], [ignore], [ignore])\n \n # Check the monitoring results.\n-AT_CHECK([${PERL} $srcdir/uuidfilt.pl db-changes-aware], [0], [dnl\n+AT_CHECK([${PERL} $srcdir/uuidfilt.pl db-change-aware], [0], [dnl\n <0> initial _Server\n \n <1> insert ordinals\n@@ -271,7 +271,7 @@ AT_CHECK([${PERL} $srcdir/uuidfilt.pl db-changes-aware], [0], [dnl\n \n <3> insert constraints\n ])\n-AT_CHECK([${PERL} $srcdir/uuidfilt.pl db-changes-unaware], [0], [dnl\n+AT_CHECK([${PERL} $srcdir/uuidfilt.pl db-change-unaware], [0], [dnl\n <0> initial _Server\n ])\n \n@@ -781,6 +781,239 @@ _uuid                                name  number\n OVSDB_SERVER_SHUTDOWN\n AT_CLEANUP\n \n+AT_SETUP([schema conversion online -- removing a column])\n+AT_KEYWORDS([ovsdb server convert])\n+on_exit 'kill `cat *.pid`'\n+ordinal_schema > schema\n+AT_DATA([new-schema], \n+  [[{\"name\": \"ordinals\",\n+     \"tables\": {\n+       \"ordinals\": {\n+         \"columns\": {\n+           \"number\": {\"type\": \"integer\"}}}}}\n+]])\n+touch .db.~lock~\n+AT_CHECK([ovsdb-tool create db schema])\n+dnl Put some data in the database.\n+AT_CHECK(\n+  [[for pair in 'zero 0' 'one 1' 'two 2' 'three 3' 'four 4' 'five 5'; do\n+      set -- $pair\n+      ovsdb-tool transact db '\n+        [\"ordinals\",\n+         {\"op\": \"insert\",\n+          \"table\": \"ordinals\",\n+          \"row\": {\"name\": \"'$1'\", \"number\": '$2'}},\n+         {\"op\": \"comment\",\n+          \"comment\": \"add row for '\"$pair\"'\"}]'\n+    done | ${PERL} $srcdir/uuidfilt.pl]], [0],\n+[[[{\"uuid\":[\"uuid\",\"<0>\"]},{}]\n+[{\"uuid\":[\"uuid\",\"<1>\"]},{}]\n+[{\"uuid\":[\"uuid\",\"<2>\"]},{}]\n+[{\"uuid\":[\"uuid\",\"<3>\"]},{}]\n+[{\"uuid\":[\"uuid\",\"<4>\"]},{}]\n+[{\"uuid\":[\"uuid\",\"<5>\"]},{}]\n+]], [ignore])\n+\n+dnl Start the database server.\n+AT_CHECK([ovsdb-server -vfile -vvlog:off --detach --no-chdir --pidfile --log-file --remote=punix:db.sock db], [0])\n+AT_CAPTURE_FILE([ovsdb-server.log])\n+\n+dnl Start two monitors on the 'ordinals' db, one that is database\n+dnl change aware and one that is not.\n+AT_CHECK([ovsdb-client -vfile -vvlog:off --detach --pidfile=monitor-ordinals-aware.pid --log-file=monitor-ordinals-aware.log --db-change-aware --no-headings monitor ordinals ordinals number name > monitor-ordinals-aware.stdout 2> monitor-ordinals-aware.stderr])\n+AT_CAPTURE_FILE([monitor-ordinals-aware.stdout])\n+AT_CAPTURE_FILE([monitor-ordinals-aware.log])\n+AT_CAPTURE_FILE([monitor-ordinals-aware.stderr])\n+\n+AT_CHECK([ovsdb-client -vfile -vvlog:off --detach --pidfile=monitor-ordinals-unaware.pid --log-file=monitor-ordinals-unaware.log --no-db-change-aware --no-headings monitor ordinals ordinals number name > monitor-ordinals-unaware.stdout 2> monitor-ordinals-unaware.stderr])\n+AT_CAPTURE_FILE([monitor-ordinals-unaware.stdout])\n+AT_CAPTURE_FILE([monitor-ordinals-unaware.log])\n+AT_CAPTURE_FILE([monitor-ordinals-unaware.stderr])\n+\n+dnl Start two monitors on the '_Server' db, one that is database\n+dnl change aware and one that is not.\n+AT_CHECK([ovsdb-client -vfile -vvlog:off --detach --pidfile=monitor-server-aware.pid --log-file=monitor-server-aware.log --db-change-aware --no-headings monitor _Server Database name > monitor-server-aware.stdout 2> monitor-server-aware.stderr])\n+AT_CAPTURE_FILE([monitor-server-aware.stdout])\n+AT_CAPTURE_FILE([monitor-server-aware.log])\n+AT_CAPTURE_FILE([monitor-server-aware.stderr])\n+\n+AT_CHECK([ovsdb-client -vfile -vvlog:off --detach --pidfile=monitor-server-unaware.pid --log-file=monitor-server-unaware.log --no-db-change-aware --no-headings monitor _Server Database name > monitor-server-unaware.stdout 2> monitor-server-unaware.stderr])\n+AT_CAPTURE_FILE([monitor-server-unaware.stdout])\n+AT_CAPTURE_FILE([monitor-server-unaware.log])\n+AT_CAPTURE_FILE([monitor-server-unaware.stderr])\n+\n+dnl Start two long-running transactions (triggers) on the 'ordinals' db,\n+dnl one that is database change aware and one that is not.\n+ordinals_txn='[[\"ordinals\",\n+\t\t{\"op\": \"wait\",\n+\t\t \"table\": \"ordinals\",\n+\t\t \"where\": [[\"name\", \"==\", \"seven\"]],\n+\t\t \"columns\": [\"name\", \"number\"],\n+\t\t \"rows\": [],\n+\t\t \"until\": \"!=\"}]]'\n+AT_CHECK([ovsdb-client -vfile -vvlog:off --detach --pidfile=trigger-ordinals-aware.pid --log-file=trigger-ordinals-aware.log --db-change-aware transact \"$ordinals_txn\"  > trigger-ordinals-aware.stdout 2> trigger-ordinals-aware.stderr])\n+AT_CAPTURE_FILE([trigger-ordinals-aware.stdout])\n+AT_CAPTURE_FILE([trigger-ordinals-aware.log])\n+AT_CAPTURE_FILE([trigger-ordinals-aware.stderr])\n+\n+AT_CHECK([ovsdb-client -vfile -vvlog:off --detach --pidfile=trigger-ordinals-unaware.pid --log-file=trigger-ordinals-unaware.log --no-db-change-aware transact  \"$ordinals_txn\" > trigger-ordinals-unaware.stdout 2> trigger-ordinals-unaware.stderr])\n+AT_CAPTURE_FILE([trigger-ordinals-unaware.stdout])\n+AT_CAPTURE_FILE([trigger-ordinals-unaware.log])\n+AT_CAPTURE_FILE([trigger-ordinals-unaware.stderr])\n+\n+dnl Start two long-running transactions (triggers) on the _Server db,\n+dnl one that is database change aware and one that is not.\n+server_txn='[[\"_Server\",\n+\t      {\"op\": \"wait\",\n+\t       \"table\": \"Database\",\n+\t       \"where\": [[\"name\", \"==\", \"xyzzy\"]],\n+\t       \"columns\": [\"name\"],\n+\t       \"rows\": [],\n+\t       \"until\": \"!=\"}]]'\n+AT_CHECK([ovsdb-client -vfile -vvlog:off --detach --pidfile=trigger-server-aware.pid --log-file=trigger-server-aware.log --db-change-aware transact \"$server_txn\"  > trigger-server-aware.stdout 2> trigger-server-aware.stderr])\n+AT_CAPTURE_FILE([trigger-server-aware.stdout])\n+AT_CAPTURE_FILE([trigger-server-aware.log])\n+AT_CAPTURE_FILE([trigger-server-aware.stderr])\n+\n+AT_CHECK([ovsdb-client -vfile -vvlog:off --detach --pidfile=trigger-server-unaware.pid --log-file=trigger-server-unaware.log --no-db-change-aware transact  \"$server_txn\" > trigger-server-unaware.stdout 2> trigger-server-unaware.stderr])\n+AT_CAPTURE_FILE([trigger-server-unaware.stdout])\n+AT_CAPTURE_FILE([trigger-server-unaware.log])\n+AT_CAPTURE_FILE([trigger-server-unaware.stderr])\n+\n+dnl Dump out and check the actual database contents.\n+AT_CHECK([ovsdb-client dump unix:db.sock ordinals], [0], [stdout])\n+AT_CHECK([${PERL} $srcdir/uuidfilt.pl stdout], [0], [dnl\n+ordinals table\n+_uuid                                name  number\n+------------------------------------ ----- ------\n+<0> five  5\n+<1> four  4\n+<2> one   1\n+<3> three 3\n+<4> two   2\n+<5> zero  0\n+])\n+\n+dnl Convert the database.\n+AT_CHECK([ovsdb-client convert new-schema])\n+\n+dnl Verify that the \"ordinals\" monitors behaved as they should have.\n+dnl Both should have exited, for different reasons.\n+dnl The db-aware _Server monitor should still be running, but not the unaware\n+dnl one.\n+for x in unaware aware; do\n+    OVS_WAIT_WHILE([test -e monitor-ordinals-$x.pid])\n+    AT_CHECK([sort -k 3 monitor-ordinals-$x.stdout | ${PERL} $srcdir/uuidfilt.pl], [0],\n+[<0> initial 0 zero\n+<1> initial 1 one\n+<2> initial 2 two\n+<3> initial 3 three\n+<4> initial 4 four\n+<5> initial 5 five\n+])\n+done\n+AT_CHECK([sed 's/.*: //' monitor-ordinals-unaware.stderr], [0], [receive failed (End of file)\n+])\n+AT_CHECK([sed 's/.*: //' monitor-ordinals-aware.stderr], [0], [ordinals database was removed\n+])\n+\n+dnl Verify that the _Server monitors behaved as they should have.\n+dnl The db-aware monitor should still be running, but not the unaware one.\n+for x in aware unaware; do\n+    AT_CHECK([sort -k 3 monitor-server-$x.stdout | ${PERL} $srcdir/uuidfilt.pl], [0],\n+[<0> initial _Server\n+<1> initial ordinals\n+])\n+done\n+OVS_WAIT_WHILE([test -e monitor-server-unaware.pid])\n+AT_CHECK([sed 's/.*: //' monitor-ordinals-unaware.stderr], [0], [receive failed (End of file)\n+])\n+AT_CHECK([test -e monitor-server-aware.pid])\n+\n+dnl Verify that the \"ordinals\" triggers behaved as they should have:\n+dnl Both should have exited, for different reasons.\n+for x in unaware aware; do\n+    OVS_WAIT_WHILE([test -e trigger-ordinals-$x.pid])\n+    AT_CHECK([cat trigger-ordinals-$x.stdout])\n+done\n+AT_CHECK([cat trigger-ordinals-unaware.stderr], [0], [ovsdb-client: transaction failed (End of file)\n+])\n+AT_CHECK([cat trigger-ordinals-aware.stderr], [0], [ovsdb-client: transaction returned error: {\"error\":\"canceled\"}\n+])\n+\n+dnl Verify that the _Server triggers behaved as they should have:\n+dnl The db-aware trigger should still be waiting, but not the unaware one.\n+for x in aware unaware; do\n+    AT_CHECK([cat trigger-server-$x.stdout])\n+done\n+OVS_WAIT_WHILE([test -e trigger-server-unaware.pid])\n+AT_CHECK([sed 's/.*: //' trigger-ordinals-unaware.stderr], [0], [transaction failed (End of file)\n+])\n+AT_CHECK([test -e trigger-server-aware.pid])\n+\n+dnl We can't fully re-check the contents of the database log, because the\n+dnl order of the records is not predictable, but there should only be 4 lines\n+dnl in it now.\n+AT_CAPTURE_FILE([db])\n+AT_CHECK([test `wc -l < db` -eq 4])\n+dnl And check that the dumped data is the same except for the removed column:\n+AT_CHECK([ovsdb-client dump unix:db.sock ordinals | ${PERL} $srcdir/uuidfilt.pl], [0], [dnl\n+ordinals table\n+_uuid                                number\n+------------------------------------ ------\n+<0> 0\n+<1> 1\n+<2> 2\n+<3> 3\n+<4> 4\n+<5> 5\n+])\n+dnl Now check that the converted database is still online and can be modified,\n+dnl then check that the database log has one more record and that the data\n+dnl is as expected.\n+AT_CHECK(\n+  [[ovsdb-client transact '\n+     [\"ordinals\",\n+      {\"op\": \"insert\",\n+       \"table\": \"ordinals\",\n+       \"row\": {\"number\": 6}},\n+      {\"op\": \"comment\",\n+       \"comment\": \"add row for 6\"}]' | ${PERL} $srcdir/uuidfilt.pl]], [0],\n+  [[[{\"uuid\":[\"uuid\",\"<0>\"]},{}]\n+]])\n+AT_CHECK([test `wc -l < db` -eq 6])\n+AT_CHECK([ovsdb-client dump unix:db.sock ordinals | ${PERL} $srcdir/uuidfilt.pl], [0], [dnl\n+ordinals table\n+_uuid                                number\n+------------------------------------ ------\n+<0> 0\n+<1> 1\n+<2> 2\n+<3> 3\n+<4> 4\n+<5> 5\n+<6> 6\n+])\n+dnl Now kill and restart the database server to ensure that the data is\n+dnl correct on disk as well as in memory.\n+OVS_APP_EXIT_AND_WAIT([ovsdb-server])\n+AT_CHECK([[ovsdb-server -vfile -vvlog:off --detach --no-chdir --pidfile --log-file --remote=punix:db.sock db]],\n+  [0])\n+AT_CHECK([ovsdb-client dump unix:db.sock ordinals | ${PERL} $srcdir/uuidfilt.pl], [0], [dnl\n+ordinals table\n+_uuid                                number\n+------------------------------------ ------\n+<0> 0\n+<1> 1\n+<2> 2\n+<3> 3\n+<4> 4\n+<5> 5\n+<6> 6\n+])\n+OVS_APP_EXIT_AND_WAIT([ovsdb-server])\n+AT_CLEANUP\n+\n AT_SETUP([ovsdb-server combines updates on backlogged connections])\n on_exit 'kill `cat *.pid`'\n \ndiff --git a/tests/test-ovsdb.c b/tests/test-ovsdb.c\nindex 9527c60d3620..1b0ec094c5e1 100644\n--- a/tests/test-ovsdb.c\n+++ b/tests/test-ovsdb.c\n@@ -321,6 +321,8 @@ do_log_io(struct ovs_cmdl_context *ctx)\n         mode = OVSDB_LOG_READ_WRITE;\n     } else if (!strcmp(mode_string, \"create\")) {\n         mode = OVSDB_LOG_CREATE;\n+    } else if (!strcmp(mode_string, \"create-excl\")) {\n+        mode = OVSDB_LOG_CREATE_EXCL;\n     } else {\n         ovs_fatal(0, \"unknown log-io open mode \\\"%s\\\"\", mode_string);\n     }\n@@ -1477,14 +1479,14 @@ struct test_trigger {\n static void\n do_trigger_dump(struct test_trigger *t, long long int now, const char *title)\n {\n-    struct json *result;\n+    struct jsonrpc_msg *reply;\n     char *s;\n \n-    result = ovsdb_trigger_steal_result(&t->trigger);\n-    s = json_to_string(result, JSSF_SORT);\n+    reply = ovsdb_trigger_steal_reply(&t->trigger);\n+    s = json_to_string(reply->result, JSSF_SORT);\n     printf(\"t=%lld: trigger %d (%s): %s\\n\", now, t->number, title, s);\n     free(s);\n-    json_destroy(result);\n+    jsonrpc_msg_destroy(reply);\n     ovsdb_trigger_destroy(&t->trigger);\n     free(t);\n }\n@@ -1524,8 +1526,10 @@ do_trigger(struct ovs_cmdl_context *ctx)\n             json_destroy(params);\n         } else {\n             struct test_trigger *t = xmalloc(sizeof *t);\n-            ovsdb_trigger_init(&session, db, &t->trigger, params, now, false,\n-                               NULL, NULL);\n+            ovsdb_trigger_init(&session, db, &t->trigger,\n+                               jsonrpc_create_request(\"transact\", params,\n+                                                      NULL),\n+                               now, false, NULL, NULL);\n             t->number = number++;\n             if (ovsdb_trigger_is_complete(&t->trigger)) {\n                 do_trigger_dump(t, now, \"immediate\");\n",
    "prefixes": [
        "ovs-dev",
        "RFC",
        "46/52"
    ]
}