From patchwork Mon Jan 1 05:16:36 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ben Pfaff X-Patchwork-Id: 854296 X-Patchwork-Delegate: jpettit@nicira.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (mailfrom) smtp.mailfrom=openvswitch.org (client-ip=140.211.169.12; helo=mail.linuxfoundation.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=) Received: from mail.linuxfoundation.org (mail.linuxfoundation.org [140.211.169.12]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 3z95F83fBhz9t84 for ; Mon, 1 Jan 2018 16:23:08 +1100 (AEDT) Received: from mail.linux-foundation.org (localhost [127.0.0.1]) by mail.linuxfoundation.org (Postfix) with ESMTP id 21A8DCFB; Mon, 1 Jan 2018 05:17:13 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@mail.linuxfoundation.org Received: from smtp1.linuxfoundation.org (smtp1.linux-foundation.org [172.17.192.35]) by mail.linuxfoundation.org (Postfix) with ESMTPS id AF96FCE0 for ; Mon, 1 Jan 2018 05:17:09 +0000 (UTC) X-Greylist: domain auto-whitelisted by SQLgrey-1.7.6 Received: from relay2-d.mail.gandi.net (relay2-d.mail.gandi.net [217.70.183.194]) by smtp1.linuxfoundation.org (Postfix) with ESMTPS id 53EAB14D for ; Mon, 1 Jan 2018 05:17:05 +0000 (UTC) X-Originating-IP: 173.228.112.64 Received: from sigabrt.gateway.sonic.net (173-228-112-64.dsl.dynamic.fusionbroadband.com [173.228.112.64]) (Authenticated sender: blp@ovn.org) by relay2-d.mail.gandi.net (Postfix) with ESMTPSA id A8D8FC5A46; Mon, 1 Jan 2018 06:17:02 +0100 (CET) From: Ben Pfaff To: dev@openvswitch.org Date: Sun, 31 Dec 2017 21:16:36 -0800 Message-Id: <20180101051640.13043-11-blp@ovn.org> X-Mailer: git-send-email 2.10.2 In-Reply-To: <20180101051640.13043-1-blp@ovn.org> References: <20180101051640.13043-1-blp@ovn.org> X-Spam-Status: No, score=-2.6 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_LOW autolearn=ham version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on smtp1.linux-foundation.org Cc: Ben Pfaff Subject: [ovs-dev] [PATCH 11/15] ovsdb: Add support for online schema conversion. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.12 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: ovs-dev-bounces@openvswitch.org Errors-To: ovs-dev-bounces@openvswitch.org With this change, "ovsdb-client convert" can be used to convert a database from one schema to another without taking the database offline. This can be useful to minimize downtime for a database during a software upgrade. Signed-off-by: Ben Pfaff Acked-by: Justin Pettit --- Documentation/ref/ovsdb-server.7.rst | 35 +++++ Documentation/ref/ovsdb.7.rst | 11 +- NEWS | 2 + lib/ovsdb-data.c | 13 ++ lib/ovsdb-data.h | 6 + ovsdb/file.c | 135 +++++++++++++++++- ovsdb/file.h | 4 + ovsdb/jsonrpc-server.c | 120 +++++++--------- ovsdb/jsonrpc-server.h | 3 +- ovsdb/monitor.c | 19 ++- ovsdb/monitor.h | 2 + ovsdb/ovsdb-client.1.in | 41 +++++- ovsdb/ovsdb-client.c | 95 ++++++++++--- ovsdb/ovsdb-server.c | 56 ++++---- ovsdb/ovsdb.c | 26 +++- ovsdb/ovsdb.h | 3 +- ovsdb/transaction.c | 37 +++-- ovsdb/transaction.h | 5 + ovsdb/trigger.c | 142 +++++++++++++++---- ovsdb/trigger.h | 17 +-- tests/ovsdb-monitor.at | 4 +- tests/ovsdb-server.at | 263 +++++++++++++++++++++++++++++++++++ tests/test-ovsdb.c | 14 +- 23 files changed, 870 insertions(+), 183 deletions(-) diff --git a/Documentation/ref/ovsdb-server.7.rst b/Documentation/ref/ovsdb-server.7.rst index 2ed392feed1f..e3a8ccc61399 100644 --- a/Documentation/ref/ovsdb-server.7.rst +++ b/Documentation/ref/ovsdb-server.7.rst @@ -414,6 +414,41 @@ The reply is always the same:: "error": null "id": same "id" as request +4.1.17 Schema Conversion +------------------------ + +Open vSwitch 2.9 adds a new JSON-RPC request to convert an online database from +one schema to another. The request contains the following members:: + + "method": "convert" + "params": [] + "id": + +Upon receipt, the server converts the database named in to +that schema. The conversion is atomic, consistent, isolated, and durable. The +data in the database must be valid when interpreted under , +with only one exception: data for tables and columns that do not exist in the +new schema are ignored. Columns that exist in but not in the +database are set to their default values. All of the new schema's constraints +apply in full. + +If the conversion is successful, the server notifies clients that use the +``set_db_change_aware`` RPC introduced in Open vSwitch 2.9 and cancels their +outstanding transactions and monitors. The server disconnects other clients, +enabling them to notice the change when they reconnect. The server sends the +following reply:: + + "result": {} + "error": null + "id": same "id" as request + +If the conversion fails, then the server sends an error reply in the following +form:: + + "result": null + "error": [] + "id": same "id" as request + 5.1 Notation ------------ diff --git a/Documentation/ref/ovsdb.7.rst b/Documentation/ref/ovsdb.7.rst index 25a6e5fc36ed..6adef73826e8 100644 --- a/Documentation/ref/ovsdb.7.rst +++ b/Documentation/ref/ovsdb.7.rst @@ -367,10 +367,17 @@ active-backup database, first stop the database server or servers, then use ``ovsdb-tool convert`` to convert it to the new schema, and then restart the database server. +OVSDB also supports online database schema conversion. +To convert a database online, use ``ovsdb-client convert``. +The conversion is atomic, consistent, isolated, and durable. ``ovsdb-server`` +disconnects any clients connected when the conversion takes place (except +clients that use the ``set_db_change_aware`` Open vSwitch extension RPC). Upon +reconnection, clients will discover that the schema has changed. + Schema versions and checksums (see Schemas_ above) can give hints about whether a database needs to be converted to a new schema. If there is any question, -though, the ``needs-conversion`` command on ``ovsdb-tool`` can provide a -definitive answer. +though, the ``needs-conversion`` command on ``ovsdb-tool`` and ``ovsdb-client`` +can provide a definitive answer. Working with Database History ----------------------------- diff --git a/NEWS b/NEWS index b697e4968072..dfc2fb7728a4 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,8 @@ Post-v2.8.0 * New high-level documentation in ovsdb(7). * New file format documentation for developers in ovsdb(5). * Protocol documentation moved from ovsdb-server(1) to ovsdb-server(7). + * ovsdb-server now supports online schema conversion via + "ovsdb-client convert". * ovsdb-server now always hosts a built-in database named _Server. See ovsdb-server(5) for more details. * ovsdb-client: New "get-schema-cksum" and "query" commands. diff --git a/lib/ovsdb-data.c b/lib/ovsdb-data.c index 87d8effd1d67..69122dc10432 100644 --- a/lib/ovsdb-data.c +++ b/lib/ovsdb-data.c @@ -1684,6 +1684,19 @@ ovsdb_datum_from_smap(struct ovsdb_datum *datum, const struct smap *smap) ovsdb_datum_sort_unique(datum, OVSDB_TYPE_STRING, OVSDB_TYPE_STRING); } +struct ovsdb_error * OVS_WARN_UNUSED_RESULT +ovsdb_datum_convert(struct ovsdb_datum *dst, + const struct ovsdb_type *dst_type, + const struct ovsdb_datum *src, + const struct ovsdb_type *src_type) +{ + struct json *json = ovsdb_datum_to_json(src, src_type); + struct ovsdb_error *error = ovsdb_datum_from_json(dst, dst_type, json, + NULL); + json_destroy(json); + return error; +} + static uint32_t hash_atoms(enum ovsdb_atomic_type type, const union ovsdb_atom *atoms, unsigned int n, uint32_t basis) diff --git a/lib/ovsdb-data.h b/lib/ovsdb-data.h index c842fe28fc1a..c5a80ee39fd6 100644 --- a/lib/ovsdb-data.h +++ b/lib/ovsdb-data.h @@ -192,6 +192,12 @@ void ovsdb_datum_to_bare(const struct ovsdb_datum *, void ovsdb_datum_from_smap(struct ovsdb_datum *, const struct smap *); +struct ovsdb_error *ovsdb_datum_convert(struct ovsdb_datum *dst, + const struct ovsdb_type *dst_type, + const struct ovsdb_datum *src, + const struct ovsdb_type *src_type) + OVS_WARN_UNUSED_RESULT; + /* Comparison. */ uint32_t ovsdb_datum_hash(const struct ovsdb_datum *, const struct ovsdb_type *, uint32_t basis); diff --git a/ovsdb/file.c b/ovsdb/file.c index 4aafb3be8ab4..dadb988d3088 100644 --- a/ovsdb/file.c +++ b/ovsdb/file.c @@ -566,22 +566,31 @@ ovsdb_file_txn_annotate(struct json *json, const char *comment) return json; } -struct ovsdb_error * -ovsdb_file_commit(struct ovsdb_file *file, - const struct ovsdb_txn *txn, bool durable) +/* Returns 'txn' transformed into the JSON format that is used in OVSDB files. + * (But the caller must use ovsdb_file_txn_annotate() to add the _comment the + * _date members.) If 'txn' doesn't actually change anything, returns NULL */ +static struct json * +ovsdb_file_txn_to_json(const struct ovsdb_txn *txn) { struct ovsdb_file_txn ftxn; - struct ovsdb_error *error; ovsdb_file_txn_init(&ftxn); ovsdb_txn_for_each_change(txn, ovsdb_file_change_cb, &ftxn); - if (!ftxn.json) { + return ftxn.json; +} + +struct ovsdb_error * +ovsdb_file_commit(struct ovsdb_file *file, + const struct ovsdb_txn *txn, bool durable) +{ + struct json *txn_json = ovsdb_file_txn_to_json(txn); + if (!txn_json) { /* Nothing to commit. */ return NULL; } - error = ovsdb_file_txn_commit(ftxn.json, ovsdb_txn_get_comment(txn), - durable, file->log); + struct ovsdb_error *error = ovsdb_file_txn_commit( + txn_json, ovsdb_txn_get_comment(txn), durable, file->log); if (error) { return error; } @@ -844,3 +853,115 @@ ovsdb_file_txn_commit(struct json *json, const char *comment, return NULL; } + +static struct ovsdb_error * OVS_WARN_UNUSED_RESULT +ovsdb_convert_table(struct ovsdb_txn *txn, + const struct ovsdb_table *src_table, + struct ovsdb_table *dst_table) +{ + const struct ovsdb_row *src_row; + HMAP_FOR_EACH (src_row, hmap_node, &src_table->rows) { + struct ovsdb_row *dst_row = ovsdb_row_create(dst_table); + *ovsdb_row_get_uuid_rw(dst_row) = *ovsdb_row_get_uuid(src_row); + + struct shash_node *node; + SHASH_FOR_EACH (node, &src_table->schema->columns) { + const struct ovsdb_column *src_column = node->data; + if (src_column->index == OVSDB_COL_UUID || + src_column->index == OVSDB_COL_VERSION) { + continue; + } + + const struct ovsdb_column *dst_column + = shash_find_data(&dst_table->schema->columns, + src_column->name); + if (!dst_column) { + continue; + } + + struct ovsdb_error *error = ovsdb_datum_convert( + &dst_row->fields[dst_column->index], &dst_column->type, + &src_row->fields[src_column->index], &src_column->type); + if (error) { + ovsdb_row_destroy(dst_row); + return error; + } + } + + ovsdb_txn_row_insert(txn, dst_row); + } + return NULL; +} + +struct ovsdb_error * OVS_WARN_UNUSED_RESULT +ovsdb_file_convert(const struct ovsdb_file *file, + const struct ovsdb_schema *new_schema) +{ + struct ovsdb *new_db = ovsdb_create(ovsdb_schema_clone(new_schema)); + struct ovsdb_txn *txn = ovsdb_txn_create(new_db); + struct ovsdb_error *error = NULL; + + struct shash_node *node; + SHASH_FOR_EACH (node, &file->db->tables) { + const char *table_name = node->name; + const struct ovsdb_table *src_table = node->data; + struct ovsdb_table *dst_table = shash_find_data(&new_db->tables, + table_name); + if (!dst_table) { + continue; + } + + error = ovsdb_convert_table(txn, src_table, dst_table); + if (error) { + goto error; + } + } + + error = ovsdb_txn_start_commit(txn); + if (error) { + goto error; + } + + struct ovsdb_log *new; + error = ovsdb_log_replace_start(file->log, &new); + if (error) { + goto error; + } + + /* Write schema. */ + struct json *schema_json = ovsdb_schema_to_json(new_schema); + error = ovsdb_log_write(new, schema_json); + json_destroy(schema_json); + if (error) { + goto error; + } + + /* Write data. */ + struct json *txn_json = ovsdb_file_txn_to_json(txn); + if (txn_json) { + error = ovsdb_log_write(new, txn_json); + json_destroy(txn_json); + if (error) { + goto error; + } + } + + error = ovsdb_log_replace_commit(file->log, new); + if (error) { + goto error; + } + + error = ovsdb_txn_finish_commit(txn, true); + ovs_assert(!error); /* Can't happen. */ + + ovsdb_replace(file->db, new_db); + + return NULL; + +error: + ovsdb_destroy(new_db); + if (txn) { + ovsdb_txn_abort(txn); + } + return error; +} diff --git a/ovsdb/file.h b/ovsdb/file.h index a9ef0585b261..bc9b32cf6c33 100644 --- a/ovsdb/file.h +++ b/ovsdb/file.h @@ -51,4 +51,8 @@ void ovsdb_file_destroy(struct ovsdb_file *); struct json *ovsdb_file_txn_annotate(struct json *, const char *comment); +struct ovsdb_error *ovsdb_file_convert(const struct ovsdb_file *, + const struct ovsdb_schema *) + OVS_WARN_UNUSED_RESULT; + #endif /* ovsdb/file.h */ diff --git a/ovsdb/jsonrpc-server.c b/ovsdb/jsonrpc-server.c index d51a56854517..df268cd4eedc 100644 --- a/ovsdb/jsonrpc-server.c +++ b/ovsdb/jsonrpc-server.c @@ -82,7 +82,7 @@ static void ovsdb_jsonrpc_session_send(struct ovsdb_jsonrpc_session *, /* Triggers. */ static void ovsdb_jsonrpc_trigger_create(struct ovsdb_jsonrpc_session *, struct ovsdb *, - struct json *id, struct json *params); + struct jsonrpc_msg *request); static struct ovsdb_jsonrpc_trigger *ovsdb_jsonrpc_trigger_find( struct ovsdb_jsonrpc_session *, const struct json *id, size_t hash); static void ovsdb_jsonrpc_trigger_complete(struct ovsdb_jsonrpc_trigger *); @@ -936,17 +936,6 @@ ovsdb_jsonrpc_session_unlock(struct ovsdb_jsonrpc_session *s, } static struct jsonrpc_msg * -execute_transaction(struct ovsdb_jsonrpc_session *s, struct ovsdb *db, - struct jsonrpc_msg *request) -{ - ovsdb_jsonrpc_trigger_create(s, db, request->id, request->params); - request->id = NULL; - request->params = NULL; - jsonrpc_msg_destroy(request); - return NULL; -} - -static struct jsonrpc_msg * ovsdb_jsonrpc_session_set_db_change_aware(struct ovsdb_jsonrpc_session *s, const struct jsonrpc_msg *request) { @@ -967,10 +956,11 @@ ovsdb_jsonrpc_session_got_request(struct ovsdb_jsonrpc_session *s, { struct jsonrpc_msg *reply; - if (!strcmp(request->method, "transact")) { + if (!strcmp(request->method, "transact") || + !strcmp(request->method, "convert")) { struct ovsdb *db = ovsdb_jsonrpc_lookup_db(s, request, &reply); if (!reply) { - reply = execute_transaction(s, db, request); + ovsdb_jsonrpc_trigger_create(s, db, request); } } else if (!strcmp(request->method, "monitor") || (monitor_cond_enable__ && !strcmp(request->method, @@ -1082,37 +1072,35 @@ struct ovsdb_jsonrpc_trigger { static void ovsdb_jsonrpc_trigger_create(struct ovsdb_jsonrpc_session *s, struct ovsdb *db, - struct json *id, struct json *params) + struct jsonrpc_msg *request) { - struct ovsdb_jsonrpc_trigger *t; - size_t hash; - /* Check for duplicate ID. */ - hash = json_hash(id, 0); - t = ovsdb_jsonrpc_trigger_find(s, id, hash); + size_t hash = json_hash(request->id, 0); + struct ovsdb_jsonrpc_trigger *t + = ovsdb_jsonrpc_trigger_find(s, request->id, hash); if (t) { - struct jsonrpc_msg *msg; - - msg = jsonrpc_create_error(json_string_create("duplicate request ID"), - id); - ovsdb_jsonrpc_session_send(s, msg); - json_destroy(id); - json_destroy(params); + ovsdb_jsonrpc_session_send( + s, syntax_error_reply(request, "duplicate request ID")); + jsonrpc_msg_destroy(request); return; } /* Insert into trigger table. */ t = xmalloc(sizeof *t); - ovsdb_trigger_init(&s->up, db, &t->trigger, params, time_msec(), - s->read_only, s->remote->role, - jsonrpc_session_get_id(s->js)); - t->id = id; + bool disconnect_all = ovsdb_trigger_init( + &s->up, db, &t->trigger, request, time_msec(), s->read_only, + s->remote->role, jsonrpc_session_get_id(s->js)); + t->id = json_clone(request->id); hmap_insert(&s->triggers, &t->hmap_node, hash); /* Complete early if possible. */ if (ovsdb_trigger_is_complete(&t->trigger)) { ovsdb_jsonrpc_trigger_complete(t); } + + if (disconnect_all) { + ovsdb_jsonrpc_server_reconnect(s->remote->server, false); + } } static struct ovsdb_jsonrpc_trigger * @@ -1139,12 +1127,9 @@ ovsdb_jsonrpc_trigger_complete(struct ovsdb_jsonrpc_trigger *t) if (jsonrpc_session_is_connected(s->js)) { struct jsonrpc_msg *reply; - struct json *result; - result = ovsdb_trigger_steal_result(&t->trigger); - if (result) { - reply = jsonrpc_create_reply(result, t->id); - } else { + reply = ovsdb_trigger_steal_reply(&t->trigger); + if (!reply) { reply = jsonrpc_create_error(json_string_create("canceled"), t->id); } @@ -1159,7 +1144,7 @@ ovsdb_jsonrpc_trigger_complete(struct ovsdb_jsonrpc_trigger *t) static void ovsdb_jsonrpc_trigger_remove__(struct ovsdb_jsonrpc_session *s, - struct ovsdb *db) + struct ovsdb *db) { struct ovsdb_jsonrpc_trigger *t, *next; HMAP_FOR_EACH_SAFE (t, next, hmap_node, &s->triggers) { @@ -1189,11 +1174,9 @@ ovsdb_jsonrpc_trigger_complete_all(struct ovsdb_jsonrpc_session *s) static void ovsdb_jsonrpc_trigger_complete_done(struct ovsdb_jsonrpc_session *s) { - while (!ovs_list_is_empty(&s->up.completions)) { - struct ovsdb_jsonrpc_trigger *t - = CONTAINER_OF(s->up.completions.next, - struct ovsdb_jsonrpc_trigger, trigger.node); - ovsdb_jsonrpc_trigger_complete(t); + struct ovsdb_jsonrpc_trigger *trigger, *next; + LIST_FOR_EACH_SAFE (trigger, next, trigger.node, &s->up.completions) { + ovsdb_jsonrpc_trigger_complete(trigger); } } @@ -1441,7 +1424,7 @@ ovsdb_jsonrpc_monitor_create(struct ovsdb_jsonrpc_session *s, struct ovsdb *db, error: if (m) { - ovsdb_jsonrpc_monitor_destroy(m); + ovsdb_jsonrpc_monitor_destroy(m, false); } return jsonrpc_create_error(ovsdb_error_to_json_free(error), request_id); @@ -1598,32 +1581,12 @@ ovsdb_jsonrpc_monitor_cancel(struct ovsdb_jsonrpc_session *s, return jsonrpc_create_error(json_string_create("unknown monitor"), request_id); } else { - ovsdb_jsonrpc_monitor_destroy(m); + ovsdb_jsonrpc_monitor_destroy(m, false); return jsonrpc_create_reply(json_object_create(), request_id); } } } -static void -ovsdb_jsonrpc_monitor_remove__(struct ovsdb_jsonrpc_session *s, - struct ovsdb *db) -{ - struct ovsdb_jsonrpc_monitor *m, *next; - - HMAP_FOR_EACH_SAFE (m, next, node, &s->monitors) { - if (!db || m->db == db) { - if (db && jsonrpc_session_is_connected(s->js) - && s->db_change_aware) { - struct jsonrpc_msg *notify = jsonrpc_create_notify( - "monitor_canceled", - json_array_create_1(json_clone(m->monitor_id))); - ovsdb_jsonrpc_session_send(s, notify); - } - ovsdb_jsonrpc_monitor_destroy(m); - } - } -} - /* Database 'db' is about to be removed from the database server. To prepare, * this function removes all references from monitors in 's' to 'db'. */ static void @@ -1631,14 +1594,24 @@ ovsdb_jsonrpc_monitor_preremove_db(struct ovsdb_jsonrpc_session *s, struct ovsdb *db) { ovs_assert(db); - ovsdb_jsonrpc_monitor_remove__(s, db); + + struct ovsdb_jsonrpc_monitor *m, *next; + HMAP_FOR_EACH_SAFE (m, next, node, &s->monitors) { + if (m->db == db) { + ovsdb_jsonrpc_monitor_destroy(m, true); + } + } } /* Cancels all monitors in 's'. */ static void ovsdb_jsonrpc_monitor_remove_all(struct ovsdb_jsonrpc_session *s) { - ovsdb_jsonrpc_monitor_remove__(s, NULL); + struct ovsdb_jsonrpc_monitor *m, *next; + + HMAP_FOR_EACH_SAFE (m, next, node, &s->monitors) { + ovsdb_jsonrpc_monitor_destroy(m, false); + } } static struct json * @@ -1669,8 +1642,19 @@ ovsdb_jsonrpc_monitor_needs_flush(struct ovsdb_jsonrpc_session *s) } void -ovsdb_jsonrpc_monitor_destroy(struct ovsdb_jsonrpc_monitor *m) -{ +ovsdb_jsonrpc_monitor_destroy(struct ovsdb_jsonrpc_monitor *m, + bool notify_cancellation) +{ + if (notify_cancellation) { + struct ovsdb_jsonrpc_session *s = m->session; + if (jsonrpc_session_is_connected(s->js) && s->db_change_aware) { + struct jsonrpc_msg *notify = jsonrpc_create_notify( + "monitor_canceled", + json_array_create_1(json_clone(m->monitor_id))); + ovsdb_jsonrpc_session_send(s, notify); + } + } + json_destroy(m->monitor_id); hmap_remove(&m->session->monitors, &m->node); ovsdb_monitor_remove_jsonrpc_monitor(m->dbmon, m, m->unflushed); diff --git a/ovsdb/jsonrpc-server.h b/ovsdb/jsonrpc-server.h index 50a8b879c5a9..0fc16f21b2d9 100644 --- a/ovsdb/jsonrpc-server.h +++ b/ovsdb/jsonrpc-server.h @@ -80,7 +80,8 @@ const struct uuid *ovsdb_jsonrpc_server_get_uuid( const struct ovsdb_jsonrpc_server *); struct ovsdb_jsonrpc_monitor; -void ovsdb_jsonrpc_monitor_destroy(struct ovsdb_jsonrpc_monitor *); +void ovsdb_jsonrpc_monitor_destroy(struct ovsdb_jsonrpc_monitor *, + bool notify_cancellation); void ovsdb_jsonrpc_disable_monitor_cond(void); #endif /* ovsdb/jsonrpc-server.h */ diff --git a/ovsdb/monitor.c b/ovsdb/monitor.c index 3e58c3fbd274..97706932614c 100644 --- a/ovsdb/monitor.c +++ b/ovsdb/monitor.c @@ -1613,7 +1613,7 @@ ovsdb_monitors_remove(struct ovsdb *db) * end monitor will also destroy the corresponding 'ovsdb_monitor'. * ovsdb monitor will also be destroied. */ LIST_FOR_EACH_SAFE (jm, next_jm, node, &m->jsonrpc_monitors) { - ovsdb_jsonrpc_monitor_destroy(jm->jsonrpc_monitor); + ovsdb_jsonrpc_monitor_destroy(jm->jsonrpc_monitor, false); } } } @@ -1630,3 +1630,20 @@ ovsdb_monitor_get_memory_usage(struct simap *usage) simap_increase(usage, "json-caches", hmap_count(&dbmon->json_cache)); } } + +void +ovsdb_monitor_prereplace_db(struct ovsdb *db) +{ + struct ovsdb_monitor *m, *next_m; + + LIST_FOR_EACH_SAFE (m, next_m, list_node, &db->monitors) { + struct jsonrpc_monitor_node *jm, *next_jm; + + /* Delete all front end monitors. Removing the last front + * end monitor will also destroy the corresponding 'ovsdb_monitor'. + * ovsdb monitor will also be destroied. */ + LIST_FOR_EACH_SAFE (jm, next_jm, node, &m->jsonrpc_monitors) { + ovsdb_jsonrpc_monitor_destroy(jm->jsonrpc_monitor, true); + } + } +} diff --git a/ovsdb/monitor.h b/ovsdb/monitor.h index 99d43c45dff9..eb3ff270c9f3 100644 --- a/ovsdb/monitor.h +++ b/ovsdb/monitor.h @@ -49,6 +49,8 @@ struct ovsdb_monitor *ovsdb_monitor_create(struct ovsdb *db, void ovsdb_monitors_remove(struct ovsdb *); void ovsdb_monitors_commit(struct ovsdb *, const struct ovsdb_txn *); +void ovsdb_monitor_prereplace_db(struct ovsdb *); + struct ovsdb_monitor *ovsdb_monitor_add(struct ovsdb_monitor *dbmon); void ovsdb_monitor_add_jsonrpc_monitor(struct ovsdb_monitor *dbmon, diff --git a/ovsdb/ovsdb-client.1.in b/ovsdb/ovsdb-client.1.in index 5dbd49f25263..56d4797e933c 100644 --- a/ovsdb/ovsdb-client.1.in +++ b/ovsdb/ovsdb-client.1.in @@ -22,6 +22,9 @@ ovsdb\-client \- command-line interface to \fBovsdb-server\fR(1) .br \fBovsdb\-client\fR [\fIoptions\fR] \fBlist\-columns\fR [\fIserver\fR] [\fIdatabase\fR] [\fItable\fR] .IP "Database Version Management Commands:" +\fBovsdb\-client \fR[\fIoptions\fR] \fBconvert \fR[\fIserver\fR] \fIschema\fR +.br +\fBovsdb\-client \fR[\fIoptions\fR] \fBneeds\-conversion \fR[\fIserver\fR] \fIschema\fR .br \fBovsdb\-client\fR [\fIoptions\fR] \fBget\-schema\-version\fR [\fIserver\fR] [\fIdatabase\fR] .IP "Data Management Commands:" @@ -117,7 +120,43 @@ listed; otherwise, the tables include columns in all tables. These commands work with different versions of OVSDB schemas and databases. . -.IP "\fBget\-schema\-version\fR [\fIserver\fR] [\fIdatabase\fR]" +.IP "\fBconvert \fR[\fIserver\fR] \fIschema\fR" +Reads an OVSDB schema in JSON format, as specified in the OVSDB +specification, from \fIschema\fR, then connects to \fIserver\fR and +requests the server to convert the database whose name is specified in +\fIschema\fR to the schema also specified in \fIschema\fR. +.IP +The conversion is atomic, consistent, isolated, and durable. +Following the schema change, the server notifies clients that use the +\fBset_db_change_aware\fR RPC introduced in Open vSwitch 2.9 and +cancels their outstanding transactions and monitors. The server +disconnects other clients, enabling them to notice the change when +they reconnect. +.IP +This command can do simple ``upgrades'' and ``downgrades'' on a +database's schema. The data in the database must be valid when +interpreted under \fIschema\fR, with only one exception: data for +tables and columns that do not exist in \fIschema\fR are ignored. +Columns that exist in \fIschema\fR but not in the database are set to +their default values. All of \fIschema\fR's constraints apply in +full. +.IP +Some uses of this command can cause unrecoverable data loss. For +example, converting a database from a schema that has a given column +or table to one that does not will delete all data in that column or +table. Back up critical databases before converting them. +.IP +This command works with clustered and standalone databases. +Standalone databases may also be converted (offline) with +\fBovsdb\-tool\fR's \fBconvert\fR command. +. +.IP "\fBneeds\-conversion \fR[\fIserver\fR] \fIschema\fR" +Reads the schema from \fIschema\fR, then connects to \fIserver\fR and +requests the schema from the database whose name is specified in +\fIschema\fR. If the two schemas are the same, prints \fBno\fR on +stdout; if they differ, prints \fByes\fR. +. +.IP "\fBget\-schema\-version \fR[\fIserver\fR] [\fIdatabase\fR]" Connects to \fIserver\fR, retrieves the schema for \fIdatabase\fR, and prints its version number on stdout. If \fIdatabase\fR was created before schema versioning was introduced, diff --git a/ovsdb/ovsdb-client.c b/ovsdb/ovsdb-client.c index 600c5070db78..a7cab600c98b 100644 --- a/ovsdb/ovsdb-client.c +++ b/ovsdb/ovsdb-client.c @@ -77,9 +77,14 @@ static bool timestamp; /* --db-change-aware, --no-db-change-aware: Enable db_change_aware feature for * "monitor" command? * - * (This option is undocumented because it is expected to be useful only for - * testing that the db_change_aware feature actually works.) */ -static int db_change_aware; + * -1 (default): Use db_change_aware if available. + * 0: Disable db_change_aware. + * 1: Require db_change_aware. + * + * (This option is undocumented because anything other than the default is + * expected to be useful only for testing that the db_change_aware feature + * actually works.) */ +static int db_change_aware = -1; /* --force: Ignore schema differences for "restore" command? */ static bool force; @@ -303,6 +308,8 @@ usage(void) " DATABASE on SERVER.\n" " COLUMNs may include !initial, !insert, !delete, !modify\n" " to avoid seeing the specified kinds of changes.\n" + "\n convert [SERVER] SCHEMA\n" + " convert database on SERVER named in SCHEMA to SCHEMA.\n" "\n monitor [SERVER] [DATABASE] ALL\n" " monitor all changes to all columns in all tables\n" " in DATBASE on SERVER.\n" @@ -557,11 +564,40 @@ do_list_columns(struct jsonrpc *rpc, const char *database, table_destroy(&t); } +static void +send_db_change_aware(struct jsonrpc *rpc) +{ + if (db_change_aware != 0) { + struct jsonrpc_msg *request = jsonrpc_create_request( + "set_db_change_aware", + json_array_create_1(json_boolean_create(true)), + NULL); + struct jsonrpc_msg *reply; + int error = jsonrpc_transact_block(rpc, request, &reply); + if (error) { + ovs_fatal(error, "%s: error setting db_change_aware", + jsonrpc_get_name(rpc)); + } + if (reply->type == JSONRPC_ERROR && db_change_aware == 1) { + ovs_fatal(0, "%s: set_db_change_aware failed (%s)", + jsonrpc_get_name(rpc), json_to_string(reply->error, 0)); + } + jsonrpc_msg_destroy(reply); + } +} + static struct json * do_transact__(struct jsonrpc *rpc, struct json *transaction) { struct jsonrpc_msg *request, *reply; + if (db_change_aware == 1) { + send_db_change_aware(rpc); + } + daemon_save_fd(STDOUT_FILENO); + daemon_save_fd(STDERR_FILENO); + daemonize(); + request = jsonrpc_create_request("transact", transaction, NULL); check_txn(jsonrpc_transact_block(rpc, request, &reply), &reply); struct json *result = json_clone(reply->result); @@ -1040,6 +1076,7 @@ do_monitor__(struct jsonrpc *rpc, const char *database, ovs_assert(version < OVSDB_MONITOR_VERSION_MAX); daemon_save_fd(STDOUT_FILENO); + daemon_save_fd(STDERR_FILENO); daemonize_start(false); if (get_detach()) { int error; @@ -1097,22 +1134,7 @@ do_monitor__(struct jsonrpc *rpc, const char *database, free(nodes); } - if (db_change_aware) { - struct jsonrpc_msg *request = jsonrpc_create_request( - "set_db_change_aware", - json_array_create_1(json_boolean_create(true)), - NULL); - struct jsonrpc_msg *reply; - int error = jsonrpc_transact_block(rpc, request, &reply); - if (error) { - ovs_fatal(error, "%s: error setting db_change_aware", server); - } - if (reply->type == JSONRPC_ERROR) { - ovs_fatal(0, "%s: set_db_change_aware failed (%s)", - server, json_to_string(reply->error, 0)); - } - jsonrpc_msg_destroy(reply); - } + send_db_change_aware(rpc); monitor = json_array_create_3(json_string_create(database), json_null_create(), monitor_requests); @@ -1174,6 +1196,10 @@ do_monitor__(struct jsonrpc *rpc, const char *database, monitor2_print(params->u.array.elems[1], mts, n_mts); fflush(stdout); } + } else if (msg->type == JSONRPC_NOTIFY + && !strcmp(msg->method, "monitor_canceled")) { + ovs_fatal(0, "%s: %s database was removed", + server, database); } jsonrpc_msg_destroy(msg); } @@ -1229,6 +1255,35 @@ do_monitor_cond(struct jsonrpc *rpc, const char *database, ovsdb_schema_destroy(schema); } +static void +do_convert(struct jsonrpc *rpc, const char *database OVS_UNUSED, + int argc OVS_UNUSED, char *argv[]) +{ + struct ovsdb_schema *new_schema; + check_ovsdb_error(ovsdb_schema_from_file(argv[0], &new_schema)); + + struct jsonrpc_msg *request, *reply; + request = jsonrpc_create_request( + "convert", + json_array_create_2(json_string_create(new_schema->name), + ovsdb_schema_to_json(new_schema)), NULL); + check_txn(jsonrpc_transact_block(rpc, request, &reply), &reply); + jsonrpc_msg_destroy(reply); +} + +static void +do_needs_conversion(struct jsonrpc *rpc, const char *database OVS_UNUSED, + int argc OVS_UNUSED, char *argv[]) +{ + struct ovsdb_schema *schema1; + check_ovsdb_error(ovsdb_schema_from_file(argv[0], &schema1)); + + struct ovsdb_schema *schema2 = fetch_schema(rpc, schema1->name); + puts(ovsdb_schema_equal(schema1, schema2) ? "no" : "yes"); + ovsdb_schema_destroy(schema1); + ovsdb_schema_destroy(schema2); +} + struct dump_table_aux { struct ovsdb_datum **data; const struct ovsdb_column **columns; @@ -1910,6 +1965,8 @@ static const struct ovsdb_client_command all_commands[] = { { "query", NEED_RPC, 1, 1, do_query }, { "monitor", NEED_DATABASE, 1, INT_MAX, do_monitor }, { "monitor-cond", NEED_DATABASE, 2, 3, do_monitor_cond }, + { "convert", NEED_RPC, 1, 1, do_convert }, + { "needs-conversion", NEED_RPC, 1, 1, do_needs_conversion }, { "dump", NEED_DATABASE, 0, INT_MAX, do_dump }, { "backup", NEED_DATABASE, 0, 0, do_backup }, { "restore", NEED_DATABASE, 0, 0, do_restore }, diff --git a/ovsdb/ovsdb-server.c b/ovsdb/ovsdb-server.c index 1e36b27958f8..f7bf1e270120 100644 --- a/ovsdb/ovsdb-server.c +++ b/ovsdb/ovsdb-server.c @@ -111,10 +111,11 @@ static char *open_db(struct server_config *config, const char *filename); static void add_server_db(struct server_config *); static void close_db(struct db *db); -static void parse_options(int *argc, char **argvp[], - struct sset *remotes, char **unixctl_pathp, - char **run_command, char **sync_from, - char **sync_exclude, bool *is_backup); +static void parse_options(int argc, char *argvp[], + struct sset *db_filenames, struct sset *remotes, + char **unixctl_pathp, char **run_command, + char **sync_from, char **sync_exclude, + bool *is_backup); OVS_NO_RETURN static void usage(void); static char *reconfigure_remotes(struct ovsdb_jsonrpc_server *, @@ -202,7 +203,9 @@ main_loop(struct ovsdb_jsonrpc_server *jsonrpc, struct shash *all_dbs, SHASH_FOR_EACH(node, all_dbs) { struct db *db = node->data; - ovsdb_trigger_run(db->db, time_msec()); + if (ovsdb_trigger_run(db->db, time_msec())) { + ovsdb_jsonrpc_server_reconnect(jsonrpc, false); + } } if (run_process) { process_run(); @@ -265,7 +268,6 @@ main(int argc, char *argv[]) struct shash all_dbs; struct shash_node *node, *next; char *error; - int i; ovs_cmdl_proctitle_init(argc, argv); set_program_name(argv[0]); @@ -274,8 +276,8 @@ main(int argc, char *argv[]) process_init(); bool active = false; - parse_options(&argc, &argv, &remotes, &unixctl_path, &run_command, - &sync_from, &sync_exclude, &active); + parse_options(argc, argv, &db_filenames, &remotes, &unixctl_path, + &run_command, &sync_from, &sync_exclude, &active); is_backup = sync_from && !active; daemon_become_new_user(false); @@ -290,17 +292,6 @@ main(int argc, char *argv[]) ovs_fatal(errno, "failed to create temporary file"); } - sset_init(&db_filenames); - if (argc > 0) { - for (i = 0; i < argc; i++) { - sset_add(&db_filenames, argv[i]); - } - } else { - char *default_db = xasprintf("%s/conf.db", ovs_dbdir()); - sset_add(&db_filenames, default_db); - free(default_db); - } - server_config.remotes = &remotes; server_config.config_tmpfile = config_tmpfile; @@ -1477,8 +1468,9 @@ ovsdb_server_get_sync_status(struct unixctl_conn *conn, int argc OVS_UNUSED, } static void -parse_options(int *argcp, char **argvp[], - struct sset *remotes, char **unixctl_pathp, char **run_command, +parse_options(int argc, char *argv[], + struct sset *db_filenames, struct sset *remotes, + char **unixctl_pathp, char **run_command, char **sync_from, char **sync_exclude, bool *active) { enum { @@ -1490,10 +1482,12 @@ parse_options(int *argcp, char **argvp[], OPT_SYNC_FROM, OPT_SYNC_EXCLUDE, OPT_ACTIVE, + OPT_NO_DBS, VLOG_OPTION_ENUMS, DAEMON_OPTION_ENUMS, SSL_OPTION_ENUMS, }; + static const struct option long_options[] = { {"remote", required_argument, NULL, OPT_REMOTE}, {"unixctl", required_argument, NULL, OPT_UNIXCTL}, @@ -1510,14 +1504,15 @@ parse_options(int *argcp, char **argvp[], {"sync-from", required_argument, NULL, OPT_SYNC_FROM}, {"sync-exclude-tables", required_argument, NULL, OPT_SYNC_EXCLUDE}, {"active", no_argument, NULL, OPT_ACTIVE}, + {"no-dbs", no_argument, NULL, OPT_NO_DBS}, {NULL, 0, NULL, 0}, }; char *short_options = ovs_cmdl_long_options_to_short_options(long_options); - int argc = *argcp; - char **argv = *argvp; + bool add_default_db = true; *sync_from = NULL; *sync_exclude = NULL; + sset_init(db_filenames); sset_init(remotes); for (;;) { int c; @@ -1596,6 +1591,10 @@ parse_options(int *argcp, char **argvp[], *active = true; break; + case OPT_NO_DBS: + add_default_db = false; + break; + case '?': exit(EXIT_FAILURE); @@ -1605,8 +1604,15 @@ parse_options(int *argcp, char **argvp[], } free(short_options); - *argcp -= optind; - *argvp += optind; + argc -= optind; + argv += optind; + if (argc > 0) { + for (int i = 0; i < argc; i++) { + sset_add(db_filenames, argv[i]); + } + } else if (add_default_db) { + sset_add_and_free(db_filenames, xasprintf("%s/conf.db", ovs_dbdir())); + } } static void diff --git a/ovsdb/ovsdb.c b/ovsdb/ovsdb.c index 19755e673861..89f530bcccfb 100644 --- a/ovsdb/ovsdb.c +++ b/ovsdb/ovsdb.c @@ -27,6 +27,7 @@ #include "simap.h" #include "table.h" #include "transaction.h" +#include "trigger.h" struct ovsdb_schema * ovsdb_schema_create(const char *name, const char *version, const char *cksum) @@ -162,7 +163,7 @@ root_set_size(const struct ovsdb_schema *schema) } struct ovsdb_error * -ovsdb_schema_from_json(struct json *json, struct ovsdb_schema **schemap) +ovsdb_schema_from_json(const struct json *json, struct ovsdb_schema **schemap) { struct ovsdb_schema *schema; const struct json *name, *tables, *version_json, *cksum; @@ -361,6 +362,29 @@ ovsdb_create(struct ovsdb_schema *schema) } void +ovsdb_replace(struct ovsdb *dst, struct ovsdb *src) +{ + /* Cancel monitors. */ + ovsdb_monitor_prereplace_db(dst); + + /* Cancel triggers. */ + struct ovsdb_trigger *trigger, *next; + LIST_FOR_EACH_SAFE (trigger, next, node, &dst->triggers) { + ovsdb_trigger_prereplace_db(trigger); + } + + struct ovsdb_schema *tmp_schema = dst->schema; + dst->schema = src->schema; + src->schema = tmp_schema; + + shash_swap(&dst->tables, &src->tables); + + dst->rbac_role = ovsdb_get_table(dst, "RBAC_Role"); + + ovsdb_destroy(src); +} + +void ovsdb_destroy(struct ovsdb *db) { if (db) { diff --git a/ovsdb/ovsdb.h b/ovsdb/ovsdb.h index 9d915f0f15ae..c3e8f2091e35 100644 --- a/ovsdb/ovsdb.h +++ b/ovsdb/ovsdb.h @@ -45,7 +45,7 @@ void ovsdb_schema_destroy(struct ovsdb_schema *); struct ovsdb_error *ovsdb_schema_from_file(const char *file_name, struct ovsdb_schema **) OVS_WARN_UNUSED_RESULT; -struct ovsdb_error *ovsdb_schema_from_json(struct json *, +struct ovsdb_error *ovsdb_schema_from_json(const struct json *, struct ovsdb_schema **) OVS_WARN_UNUSED_RESULT; struct json *ovsdb_schema_to_json(const struct ovsdb_schema *); @@ -68,6 +68,7 @@ struct ovsdb { }; struct ovsdb *ovsdb_create(struct ovsdb_schema *); +void ovsdb_replace(struct ovsdb *dst, struct ovsdb *src); void ovsdb_destroy(struct ovsdb *); void ovsdb_get_memory_usage(const struct ovsdb *, struct simap *usage); diff --git a/ovsdb/transaction.c b/ovsdb/transaction.c index f1502ffb398c..893ea1152c5a 100644 --- a/ovsdb/transaction.c +++ b/ovsdb/transaction.c @@ -806,8 +806,14 @@ update_version(struct ovsdb_txn *txn OVS_UNUSED, struct ovsdb_txn_row *txn_row) return NULL; } -static struct ovsdb_error * -ovsdb_txn_commit_(struct ovsdb_txn *txn, bool durable) +static bool +ovsdb_txn_is_empty(const struct ovsdb_txn *txn) +{ + return ovs_list_is_empty(&txn->txn_tables); +} + +struct ovsdb_error * OVS_WARN_UNUSED_RESULT +ovsdb_txn_start_commit(struct ovsdb_txn *txn) { struct ovsdb_error *error; @@ -818,29 +824,25 @@ ovsdb_txn_commit_(struct ovsdb_txn *txn, bool durable) ovsdb_txn_abort(txn); return OVSDB_WRAP_BUG("can't happen", error); } - if (ovs_list_is_empty(&txn->txn_tables)) { - ovsdb_txn_abort(txn); + if (ovsdb_txn_is_empty(txn)) { return NULL; } /* Update reference counts and check referential integrity. */ error = update_ref_counts(txn); if (error) { - ovsdb_txn_abort(txn); return error; } /* Delete unreferenced, non-root rows. */ error = for_each_txn_row(txn, collect_garbage); if (error) { - ovsdb_txn_abort(txn); return OVSDB_WRAP_BUG("can't happen", error); } /* Check maximum rows table constraints. */ error = check_max_rows(txn); if (error) { - ovsdb_txn_abort(txn); return error; } @@ -848,14 +850,12 @@ ovsdb_txn_commit_(struct ovsdb_txn *txn, bool durable) * integrity. */ error = for_each_txn_row(txn, assess_weak_refs); if (error) { - ovsdb_txn_abort(txn); return error; } /* Verify that the indexes will still be unique post-transaction. */ error = for_each_txn_row(txn, check_index_uniqueness); if (error) { - ovsdb_txn_abort(txn); return error; } @@ -865,9 +865,16 @@ ovsdb_txn_commit_(struct ovsdb_txn *txn, bool durable) return OVSDB_WRAP_BUG("can't happen", error); } + return NULL; +} + +struct ovsdb_error * +ovsdb_txn_finish_commit(struct ovsdb_txn *txn, bool durable) +{ /* Send the commit to each replica. */ if (txn->db->file) { - error = ovsdb_file_commit(txn->db->file, txn, durable); + struct ovsdb_error *error = ovsdb_file_commit(txn->db->file, txn, + durable); if (error) { ovsdb_txn_abort(txn); return error; @@ -887,10 +894,12 @@ ovsdb_txn_commit_(struct ovsdb_txn *txn, bool durable) struct ovsdb_error * ovsdb_txn_commit(struct ovsdb_txn *txn, bool durable) { - struct ovsdb_error *err; - - PERF(__func__, err = ovsdb_txn_commit_(txn, durable)); - return err; + struct ovsdb_error *error = ovsdb_txn_start_commit(txn); + if (error || ovsdb_txn_is_empty(txn)) { + ovsdb_txn_abort(txn); + return error; + } + return ovsdb_txn_finish_commit(txn, durable); } void diff --git a/ovsdb/transaction.h b/ovsdb/transaction.h index 1ecd15a56a8d..f9b886411bf4 100644 --- a/ovsdb/transaction.h +++ b/ovsdb/transaction.h @@ -26,6 +26,11 @@ struct uuid; struct ovsdb_txn *ovsdb_txn_create(struct ovsdb *); void ovsdb_txn_abort(struct ovsdb_txn *); + +struct ovsdb_error *ovsdb_txn_start_commit(struct ovsdb_txn *) + OVS_WARN_UNUSED_RESULT; +struct ovsdb_error *ovsdb_txn_finish_commit(struct ovsdb_txn *, bool durable) + OVS_WARN_UNUSED_RESULT; struct ovsdb_error *ovsdb_txn_commit(struct ovsdb_txn *, bool durable) OVS_WARN_UNUSED_RESULT; diff --git a/ovsdb/trigger.c b/ovsdb/trigger.c index 165cd6ebbdd1..346db7b5fb28 100644 --- a/ovsdb/trigger.c +++ b/ovsdb/trigger.c @@ -19,42 +19,48 @@ #include +#include "file.h" +#include "log.h" #include "openvswitch/json.h" #include "jsonrpc.h" #include "ovsdb.h" +#include "ovsdb-error.h" #include "openvswitch/poll-loop.h" #include "server.h" #include "util.h" + static bool ovsdb_trigger_try(struct ovsdb_trigger *, long long int now); -static void ovsdb_trigger_complete(struct ovsdb_trigger *); +static void trigger_error(struct ovsdb_trigger *, struct ovsdb_error *); +static void trigger_success(struct ovsdb_trigger *, struct json *result); -void +bool ovsdb_trigger_init(struct ovsdb_session *session, struct ovsdb *db, struct ovsdb_trigger *trigger, - struct json *request, long long int now, - bool read_only, const char *role, - const char *id) + struct jsonrpc_msg *request, long long int now, + bool read_only, const char *role, const char *id) { + ovs_assert(!strcmp(request->method, "transact") || + !strcmp(request->method, "convert")); trigger->session = session; trigger->db = db; ovs_list_push_back(&trigger->db->triggers, &trigger->node); trigger->request = request; - trigger->result = NULL; + trigger->reply = NULL; trigger->created = now; trigger->timeout_msec = LLONG_MAX; trigger->read_only = read_only; trigger->role = nullable_xstrdup(role); trigger->id = nullable_xstrdup(id); - ovsdb_trigger_try(trigger, now); + return ovsdb_trigger_try(trigger, now); } void ovsdb_trigger_destroy(struct ovsdb_trigger *trigger) { ovs_list_remove(&trigger->node); - json_destroy(trigger->request); - json_destroy(trigger->result); + jsonrpc_msg_destroy(trigger->request); + jsonrpc_msg_destroy(trigger->reply); free(trigger->role); free(trigger->id); } @@ -62,30 +68,53 @@ ovsdb_trigger_destroy(struct ovsdb_trigger *trigger) bool ovsdb_trigger_is_complete(const struct ovsdb_trigger *trigger) { - return trigger->result != NULL; + return trigger->reply != NULL; } -struct json * -ovsdb_trigger_steal_result(struct ovsdb_trigger *trigger) +struct jsonrpc_msg * +ovsdb_trigger_steal_reply(struct ovsdb_trigger *trigger) { - struct json *result = trigger->result; - trigger->result = NULL; - return result; + struct jsonrpc_msg *reply = trigger->reply; + trigger->reply = NULL; + return reply; } void +ovsdb_trigger_prereplace_db(struct ovsdb_trigger *trigger) +{ + if (!strcmp(trigger->request->method, "transact")) { + trigger_error(trigger, ovsdb_error("canceled", NULL)); + } else if (!strcmp(trigger->request->method, "convert")) { + /* We don't cancel "convert" requests when a database is being replaced + * for two reasons. First, we expect the administrator to do some kind + * of sensible synchronization on conversion requests, that is, it only + * really makes sense for the admin to do a single conversion at a time + * at a scheduled point. Second, if we did then every "convert" + * request would end up getting canceled since "convert" itself causes + * the database to be replaced. */ + } else { + OVS_NOT_REACHED(); + } +} + +bool ovsdb_trigger_run(struct ovsdb *db, long long int now) { struct ovsdb_trigger *t, *next; - bool run_triggers; - run_triggers = db->run_triggers; + bool run_triggers = db->run_triggers; db->run_triggers = false; + + bool disconnect_all = false; + LIST_FOR_EACH_SAFE (t, next, node, &db->triggers) { if (run_triggers || now - t->created >= t->timeout_msec) { - ovsdb_trigger_try(t, now); + if (ovsdb_trigger_try(t, now)) { + disconnect_all = true; + } } } + return disconnect_all; } void @@ -118,22 +147,81 @@ ovsdb_trigger_wait(struct ovsdb *db, long long int now) static bool ovsdb_trigger_try(struct ovsdb_trigger *t, long long int now) { - t->result = ovsdb_execute(t->db, t->session, - t->request, t->read_only, - t->role, t->id, - now - t->created, &t->timeout_msec); - if (t->result) { - ovsdb_trigger_complete(t); + if (!strcmp(t->request->method, "transact")) { + struct json *result = ovsdb_execute(t->db, t->session, + t->request->params, t->read_only, + t->role, t->id, now - t->created, + &t->timeout_msec); + if (result) { + trigger_success(t, result); + } + return false; + } else if (!strcmp(t->request->method, "convert")) { + /* Permission check. */ + if (t->role && *t->role) { + trigger_error(t, ovsdb_perm_error( + "RBAC rules for client \"%s\" role \"%s\" " + "prohibit \"convert\" of database %s " + "(only the root role may convert databases)", + t->id, t->role, t->db->schema->name)); + return false; + } + + /* Validate parameters. */ + const struct json *params = t->request->params; + if (params->type != JSON_ARRAY || params->u.array.n != 2) { + trigger_error(t, ovsdb_syntax_error(params, NULL, + "array expected")); + return false; + } + + /* Parse new schema and make a converted copy. */ + const struct json *new_schema_json = params->u.array.elems[1]; + struct ovsdb_schema *new_schema; + struct ovsdb_error *error = ovsdb_schema_from_json(new_schema_json, + &new_schema); + if (!error && strcmp(new_schema->name, t->db->schema->name)) { + error = ovsdb_error( + "invalid parameters", + "new schema name (%s) does not match database name (%s)", + new_schema->name, t->db->schema->name); + } + if (!error) { + error = ovsdb_file_convert(t->db->file, new_schema); + } + ovsdb_schema_destroy(new_schema); + if (error) { + trigger_error(t, error); + return false; + } + + trigger_success(t, json_object_create()); return true; } else { - return false; + OVS_NOT_REACHED(); } } static void -ovsdb_trigger_complete(struct ovsdb_trigger *t) +ovsdb_trigger_complete(struct ovsdb_trigger *t, struct jsonrpc_msg *reply) { - ovs_assert(t->result != NULL); + ovs_assert(reply && !t->reply); + t->reply = reply; ovs_list_remove(&t->node); ovs_list_push_back(&t->session->completions, &t->node); } + +static void +trigger_error(struct ovsdb_trigger *t, struct ovsdb_error *error) +{ + struct jsonrpc_msg *reply = jsonrpc_create_error( + ovsdb_error_to_json_free(error), t->request->id); + ovsdb_trigger_complete(t, reply); +} + +static void +trigger_success(struct ovsdb_trigger *t, struct json *result) +{ + struct jsonrpc_msg *reply = jsonrpc_create_reply(result, t->request->id); + ovsdb_trigger_complete(t, reply); +} diff --git a/ovsdb/trigger.h b/ovsdb/trigger.h index 90246a4a42bd..d9df97f31222 100644 --- a/ovsdb/trigger.h +++ b/ovsdb/trigger.h @@ -25,8 +25,8 @@ struct ovsdb_trigger { struct ovsdb *db; /* Database on which trigger acts. */ struct ovs_list node; /* !result: in db->triggers; * result: in session->completions. */ - struct json *request; /* Database request. */ - struct json *result; /* Result (null if none yet). */ + struct jsonrpc_msg *request; /* Database request. */ + struct jsonrpc_msg *reply; /* Result (null if none yet).. */ long long int created; /* Time created. */ long long int timeout_msec; /* Max wait duration. */ bool read_only; /* Database is in read only mode. */ @@ -34,17 +34,18 @@ struct ovsdb_trigger { char *id; /* ID, for role-based access controls. */ }; -void ovsdb_trigger_init(struct ovsdb_session *, struct ovsdb *, +bool ovsdb_trigger_init(struct ovsdb_session *, struct ovsdb *, struct ovsdb_trigger *, - struct json *request, long long int now, - bool read_only, const char *role, - const char *id); + struct jsonrpc_msg *request, long long int now, + bool read_only, const char *role, const char *id); void ovsdb_trigger_destroy(struct ovsdb_trigger *); bool ovsdb_trigger_is_complete(const struct ovsdb_trigger *); -struct json *ovsdb_trigger_steal_result(struct ovsdb_trigger *); +struct jsonrpc_msg *ovsdb_trigger_steal_reply(struct ovsdb_trigger *); -void ovsdb_trigger_run(struct ovsdb *, long long int now); +void ovsdb_trigger_prereplace_db(struct ovsdb_trigger *); + +bool ovsdb_trigger_run(struct ovsdb *, long long int now); void ovsdb_trigger_wait(struct ovsdb *, long long int now); #endif /* ovsdb/trigger.h */ diff --git a/tests/ovsdb-monitor.at b/tests/ovsdb-monitor.at index 2434f43cb761..917a5cc09ace 100644 --- a/tests/ovsdb-monitor.at +++ b/tests/ovsdb-monitor.at @@ -29,11 +29,11 @@ m4_define([OVSDB_CHECK_MONITOR], on_exit 'kill `cat ovsdb-server.pid`' AT_CAPTURE_FILE([ovsdb-client-log]) if test "$IS_WIN32" = "yes"; then - 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 &], + AT_CHECK([ovsdb-client -vjsonrpc --detach --pidfile --log-file="`pwd`"/ovsdb-client-log -d json monitor --format=csv unix:socket $4 $5 $8 > output], [0], [ignore], [ignore]) sleep 1 else - 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], + 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], [0], [ignore], [ignore]) fi on_exit 'kill `cat ovsdb-client.pid`' diff --git a/tests/ovsdb-server.at b/tests/ovsdb-server.at index 2e3d8ad14636..54ff04ef3146 100644 --- a/tests/ovsdb-server.at +++ b/tests/ovsdb-server.at @@ -790,6 +790,269 @@ _uuid name number OVSDB_SERVER_SHUTDOWN AT_CLEANUP +AT_SETUP([schema conversion online]) +AT_KEYWORDS([ovsdb server convert needs-conversion]) +on_exit 'kill `cat *.pid`' +ordinal_schema > schema +AT_DATA([new-schema], + [[{"name": "ordinals", + "tables": { + "ordinals": { + "columns": { + "number": {"type": "integer"}}}}} +]]) +dnl Make sure that "ovsdb-tool create" works with a dangling symlink for +dnl the database and the lockfile, creating the target of each symlink rather +dnl than replacing the symlinks with regular files. +mkdir dir +if test "$IS_WIN32" = "no"; then + ln -s dir/db db + ln -s dir/.db.~lock~ .db.~lock~ + AT_SKIP_IF([test ! -h db || test ! -h .db.~lock~]) +fi +AT_CHECK([ovsdb-tool create db schema]) +dnl Put some data in the database. +AT_CHECK( + [[for pair in 'zero 0' 'one 1' 'two 2' 'three 3' 'four 4' 'five 5'; do + set -- $pair + ovsdb-tool transact db ' + ["ordinals", + {"op": "insert", + "table": "ordinals", + "row": {"name": "'$1'", "number": '$2'}}, + {"op": "comment", + "comment": "add row for '"$pair"'"}]' + done | uuidfilt]], [0], +[[[{"uuid":["uuid","<0>"]},{}] +[{"uuid":["uuid","<1>"]},{}] +[{"uuid":["uuid","<2>"]},{}] +[{"uuid":["uuid","<3>"]},{}] +[{"uuid":["uuid","<4>"]},{}] +[{"uuid":["uuid","<5>"]},{}] +]], [ignore]) + +dnl Start the database server. +AT_CHECK([ovsdb-server -vfile -vvlog:off --detach --no-chdir --pidfile --log-file --remote=punix:db.sock db], [0]) +AT_CAPTURE_FILE([ovsdb-server.log]) + +dnl Try "needs-conversion". +AT_CHECK([ovsdb-client needs-conversion schema], [0], [no +]) +AT_CHECK([ovsdb-client needs-conversion new-schema], [0], [yes +]) + +dnl Start two monitors on the 'ordinals' db, one that is database +dnl change aware and one that is not. +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]) +AT_CAPTURE_FILE([monitor-ordinals-aware.stdout]) +AT_CAPTURE_FILE([monitor-ordinals-aware.log]) +AT_CAPTURE_FILE([monitor-ordinals-aware.stderr]) + +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]) +AT_CAPTURE_FILE([monitor-ordinals-unaware.stdout]) +AT_CAPTURE_FILE([monitor-ordinals-unaware.log]) +AT_CAPTURE_FILE([monitor-ordinals-unaware.stderr]) + +dnl Start two monitors on the '_Server' db, one that is database +dnl change aware and one that is not. +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]) +AT_CAPTURE_FILE([monitor-server-aware.stdout]) +AT_CAPTURE_FILE([monitor-server-aware.log]) +AT_CAPTURE_FILE([monitor-server-aware.stderr]) + +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]) +AT_CAPTURE_FILE([monitor-server-unaware.stdout]) +AT_CAPTURE_FILE([monitor-server-unaware.log]) +AT_CAPTURE_FILE([monitor-server-unaware.stderr]) + +dnl Start two long-running transactions (triggers) on the 'ordinals' db, +dnl one that is database change aware and one that is not. +ordinals_txn='[["ordinals", + {"op": "wait", + "table": "ordinals", + "where": [["name", "==", "seven"]], + "columns": ["name", "number"], + "rows": [], + "until": "!="}]]' +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]) +AT_CAPTURE_FILE([trigger-ordinals-aware.stdout]) +AT_CAPTURE_FILE([trigger-ordinals-aware.log]) +AT_CAPTURE_FILE([trigger-ordinals-aware.stderr]) + +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]) +AT_CAPTURE_FILE([trigger-ordinals-unaware.stdout]) +AT_CAPTURE_FILE([trigger-ordinals-unaware.log]) +AT_CAPTURE_FILE([trigger-ordinals-unaware.stderr]) + +dnl Start two long-running transactions (triggers) on the _Server db, +dnl one that is database change aware and one that is not. +server_txn='[["_Server", + {"op": "wait", + "table": "Database", + "where": [["name", "==", "xyzzy"]], + "columns": ["name"], + "rows": [], + "until": "!="}]]' +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]) +AT_CAPTURE_FILE([trigger-server-aware.stdout]) +AT_CAPTURE_FILE([trigger-server-aware.log]) +AT_CAPTURE_FILE([trigger-server-aware.stderr]) + +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]) +AT_CAPTURE_FILE([trigger-server-unaware.stdout]) +AT_CAPTURE_FILE([trigger-server-unaware.log]) +AT_CAPTURE_FILE([trigger-server-unaware.stderr]) + +dnl Dump out and check the actual database contents. +AT_CHECK([ovsdb-client dump unix:db.sock ordinals], [0], [stdout]) +AT_CHECK([uuidfilt stdout], [0], [dnl +ordinals table +_uuid name number +------------------------------------ ----- ------ +<0> five 5 +<1> four 4 +<2> one 1 +<3> three 3 +<4> two 2 +<5> zero 0 +]) + +dnl Convert the database. +AT_CHECK([ovsdb-client convert new-schema]) + +dnl Try "needs-conversion". +AT_CHECK([ovsdb-client needs-conversion schema], [0], [yes +]) +AT_CHECK([ovsdb-client needs-conversion new-schema], [0], [no +]) + +dnl Verify that the "ordinals" monitors behaved as they should have. +dnl Both should have exited, for different reasons. +dnl The db-aware _Server monitor should still be running, but not the unaware +dnl one. +for x in unaware aware; do + OVS_WAIT_WHILE([test -e monitor-ordinals-$x.pid]) + AT_CHECK([sort -k 3 monitor-ordinals-$x.stdout | uuidfilt], [0], +[<0> initial 0 zero +<1> initial 1 one +<2> initial 2 two +<3> initial 3 three +<4> initial 4 four +<5> initial 5 five +]) +done +AT_CHECK([sed 's/.*: //' monitor-ordinals-unaware.stderr], [0], [receive failed (End of file) +]) +AT_CHECK([sed 's/.*: //' monitor-ordinals-aware.stderr], [0], [ordinals database was removed +]) + +dnl Verify that the _Server monitors behaved as they should have. +dnl The db-aware monitor should still be running, but not the unaware one. +for x in aware unaware; do + AT_CHECK([sort -k 3 monitor-server-$x.stdout | uuidfilt], [0], +[<0> initial _Server +<1> initial ordinals +]) +done +OVS_WAIT_WHILE([test -e monitor-server-unaware.pid]) +AT_CHECK([sed 's/.*: //' monitor-ordinals-unaware.stderr], [0], [receive failed (End of file) +]) +AT_CHECK([test -e monitor-server-aware.pid]) + +dnl Verify that the "ordinals" triggers behaved as they should have: +dnl Both should have exited, for different reasons. +for x in unaware aware; do + OVS_WAIT_WHILE([test -e trigger-ordinals-$x.pid]) + AT_CHECK([cat trigger-ordinals-$x.stdout]) +done +AT_CHECK([cat trigger-ordinals-unaware.stderr], [0], [ovsdb-client: transaction failed (End of file) +]) +AT_CHECK([cat trigger-ordinals-aware.stderr], [0], [ovsdb-client: transaction returned error: {"error":"canceled"} +]) + +dnl Verify that the _Server triggers behaved as they should have: +dnl The db-aware trigger should still be waiting, but not the unaware one. +for x in aware unaware; do + AT_CHECK([cat trigger-server-$x.stdout]) +done +OVS_WAIT_WHILE([test -e trigger-server-unaware.pid]) +AT_CHECK([sed 's/.*: //' trigger-ordinals-unaware.stderr], [0], [transaction failed (End of file) +]) +AT_CHECK([test -e trigger-server-aware.pid]) + +dnl We can't fully re-check the contents of the database log, because the +dnl order of the records is not predictable, but there should only be 4 lines +dnl in it now. +AT_CAPTURE_FILE([db]) +AT_CHECK([test `wc -l < db` -eq 4]) +dnl And check that the dumped data is the same except for the removed column: +AT_CHECK([ovsdb-client dump unix:db.sock ordinals | uuidfilt], [0], [dnl +ordinals table +_uuid number +------------------------------------ ------ +<0> 0 +<1> 1 +<2> 2 +<3> 3 +<4> 4 +<5> 5 +]) +dnl Now check that the converted database is still online and can be modified, +dnl then check that the database log has one more record and that the data +dnl is as expected. +AT_CHECK( + [[ovsdb-client transact ' + ["ordinals", + {"op": "insert", + "table": "ordinals", + "row": {"number": 6}}, + {"op": "comment", + "comment": "add row for 6"}]' | uuidfilt]], [0], + [[[{"uuid":["uuid","<0>"]},{}] +]]) +AT_CHECK([test `wc -l < db` -eq 6]) +AT_CHECK([ovsdb-client dump unix:db.sock ordinals | uuidfilt], [0], [dnl +ordinals table +_uuid number +------------------------------------ ------ +<0> 0 +<1> 1 +<2> 2 +<3> 3 +<4> 4 +<5> 5 +<6> 6 +]) +dnl Now kill and restart the database server to ensure that the data is +dnl correct on disk as well as in memory. +OVS_APP_EXIT_AND_WAIT([ovsdb-server]) +AT_CHECK([[ovsdb-server -vfile -vvlog:off --detach --no-chdir --pidfile --log-file --remote=punix:db.sock db]], + [0]) +AT_CHECK([ovsdb-client dump unix:db.sock ordinals | uuidfilt], [0], [dnl +ordinals table +_uuid number +------------------------------------ ------ +<0> 0 +<1> 1 +<2> 2 +<3> 3 +<4> 4 +<5> 5 +<6> 6 +]) + +dnl Make sure that "db" is still a symlink to dir/db instead of getting +dnl replaced by a regular file, ditto for .db.~lock~. +if test "$IS_WIN32" = "no"; then + AT_CHECK([test -h db]) + AT_CHECK([test -h .db.~lock~]) + AT_CHECK([test -f dir/db]) + AT_CHECK([test -f dir/.db.~lock~]) +fi + +OVS_APP_EXIT_AND_WAIT([ovsdb-server]) +AT_CLEANUP + AT_SETUP([ovsdb-server combines updates on backlogged connections]) on_exit 'kill `cat *.pid`' diff --git a/tests/test-ovsdb.c b/tests/test-ovsdb.c index c0c5a4df51af..8502ad73ff69 100644 --- a/tests/test-ovsdb.c +++ b/tests/test-ovsdb.c @@ -1522,14 +1522,14 @@ struct test_trigger { static void do_trigger_dump(struct test_trigger *t, long long int now, const char *title) { - struct json *result; + struct jsonrpc_msg *reply; char *s; - result = ovsdb_trigger_steal_result(&t->trigger); - s = json_to_string(result, JSSF_SORT); + reply = ovsdb_trigger_steal_reply(&t->trigger); + s = json_to_string(reply->result, JSSF_SORT); printf("t=%lld: trigger %d (%s): %s\n", now, t->number, title, s); free(s); - json_destroy(result); + jsonrpc_msg_destroy(reply); ovsdb_trigger_destroy(&t->trigger); free(t); } @@ -1569,8 +1569,10 @@ do_trigger(struct ovs_cmdl_context *ctx) json_destroy(params); } else { struct test_trigger *t = xmalloc(sizeof *t); - ovsdb_trigger_init(&session, db, &t->trigger, params, now, false, - NULL, NULL); + ovsdb_trigger_init(&session, db, &t->trigger, + jsonrpc_create_request("transact", params, + NULL), + now, false, NULL, NULL); t->number = number++; if (ovsdb_trigger_is_complete(&t->trigger)) { do_trigger_dump(t, now, "immediate");