2
0
mirror of https://github.com/openvswitch/ovs synced 2025-08-22 01:51:26 +00:00

ovsdb: Add support for online schema conversion.

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>
Acked-by: Justin Pettit <jpettit@ovn.org>
This commit is contained in:
Ben Pfaff 2017-12-28 13:21:11 -08:00
parent 10621d7953
commit 53178986d7
23 changed files with 876 additions and 187 deletions

View File

@ -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": [<db-name>, <database-schema>]
"id": <nonnull-json-value>
Upon receipt, the server converts database <db-name> to schema
<database-schema>. The schema's name must be <db-name>. 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
------------

View File

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

2
NEWS
View File

@ -26,6 +26,8 @@ v2.9.0 - 19 Feb 2018
* 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.

View File

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

View File

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

View File

@ -570,23 +570,31 @@ ovsdb_file_txn_annotate(struct json *json, const char *comment)
return json;
}
/* 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 and
* _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;
ovsdb_file_txn_init(&ftxn);
ovsdb_txn_for_each_change(txn, ovsdb_file_change_cb, &ftxn);
return ftxn.json;
}
struct ovsdb_error *
ovsdb_file_commit(struct ovsdb_file *file,
const struct ovsdb_txn *txn, bool durable)
{
struct ovsdb_file_txn ftxn;
struct ovsdb_error *error;
long long int current_time;
ovsdb_file_txn_init(&ftxn);
ovsdb_txn_for_each_change(txn, ovsdb_file_change_cb, &ftxn);
if (!ftxn.json) {
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;
}
@ -599,7 +607,7 @@ ovsdb_file_commit(struct ovsdb_file *file,
* size of the previous snapshot, then compact the database. However, if
* it has been over COMPACT_MAX_MSEC ms since the last compaction, the
* database size is not taken into account. */
current_time = time_msec();
long long int current_time = time_msec();
if (current_time >= file->next_compact
&& file->n_transactions >= 100
&& (current_time - file->last_compact >= COMPACT_MAX_MSEC
@ -857,3 +865,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;
}

View File

@ -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 */

View File

@ -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 *);
@ -929,17 +929,6 @@ ovsdb_jsonrpc_session_unlock(struct ovsdb_jsonrpc_session *s,
return jsonrpc_create_reply(json_object_create(), request->id);
}
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)
@ -961,10 +950,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,
@ -1076,37 +1066,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 *
@ -1133,12 +1121,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);
}
@ -1153,7 +1138,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) {
@ -1183,11 +1168,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);
}
}
@ -1435,7 +1418,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);
@ -1592,32 +1575,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
@ -1625,14 +1588,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 *
@ -1663,8 +1636,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);

View File

@ -79,7 +79,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 */

View File

@ -1612,7 +1612,7 @@ ovsdb_monitors_remove(struct ovsdb *db)
/* Delete all front-end monitors. Removing the last front-end monitor
* will also destroy the corresponding ovsdb_monitor. */
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);
}
}
}
@ -1629,3 +1629,19 @@ 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. */
LIST_FOR_EACH_SAFE (jm, next_jm, node, &m->jsonrpc_monitors) {
ovsdb_jsonrpc_monitor_destroy(jm->jsonrpc_monitor, true);
}
}
}

View File

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

View File

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

View File

@ -78,9 +78,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;
@ -316,6 +321,10 @@ 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 needs-conversion [SERVER] SCHEMA\n"
" tests whether SCHEMA's db on SERVER needs conversion.\n"
"\n monitor [SERVER] [DATABASE] ALL\n"
" monitor all changes to all columns in all tables\n"
" in DATBASE on SERVER.\n"
@ -570,11 +579,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);
@ -1053,6 +1091,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;
@ -1110,22 +1149,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);
@ -1187,6 +1211,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);
}
@ -1242,6 +1270,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;
@ -1941,6 +1998,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 },

View File

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

View File

@ -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;
@ -360,6 +361,29 @@ ovsdb_create(struct ovsdb_schema *schema)
return db;
}
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)
{

View File

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

View File

@ -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,14 +865,23 @@ ovsdb_txn_commit_(struct ovsdb_txn *txn, bool durable)
return OVSDB_WRAP_BUG("can't happen", error);
}
/* Commit to disk and send the commit to each replica. */
return NULL;
}
struct ovsdb_error *
ovsdb_txn_finish_commit(struct ovsdb_txn *txn, bool durable)
{
/* Commit to disk. */
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;
}
}
/* Send the transaction to each monitor. */
ovsdb_monitors_commit(txn->db, txn);
/* Finalize commit. */
@ -887,10 +896,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

View File

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

View File

@ -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 *);
void
static bool ovsdb_trigger_try(struct ovsdb_trigger *, long long int now);
static void trigger_error(struct ovsdb_trigger *, struct ovsdb_error *);
static void trigger_success(struct ovsdb_trigger *, struct json *result);
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);
}

View File

@ -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 */

View File

@ -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`'

View File

@ -789,6 +789,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`'

View File

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