@@ -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": [<database-schema>]
+ "id": <nonnull-json-value>
+
+Upon receipt, the server converts the database named in <database-schema> to
+that schema. The conversion is atomic, consistent, isolated, and durable. The
+data in the database must be valid when interpreted under <database-schema>,
+with only one exception: data for tables and columns that do not exist in the
+new schema are ignored. Columns that exist in <database-schema> 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": [<error>]
+ "id": same "id" as request
+
5.1 Notation
------------
@@ -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
-----------------------------
@@ -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.
@@ -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)
@@ -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);
@@ -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;
+}
@@ -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 */
@@ -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);
@@ -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 */
@@ -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);
+ }
+ }
+}
@@ -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,
@@ -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,
@@ -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 },
@@ -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
@@ -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) {
@@ -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);
@@ -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
@@ -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;
@@ -19,42 +19,48 @@
#include <limits.h>
+#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);
+}
@@ -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 */
@@ -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`'
@@ -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`'
@@ -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");
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 <blp@ovn.org> --- 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(-)