Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/patches/815900/?format=api
{ "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" ] }