With this change, "ovsdb-client convert" can be used to convert a database from one schema to another without taking the database offline.
Signed-off-by: Ben Pfaff <[email protected]> --- 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/log.c | 144 ++++++++++++++++++++++++---- ovsdb/log.h | 14 ++- ovsdb/monitor.c | 19 +++- ovsdb/monitor.h | 2 + ovsdb/ovsdb-client.1.in | 39 ++++++++ ovsdb/ovsdb-client.c | 108 +++++++++++++++------ ovsdb/ovsdb-server.1.in | 43 +++++++++ ovsdb/ovsdb-server.c | 56 ++++++----- ovsdb/ovsdb.7.xml | 9 +- ovsdb/ovsdb.c | 26 +++++- ovsdb/ovsdb.h | 3 +- ovsdb/transaction.c | 37 +++++--- ovsdb/transaction.h | 5 + ovsdb/trigger.c | 132 ++++++++++++++++++++------ ovsdb/trigger.h | 17 ++-- tests/ovsdb-log.at | 2 +- tests/ovsdb-monitor.at | 4 +- tests/ovsdb-server.at | 243 +++++++++++++++++++++++++++++++++++++++++++++++- tests/test-ovsdb.c | 16 ++-- 26 files changed, 984 insertions(+), 218 deletions(-) diff --git a/NEWS b/NEWS index 31f4b884bf55..f678e7caedb9 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,8 @@ Post-v2.8.0 - OVSDB: * New high-level documentation in ovsdb(7). * New file format documentation for developers in ovsdb(5). + * 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. - ovs-vsctl and other commands that display data in tables now support a diff --git a/lib/ovsdb-data.c b/lib/ovsdb-data.c index 711d84d9badc..b4ea3dcac227 100644 --- a/lib/ovsdb-data.c +++ b/lib/ovsdb-data.c @@ -1650,6 +1650,19 @@ ovsdb_datum_from_smap(struct ovsdb_datum *datum, const struct smap *smap) ovsdb_datum_sort_unique(datum, OVSDB_TYPE_STRING, OVSDB_TYPE_STRING); } +struct ovsdb_error * OVS_WARN_UNUSED_RESULT +ovsdb_datum_convert(struct ovsdb_datum *dst, + const struct ovsdb_type *dst_type, + const struct ovsdb_datum *src, + const struct ovsdb_type *src_type) +{ + struct json *json = ovsdb_datum_to_json(src, src_type); + struct ovsdb_error *error = ovsdb_datum_from_json(dst, dst_type, json, + NULL); + json_destroy(json); + return error; +} + static uint32_t hash_atoms(enum ovsdb_atomic_type type, const union ovsdb_atom *atoms, unsigned int n, uint32_t basis) diff --git a/lib/ovsdb-data.h b/lib/ovsdb-data.h index 257e58e2a653..835d0a8a14e7 100644 --- a/lib/ovsdb-data.h +++ b/lib/ovsdb-data.h @@ -188,6 +188,12 @@ void ovsdb_datum_to_bare(const struct ovsdb_datum *, void ovsdb_datum_from_smap(struct ovsdb_datum *, const struct smap *); +struct ovsdb_error *ovsdb_datum_convert(struct ovsdb_datum *dst, + const struct ovsdb_type *dst_type, + const struct ovsdb_datum *src, + const struct ovsdb_type *src_type) + OVS_WARN_UNUSED_RESULT; + /* Comparison. */ uint32_t ovsdb_datum_hash(const struct ovsdb_datum *, const struct ovsdb_type *, uint32_t basis); diff --git a/ovsdb/file.c b/ovsdb/file.c index 6b2adad2759c..54e5df15a2cd 100644 --- a/ovsdb/file.c +++ b/ovsdb/file.c @@ -559,22 +559,31 @@ ovsdb_file_change_cb(const struct ovsdb_row *old, return true; } -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; } @@ -846,3 +855,115 @@ ovsdb_file_txn_commit(struct json *json, const char *comment, return NULL; } + +static struct ovsdb_error * OVS_WARN_UNUSED_RESULT +ovsdb_convert_table(struct ovsdb_txn *txn, + const struct ovsdb_table *src_table, + struct ovsdb_table *dst_table) +{ + const struct ovsdb_row *src_row; + HMAP_FOR_EACH (src_row, hmap_node, &src_table->rows) { + struct ovsdb_row *dst_row = ovsdb_row_create(dst_table); + *ovsdb_row_get_uuid_rw(dst_row) = *ovsdb_row_get_uuid(src_row); + + struct shash_node *node; + SHASH_FOR_EACH (node, &src_table->schema->columns) { + const struct ovsdb_column *src_column = node->data; + if (src_column->index == OVSDB_COL_UUID || + src_column->index == OVSDB_COL_VERSION) { + continue; + } + + const struct ovsdb_column *dst_column + = shash_find_data(&dst_table->schema->columns, + src_column->name); + if (!dst_column) { + continue; + } + + struct ovsdb_error *error = ovsdb_datum_convert( + &dst_row->fields[dst_column->index], &dst_column->type, + &src_row->fields[src_column->index], &src_column->type); + if (error) { + ovsdb_row_destroy(dst_row); + return error; + } + } + + ovsdb_txn_row_insert(txn, dst_row); + } + return NULL; +} + +struct ovsdb_error * OVS_WARN_UNUSED_RESULT +ovsdb_file_convert(const struct ovsdb_file *file, + const struct ovsdb_schema *new_schema) +{ + struct ovsdb *new_db = ovsdb_create(ovsdb_schema_clone(new_schema)); + struct ovsdb_txn *txn = ovsdb_txn_create(new_db); + struct ovsdb_error *error = NULL; + + struct shash_node *node; + SHASH_FOR_EACH (node, &file->db->tables) { + const char *table_name = node->name; + const struct ovsdb_table *src_table = node->data; + struct ovsdb_table *dst_table = shash_find_data(&new_db->tables, + table_name); + if (!dst_table) { + continue; + } + + error = ovsdb_convert_table(txn, src_table, dst_table); + if (error) { + goto error; + } + } + + error = ovsdb_txn_start_commit(txn); + if (error) { + goto error; + } + + struct ovsdb_log *new; + error = ovsdb_log_replace_start(file->log, &new); + if (error) { + goto error; + } + + /* Write schema. */ + struct json *schema_json = ovsdb_schema_to_json(new_schema); + error = ovsdb_log_write(new, schema_json); + json_destroy(schema_json); + if (error) { + goto error; + } + + /* Write data. */ + struct json *txn_json = ovsdb_file_txn_to_json(txn); + if (txn_json) { + error = ovsdb_log_write(new, txn_json); + json_destroy(txn_json); + if (error) { + goto error; + } + } + + error = ovsdb_log_replace_commit(file->log, new); + if (error) { + goto error; + } + + error = ovsdb_txn_finish_commit(txn, true); + ovs_assert(!error); /* Can't happen. */ + + ovsdb_replace(file->db, new_db); + + return NULL; + +error: + ovsdb_destroy(new_db); + if (txn) { + ovsdb_txn_abort(txn); + } + return error; +} diff --git a/ovsdb/file.h b/ovsdb/file.h index d4b8c2015815..30f211c431dc 100644 --- a/ovsdb/file.h +++ b/ovsdb/file.h @@ -49,4 +49,8 @@ struct ovsdb_error *ovsdb_file_commit(struct ovsdb_file *, const struct ovsdb_txn *, bool durable); void ovsdb_file_destroy(struct ovsdb_file *); +struct ovsdb_error *ovsdb_file_convert(const struct ovsdb_file *, + const struct ovsdb_schema *) + OVS_WARN_UNUSED_RESULT; + #endif /* ovsdb/file.h */ diff --git a/ovsdb/jsonrpc-server.c b/ovsdb/jsonrpc-server.c index a667dbe67f5f..8b2a4648bb1d 100644 --- a/ovsdb/jsonrpc-server.c +++ b/ovsdb/jsonrpc-server.c @@ -82,7 +82,7 @@ static void ovsdb_jsonrpc_session_send(struct ovsdb_jsonrpc_session *, /* Triggers. */ static void ovsdb_jsonrpc_trigger_create(struct ovsdb_jsonrpc_session *, struct ovsdb *, - struct json *id, struct json *params); + struct jsonrpc_msg *request); static struct ovsdb_jsonrpc_trigger *ovsdb_jsonrpc_trigger_find( struct ovsdb_jsonrpc_session *, const struct json *id, size_t hash); static void ovsdb_jsonrpc_trigger_complete(struct ovsdb_jsonrpc_trigger *); @@ -936,17 +936,6 @@ ovsdb_jsonrpc_session_unlock(struct ovsdb_jsonrpc_session *s, } static struct jsonrpc_msg * -execute_transaction(struct ovsdb_jsonrpc_session *s, struct ovsdb *db, - struct jsonrpc_msg *request) -{ - ovsdb_jsonrpc_trigger_create(s, db, request->id, request->params); - request->id = NULL; - request->params = NULL; - jsonrpc_msg_destroy(request); - return NULL; -} - -static struct jsonrpc_msg * ovsdb_jsonrpc_session_set_db_change_aware(struct ovsdb_jsonrpc_session *s, const struct jsonrpc_msg *request) { @@ -967,10 +956,11 @@ ovsdb_jsonrpc_session_got_request(struct ovsdb_jsonrpc_session *s, { struct jsonrpc_msg *reply; - if (!strcmp(request->method, "transact")) { + if (!strcmp(request->method, "transact") || + !strcmp(request->method, "convert_db")) { 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); @@ -1599,32 +1582,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 @@ -1632,14 +1595,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 * @@ -1670,8 +1643,19 @@ ovsdb_jsonrpc_monitor_needs_flush(struct ovsdb_jsonrpc_session *s) } void -ovsdb_jsonrpc_monitor_destroy(struct ovsdb_jsonrpc_monitor *m) -{ +ovsdb_jsonrpc_monitor_destroy(struct ovsdb_jsonrpc_monitor *m, + bool notify_cancellation) +{ + if (notify_cancellation) { + struct ovsdb_jsonrpc_session *s = m->session; + if (jsonrpc_session_is_connected(s->js) && s->db_change_aware) { + struct jsonrpc_msg *notify = jsonrpc_create_notify( + "monitor_canceled", + json_array_create_1(json_clone(m->monitor_id))); + ovsdb_jsonrpc_session_send(s, notify); + } + } + json_destroy(m->monitor_id); hmap_remove(&m->session->monitors, &m->node); ovsdb_monitor_remove_jsonrpc_monitor(m->dbmon, m, m->unflushed); diff --git a/ovsdb/jsonrpc-server.h b/ovsdb/jsonrpc-server.h index 50a8b879c5a9..0fc16f21b2d9 100644 --- a/ovsdb/jsonrpc-server.h +++ b/ovsdb/jsonrpc-server.h @@ -80,7 +80,8 @@ const struct uuid *ovsdb_jsonrpc_server_get_uuid( const struct ovsdb_jsonrpc_server *); struct ovsdb_jsonrpc_monitor; -void ovsdb_jsonrpc_monitor_destroy(struct ovsdb_jsonrpc_monitor *); +void ovsdb_jsonrpc_monitor_destroy(struct ovsdb_jsonrpc_monitor *, + bool notify_cancellation); void ovsdb_jsonrpc_disable_monitor_cond(void); #endif /* ovsdb/jsonrpc-server.h */ diff --git a/ovsdb/log.c b/ovsdb/log.c index 4b0d9640cafd..a223d30ac28b 100644 --- a/ovsdb/log.c +++ b/ovsdb/log.c @@ -92,26 +92,24 @@ ovsdb_log_open(const char *name, const char *magic, lockfile = NULL; } - if (open_mode == OVSDB_LOG_READ_ONLY) { + switch (open_mode) { + case OVSDB_LOG_READ_ONLY: flags = O_RDONLY; - } else if (open_mode == OVSDB_LOG_READ_WRITE) { + break; + + case OVSDB_LOG_READ_WRITE: flags = O_RDWR; - } else if (open_mode == OVSDB_LOG_CREATE) { -#ifndef _WIN32 - if (stat(name, &s) == -1 && errno == ENOENT - && lstat(name, &s) == 0 && S_ISLNK(s.st_mode)) { - /* 'name' is a dangling symlink. We want to create the file that - * the symlink points to, but POSIX says that open() with O_EXCL - * must fail with EEXIST if the named file is a symlink. So, we - * have to leave off O_EXCL and accept the race. */ - flags = O_RDWR | O_CREAT; - } else { - flags = O_RDWR | O_CREAT | O_EXCL; - } -#else + break; + + case OVSDB_LOG_CREATE_EXCL: flags = O_RDWR | O_CREAT | O_EXCL; -#endif - } else { + break; + + case OVSDB_LOG_CREATE: + flags = O_RDWR | O_CREAT; + break; + + default: OVS_NOT_REACHED(); } #ifdef _WIN32 @@ -119,7 +117,9 @@ ovsdb_log_open(const char *name, const char *magic, #endif fd = open(name, flags, 0666); if (fd < 0) { - const char *op = open_mode == OVSDB_LOG_CREATE ? "create" : "open"; + const char *op = (open_mode == OVSDB_LOG_CREATE_EXCL ? "create" + : open_mode == OVSDB_LOG_CREATE ? "create or open" + : "open"); error = ovsdb_io_error(errno, "%s: %s failed", name, op); goto error_unlock; } @@ -182,7 +182,9 @@ ovsdb_log_close(struct ovsdb_log *file) { if (file) { free(file->name); - fclose(file->stream); + if (file->stream) { + fclose(file->stream); + } lockfile_unlock(file->lockfile); ovsdb_error_destroy(file->read_error); free(file); @@ -433,3 +435,107 @@ ovsdb_log_get_offset(const struct ovsdb_log *log) { return log->offset; } + +struct ovsdb_error * OVS_WARN_UNUSED_RESULT +ovsdb_log_replace(struct ovsdb_log *log, struct json **entries, size_t n) +{ + struct ovsdb_error *error; + struct ovsdb_log *new; + + error = ovsdb_log_replace_start(log, &new); + if (error) { + return error; + } + + for (size_t i = 0; i < n; i++) { + error = ovsdb_log_write(new, entries[i]); + if (error) { + ovsdb_log_replace_abort(new); + return error; + } + } + + return ovsdb_log_replace_commit(log, new); +} + +struct ovsdb_error * OVS_WARN_UNUSED_RESULT +ovsdb_log_replace_start(struct ovsdb_log *old, + struct ovsdb_log **newp) +{ + char *tmp_name = xasprintf("%s.tmp", old->name); + struct ovsdb_error *error; + + ovs_assert(old->lockfile); + + /* Remove temporary file. (It might not exist.) */ + if (unlink(tmp_name) < 0 && errno != ENOENT) { + error = ovsdb_io_error(errno, "failed to remove %s", tmp_name); + free(tmp_name); + *newp = NULL; + return error; + } + + /* Create temporary file. */ + error = ovsdb_log_open(tmp_name, old->magic, OVSDB_LOG_CREATE_EXCL, + false, newp); + if (error) { + free(tmp_name); + *newp = NULL; + } + return error; +} + +struct ovsdb_error * OVS_WARN_UNUSED_RESULT +ovsdb_log_replace_commit(struct ovsdb_log *old, struct ovsdb_log *new) +{ + struct ovsdb_error *error = ovsdb_log_commit(new); + if (error) { + ovsdb_log_close(new); + return error; + } + + /* Replace old file by new file on-disk. */ + if (rename(new->name, old->name)) { + error = ovsdb_io_error(errno, "failed to rename \"%s\" to \"%s\"", + new->name, old->name); + ovsdb_log_close(new); + return error; + } + fsync_parent_dir(old->name); + + /* Replace 'old' by 'new' in memory. + * + * 'old' transitions to OVSDB_LOG_WRITE (it was probably in that mode + * anyway). */ + /* prev_offset only matters for OVSDB_LOG_READ. */ + old->offset = new->offset; + /* Keep old->name and old->rel_name. */ + free(old->magic); + old->magic = new->magic; + new->magic = NULL; + /* Keep old->lockfile. */ + fclose(old->stream); + old->stream = new->stream; + new->stream = NULL; + /* read_error only matters for OVSDB_LOG_READ. */ + old->write_error = new->write_error; + old->mode = OVSDB_LOG_WRITE; + + /* Free 'new'. */ + ovsdb_log_close(new); + + return NULL; +} + +void +ovsdb_log_replace_abort(struct ovsdb_log *new) +{ + if (new) { + /* Unlink the new file, but only after we close it (for Windows + * compatibility). */ + char *name = xstrdup(new->name); + ovsdb_log_close(new); + unlink(name); + free(name); + } +} diff --git a/ovsdb/log.h b/ovsdb/log.h index fd495e518dd0..439487ade12e 100644 --- a/ovsdb/log.h +++ b/ovsdb/log.h @@ -26,7 +26,8 @@ struct ovsdb_log; enum ovsdb_log_open_mode { OVSDB_LOG_READ_ONLY, /* Open existing file, read-only. */ OVSDB_LOG_READ_WRITE, /* Open existing file, read/write. */ - OVSDB_LOG_CREATE /* Create new file, read/write. */ + OVSDB_LOG_CREATE_EXCL, /* Create new file, read/write. */ + OVSDB_LOG_CREATE /* Create or open file, read/write. */ }; #define OVSDB_MAGIC "OVSDB JSON" @@ -48,4 +49,15 @@ struct ovsdb_error *ovsdb_log_commit(struct ovsdb_log *) off_t ovsdb_log_get_offset(const struct ovsdb_log *); +struct ovsdb_error *ovsdb_log_replace(struct ovsdb_log *, + struct json **entries, size_t n) + OVS_WARN_UNUSED_RESULT; +struct ovsdb_error *ovsdb_log_replace_start(struct ovsdb_log *old, + struct ovsdb_log **newp) + OVS_WARN_UNUSED_RESULT; +struct ovsdb_error *ovsdb_log_replace_commit(struct ovsdb_log *old, + struct ovsdb_log *new) + OVS_WARN_UNUSED_RESULT; +void ovsdb_log_replace_abort(struct ovsdb_log *new); + #endif /* ovsdb/log.h */ diff --git a/ovsdb/monitor.c b/ovsdb/monitor.c index 3e58c3fbd274..97706932614c 100644 --- a/ovsdb/monitor.c +++ b/ovsdb/monitor.c @@ -1613,7 +1613,7 @@ ovsdb_monitors_remove(struct ovsdb *db) * end monitor will also destroy the corresponding 'ovsdb_monitor'. * ovsdb monitor will also be destroied. */ LIST_FOR_EACH_SAFE (jm, next_jm, node, &m->jsonrpc_monitors) { - ovsdb_jsonrpc_monitor_destroy(jm->jsonrpc_monitor); + ovsdb_jsonrpc_monitor_destroy(jm->jsonrpc_monitor, false); } } } @@ -1630,3 +1630,20 @@ ovsdb_monitor_get_memory_usage(struct simap *usage) simap_increase(usage, "json-caches", hmap_count(&dbmon->json_cache)); } } + +void +ovsdb_monitor_prereplace_db(struct ovsdb *db) +{ + struct ovsdb_monitor *m, *next_m; + + LIST_FOR_EACH_SAFE (m, next_m, list_node, &db->monitors) { + struct jsonrpc_monitor_node *jm, *next_jm; + + /* Delete all front end monitors. Removing the last front + * end monitor will also destroy the corresponding 'ovsdb_monitor'. + * ovsdb monitor will also be destroied. */ + LIST_FOR_EACH_SAFE (jm, next_jm, node, &m->jsonrpc_monitors) { + ovsdb_jsonrpc_monitor_destroy(jm->jsonrpc_monitor, true); + } + } +} diff --git a/ovsdb/monitor.h b/ovsdb/monitor.h index 99d43c45dff9..eb3ff270c9f3 100644 --- a/ovsdb/monitor.h +++ b/ovsdb/monitor.h @@ -49,6 +49,8 @@ struct ovsdb_monitor *ovsdb_monitor_create(struct ovsdb *db, void ovsdb_monitors_remove(struct ovsdb *); void ovsdb_monitors_commit(struct ovsdb *, const struct ovsdb_txn *); +void ovsdb_monitor_prereplace_db(struct ovsdb *); + struct ovsdb_monitor *ovsdb_monitor_add(struct ovsdb_monitor *dbmon); void ovsdb_monitor_add_jsonrpc_monitor(struct ovsdb_monitor *dbmon, diff --git a/ovsdb/ovsdb-client.1.in b/ovsdb/ovsdb-client.1.in index 4b543d96e552..f8630ba40f25 100644 --- a/ovsdb/ovsdb-client.1.in +++ b/ovsdb/ovsdb-client.1.in @@ -22,6 +22,9 @@ ovsdb\-client \- command-line interface to \fBovsdb-server\fR(1) .br \fBovsdb\-client \fR[\fIoptions\fR] \fBlist\-columns\fI \fR[\fIserver\fR] \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\fI \fR[\fIserver\fR] \fR[\fIdatabase\fR] .IP "Data Management Commands:" @@ -110,6 +113,42 @@ listed; otherwise, the tables include columns in all tables. These commands work with different versions of OVSDB schemas and databases. . +.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 +cancel their outstanding transactions and monitors. The server +disconnects other clients, enabling them to notify 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\fI \fR[\fIserver\fR] \fR[\fIdatabase\fR]" Connects to \fIserver\fR, retrieves the schema for \fIdatabase\fR, and prints its version number on stdout. diff --git a/ovsdb/ovsdb-client.c b/ovsdb/ovsdb-client.c index cecc0346eda1..e12702200616 100644 --- a/ovsdb/ovsdb-client.c +++ b/ovsdb/ovsdb-client.c @@ -71,11 +71,17 @@ struct ovsdb_client_command { /* --timestamp: Print a timestamp before each update on "monitor" command? */ static bool timestamp; -/* --db-change-aware: Enable db_change_aware feature for "monitor" command? +/* --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 bool 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; /* Format for table output. */ static struct table_style table_style = TABLE_STYLE_DEFAULT; @@ -188,7 +194,6 @@ parse_options(int argc, char *argv[]) enum { OPT_BOOTSTRAP_CA_CERT = UCHAR_MAX + 1, OPT_TIMESTAMP, - OPT_DB_CHANGE_AWARE, VLOG_OPTION_ENUMS, DAEMON_OPTION_ENUMS, TABLE_OPTION_ENUMS, @@ -198,7 +203,8 @@ parse_options(int argc, char *argv[]) {"help", no_argument, NULL, 'h'}, {"version", no_argument, NULL, 'V'}, {"timestamp", no_argument, NULL, OPT_TIMESTAMP}, - {"db-change-aware", no_argument, NULL, OPT_DB_CHANGE_AWARE}, + {"db-change-aware", no_argument, &db_change_aware, 1}, + {"no-db-change-aware", no_argument, &db_change_aware, 0}, VLOG_LONG_OPTIONS, DAEMON_LONG_OPTIONS, #ifdef HAVE_OPENSSL @@ -241,10 +247,6 @@ parse_options(int argc, char *argv[]) timestamp = true; break; - case OPT_DB_CHANGE_AWARE: - db_change_aware = true; - break; - case '?': exit(EXIT_FAILURE); @@ -288,6 +290,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" @@ -530,6 +534,28 @@ do_list_columns(struct jsonrpc *rpc, const char *database, } 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 void do_transact(struct jsonrpc *rpc, const char *database OVS_UNUSED, int argc OVS_UNUSED, char *argv[]) { @@ -538,6 +564,13 @@ do_transact(struct jsonrpc *rpc, const char *database OVS_UNUSED, transaction = parse_json(argv[0]); + 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); print_json(reply->result); @@ -971,6 +1004,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; @@ -1028,22 +1062,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); @@ -1105,6 +1124,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); } @@ -1160,6 +1183,37 @@ 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_db", + 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); + if (ovsdb_schema_equal(schema1, schema2)) { + 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; @@ -1617,6 +1671,8 @@ static const struct ovsdb_client_command all_commands[] = { { "transact", NEED_RPC, 1, 1, do_transact }, { "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 }, { "lock", NEED_RPC, 1, 1, do_lock_create }, { "steal", NEED_RPC, 1, 1, do_lock_steal }, diff --git a/ovsdb/ovsdb-server.1.in b/ovsdb/ovsdb-server.1.in index 9bfd36edd351..02a887b94935 100644 --- a/ovsdb/ovsdb-server.1.in +++ b/ovsdb/ovsdb-server.1.in @@ -761,6 +761,49 @@ restores the default behavior. The reply is always the same: .fi .RE . +.IP "4.1.17. Online Database Schema Conversion" +.IP +Before Open vSwitch 2.9, a database's schema could be changed only by +taking its server offline, converting it using \fBovsdb\-tool +convert\fR, and then bringing it back online. Open vSwitch 2.9 +introduces the following RPC to convert the schema of a database +without taking it offline. +.PP +.RS +.nf +"method": "convert_db" +"params": [<database-schema>] +"id": <nonnull-json-value> +.fi +.RE +.IP +If the server successfully converts the database, the server replies +with: +.PP +.RS +.nf +"result": {} +"error": null +"id": same "id" as request +.fi +.RE +.IP +If the server cannot convert the database, it replies with an error: +.PP +.RS +.nf +"result": null +"error": <error> +"id": same "id" as request +.fi +.RE +.IP +If the new schema and the old schema are identical, the server does +not disrupt clients. Otherwise, when a database's schema changes, the +server disconnects all clients that are not database change aware and +sends \fBcancel\fR RPCs for outstanding transactions and +\fBmonitor_cancel\fR RPCs for ongoing monitors against the database +that changed. .IP "5.1. Notation" For <condition>, RFC 7047 only allows the use of \fB!=\fR, \fB==\fR, \fBincludes\fR, and \fBexcludes\fR operators with set types. Open diff --git a/ovsdb/ovsdb-server.c b/ovsdb/ovsdb-server.c index e28fca5989fc..65cf61ade954 100644 --- a/ovsdb/ovsdb-server.c +++ b/ovsdb/ovsdb-server.c @@ -111,10 +111,11 @@ static char *open_db(struct server_config *config, const char *filename); static void add_server_db(struct server_config *); static void close_db(struct db *db); -static void parse_options(int *argc, char **argvp[], - struct sset *remotes, char **unixctl_pathp, - char **run_command, char **sync_from, - char **sync_exclude, bool *is_backup); +static void parse_options(int argc, char *argvp[], + struct sset *db_filenames, struct sset *remotes, + char **unixctl_pathp, char **run_command, + char **sync_from, char **sync_exclude, + bool *is_backup); OVS_NO_RETURN static void usage(void); static char *reconfigure_remotes(struct ovsdb_jsonrpc_server *, @@ -202,7 +203,9 @@ main_loop(struct ovsdb_jsonrpc_server *jsonrpc, struct shash *all_dbs, SHASH_FOR_EACH(node, all_dbs) { struct db *db = node->data; - ovsdb_trigger_run(db->db, time_msec()); + if (ovsdb_trigger_run(db->db, time_msec())) { + ovsdb_jsonrpc_server_reconnect(jsonrpc, false); + } } if (run_process) { process_run(); @@ -265,7 +268,6 @@ main(int argc, char *argv[]) struct shash all_dbs; struct shash_node *node, *next; char *error; - int i; ovs_cmdl_proctitle_init(argc, argv); set_program_name(argv[0]); @@ -274,8 +276,8 @@ main(int argc, char *argv[]) process_init(); bool active = false; - parse_options(&argc, &argv, &remotes, &unixctl_path, &run_command, - &sync_from, &sync_exclude, &active); + parse_options(argc, argv, &db_filenames, &remotes, &unixctl_path, + &run_command, &sync_from, &sync_exclude, &active); is_backup = sync_from && !active; daemon_become_new_user(false); @@ -290,17 +292,6 @@ main(int argc, char *argv[]) ovs_fatal(errno, "failed to create temporary file"); } - sset_init(&db_filenames); - if (argc > 0) { - for (i = 0; i < argc; i++) { - sset_add(&db_filenames, argv[i]); - } - } else { - char *default_db = xasprintf("%s/conf.db", ovs_dbdir()); - sset_add(&db_filenames, default_db); - free(default_db); - } - server_config.remotes = &remotes; server_config.config_tmpfile = config_tmpfile; @@ -1463,8 +1454,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 { @@ -1476,10 +1468,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}, @@ -1496,14 +1490,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; @@ -1582,6 +1577,10 @@ parse_options(int *argcp, char **argvp[], *active = true; break; + case OPT_NO_DBS: + add_default_db = false; + break; + case '?': exit(EXIT_FAILURE); @@ -1591,8 +1590,15 @@ parse_options(int *argcp, char **argvp[], } free(short_options); - *argcp -= optind; - *argvp += optind; + argc -= optind; + argv += optind; + if (argc > 0) { + for (int i = 0; i < argc; i++) { + sset_add(db_filenames, argv[i]); + } + } else if (add_default_db) { + sset_add_and_free(db_filenames, xasprintf("%s/conf.db", ovs_dbdir())); + } } static void diff --git a/ovsdb/ovsdb.7.xml b/ovsdb/ovsdb.7.xml index 8d9a6ec97caf..efd7a622e2e0 100644 --- a/ovsdb/ovsdb.7.xml +++ b/ovsdb/ovsdb.7.xml @@ -461,11 +461,10 @@ OVSDB also supports online database schema conversion, for any of its database service models. To convert a database online, use <code>ovsdb-client convert</code>. The conversion is atomic, consistent, - isolated, and durable. The OVSDB protocol does not provide a way for the - server to notify a client that a database's schema has changed, so - currently <code>ovsdb-server</code> disconnects any clients connected when - the conversion takes place. Upon reconnction, clients will discover that - the schema has changed. + isolated, and durable. <code>ovsdb-server</code> disconnects any clients + connected when the conversion takes place (except clients that use the + <code>set_db_change_aware</code> Open vSwitch extension RPC). Upon + reconnection, clients will discover that the schema has changed. </p> <p> diff --git a/ovsdb/ovsdb.c b/ovsdb/ovsdb.c index 19755e673861..89f530bcccfb 100644 --- a/ovsdb/ovsdb.c +++ b/ovsdb/ovsdb.c @@ -27,6 +27,7 @@ #include "simap.h" #include "table.h" #include "transaction.h" +#include "trigger.h" struct ovsdb_schema * ovsdb_schema_create(const char *name, const char *version, const char *cksum) @@ -162,7 +163,7 @@ root_set_size(const struct ovsdb_schema *schema) } struct ovsdb_error * -ovsdb_schema_from_json(struct json *json, struct ovsdb_schema **schemap) +ovsdb_schema_from_json(const struct json *json, struct ovsdb_schema **schemap) { struct ovsdb_schema *schema; const struct json *name, *tables, *version_json, *cksum; @@ -361,6 +362,29 @@ ovsdb_create(struct ovsdb_schema *schema) } void +ovsdb_replace(struct ovsdb *dst, struct ovsdb *src) +{ + /* Cancel monitors. */ + ovsdb_monitor_prereplace_db(dst); + + /* Cancel triggers. */ + struct ovsdb_trigger *trigger, *next; + LIST_FOR_EACH_SAFE (trigger, next, node, &dst->triggers) { + ovsdb_trigger_prereplace_db(trigger); + } + + struct ovsdb_schema *tmp_schema = dst->schema; + dst->schema = src->schema; + src->schema = tmp_schema; + + shash_swap(&dst->tables, &src->tables); + + dst->rbac_role = ovsdb_get_table(dst, "RBAC_Role"); + + ovsdb_destroy(src); +} + +void ovsdb_destroy(struct ovsdb *db) { if (db) { diff --git a/ovsdb/ovsdb.h b/ovsdb/ovsdb.h index 9d915f0f15ae..c3e8f2091e35 100644 --- a/ovsdb/ovsdb.h +++ b/ovsdb/ovsdb.h @@ -45,7 +45,7 @@ void ovsdb_schema_destroy(struct ovsdb_schema *); struct ovsdb_error *ovsdb_schema_from_file(const char *file_name, struct ovsdb_schema **) OVS_WARN_UNUSED_RESULT; -struct ovsdb_error *ovsdb_schema_from_json(struct json *, +struct ovsdb_error *ovsdb_schema_from_json(const struct json *, struct ovsdb_schema **) OVS_WARN_UNUSED_RESULT; struct json *ovsdb_schema_to_json(const struct ovsdb_schema *); @@ -68,6 +68,7 @@ struct ovsdb { }; struct ovsdb *ovsdb_create(struct ovsdb_schema *); +void ovsdb_replace(struct ovsdb *dst, struct ovsdb *src); void ovsdb_destroy(struct ovsdb *); void ovsdb_get_memory_usage(const struct ovsdb *, struct simap *usage); diff --git a/ovsdb/transaction.c b/ovsdb/transaction.c index e6e13f650070..e38a174109bb 100644 --- a/ovsdb/transaction.c +++ b/ovsdb/transaction.c @@ -806,8 +806,14 @@ update_version(struct ovsdb_txn *txn OVS_UNUSED, struct ovsdb_txn_row *txn_row) return NULL; } -static struct ovsdb_error * -ovsdb_txn_commit_(struct ovsdb_txn *txn, bool durable) +static bool +ovsdb_txn_is_empty(const struct ovsdb_txn *txn) +{ + return ovs_list_is_empty(&txn->txn_tables); +} + +struct ovsdb_error * OVS_WARN_UNUSED_RESULT +ovsdb_txn_start_commit(struct ovsdb_txn *txn) { struct ovsdb_error *error; @@ -817,29 +823,25 @@ ovsdb_txn_commit_(struct ovsdb_txn *txn, bool durable) if (error) { 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; } @@ -847,14 +849,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; } @@ -864,9 +864,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; @@ -886,10 +893,12 @@ ovsdb_txn_commit_(struct ovsdb_txn *txn, bool durable) struct ovsdb_error * ovsdb_txn_commit(struct ovsdb_txn *txn, bool durable) { - struct ovsdb_error *err; - - PERF(__func__, err = ovsdb_txn_commit_(txn, durable)); - return err; + struct ovsdb_error *error = ovsdb_txn_start_commit(txn); + if (error || ovsdb_txn_is_empty(txn)) { + ovsdb_txn_abort(txn); + return error; + } + return ovsdb_txn_finish_commit(txn, durable); } void diff --git a/ovsdb/transaction.h b/ovsdb/transaction.h index 1ecd15a56a8d..f9b886411bf4 100644 --- a/ovsdb/transaction.h +++ b/ovsdb/transaction.h @@ -26,6 +26,11 @@ struct uuid; struct ovsdb_txn *ovsdb_txn_create(struct ovsdb *); void ovsdb_txn_abort(struct ovsdb_txn *); + +struct ovsdb_error *ovsdb_txn_start_commit(struct ovsdb_txn *) + OVS_WARN_UNUSED_RESULT; +struct ovsdb_error *ovsdb_txn_finish_commit(struct ovsdb_txn *, bool durable) + OVS_WARN_UNUSED_RESULT; struct ovsdb_error *ovsdb_txn_commit(struct ovsdb_txn *, bool durable) OVS_WARN_UNUSED_RESULT; diff --git a/ovsdb/trigger.c b/ovsdb/trigger.c index 1b960f5723ec..10b155f8bcb4 100644 --- a/ovsdb/trigger.c +++ b/ovsdb/trigger.c @@ -19,41 +19,47 @@ #include <limits.h> +#include "file.h" +#include "log.h" #include "openvswitch/json.h" #include "jsonrpc.h" #include "ovsdb.h" +#include "ovsdb-error.h" #include "poll-loop.h" #include "server.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_db")); 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); } @@ -61,30 +67,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_db")) { + /* We don't cancel convert_db 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_db request would end up getting canceled since + * convert_db 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 @@ -117,22 +146,71 @@ 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_db")) { + /* Validate parameters. */ + const struct json *params = t->request->params; + if (params->type != JSON_ARRAY || params->u.array.n != 2) { + trigger_error(t, ovsdb_syntax_error(params, NULL, + "array expected")); + return false; + } + + /* Parse new schema and make a converted copy. */ + const struct json *new_schema_json = params->u.array.elems[1]; + struct ovsdb_schema *new_schema; + struct ovsdb_error *error = ovsdb_schema_from_json(new_schema_json, + &new_schema); + if (!error && strcmp(new_schema->name, t->db->schema->name)) { + error = ovsdb_error( + "invalid parameters", + "new schema name (%s) does not match database name (%s)", + new_schema->name, t->db->schema->name); + } + if (!error) { + error = ovsdb_file_convert(t->db->file, new_schema); + } + ovsdb_schema_destroy(new_schema); + if (error) { + trigger_error(t, error); + return false; + } + + trigger_success(t, json_object_create()); return true; } else { - return false; + OVS_NOT_REACHED(); } } static void -ovsdb_trigger_complete(struct ovsdb_trigger *t) +ovsdb_trigger_complete(struct ovsdb_trigger *t, struct jsonrpc_msg *reply) { - ovs_assert(t->result != NULL); + ovs_assert(reply && !t->reply); + t->reply = reply; ovs_list_remove(&t->node); ovs_list_push_back(&t->session->completions, &t->node); } + +static void +trigger_error(struct ovsdb_trigger *t, struct ovsdb_error *error) +{ + struct jsonrpc_msg *reply = jsonrpc_create_error( + ovsdb_error_to_json_free(error), t->request->id); + ovsdb_trigger_complete(t, reply); +} + +static void +trigger_success(struct ovsdb_trigger *t, struct json *result) +{ + struct jsonrpc_msg *reply = jsonrpc_create_reply(result, t->request->id); + ovsdb_trigger_complete(t, reply); +} diff --git a/ovsdb/trigger.h b/ovsdb/trigger.h index 90246a4a42bd..d9df97f31222 100644 --- a/ovsdb/trigger.h +++ b/ovsdb/trigger.h @@ -25,8 +25,8 @@ struct ovsdb_trigger { struct ovsdb *db; /* Database on which trigger acts. */ struct ovs_list node; /* !result: in db->triggers; * result: in session->completions. */ - struct json *request; /* Database request. */ - struct json *result; /* Result (null if none yet). */ + struct jsonrpc_msg *request; /* Database request. */ + struct jsonrpc_msg *reply; /* Result (null if none yet).. */ long long int created; /* Time created. */ long long int timeout_msec; /* Max wait duration. */ bool read_only; /* Database is in read only mode. */ @@ -34,17 +34,18 @@ struct ovsdb_trigger { char *id; /* ID, for role-based access controls. */ }; -void ovsdb_trigger_init(struct ovsdb_session *, struct ovsdb *, +bool ovsdb_trigger_init(struct ovsdb_session *, struct ovsdb *, struct ovsdb_trigger *, - struct json *request, long long int now, - bool read_only, const char *role, - const char *id); + struct jsonrpc_msg *request, long long int now, + bool read_only, const char *role, const char *id); void ovsdb_trigger_destroy(struct ovsdb_trigger *); bool ovsdb_trigger_is_complete(const struct ovsdb_trigger *); -struct json *ovsdb_trigger_steal_result(struct ovsdb_trigger *); +struct jsonrpc_msg *ovsdb_trigger_steal_reply(struct ovsdb_trigger *); -void ovsdb_trigger_run(struct ovsdb *, long long int now); +void ovsdb_trigger_prereplace_db(struct ovsdb_trigger *); + +bool ovsdb_trigger_run(struct ovsdb *, long long int now); void ovsdb_trigger_wait(struct ovsdb *, long long int now); #endif /* ovsdb/trigger.h */ diff --git a/tests/ovsdb-log.at b/tests/ovsdb-log.at index fb9af12c657d..d295cedd639f 100644 --- a/tests/ovsdb-log.at +++ b/tests/ovsdb-log.at @@ -46,7 +46,7 @@ AT_CHECK( file: read: {"x":1} ]], [ignore]) AT_CHECK( - [test-ovsdb log-io file create read], [1], + [test-ovsdb log-io file create-excl read], [1], [], [test-ovsdb: I/O error: file: create failed (File exists) ]) AT_CHECK([test -f .file.~lock~]) diff --git a/tests/ovsdb-monitor.at b/tests/ovsdb-monitor.at index 256868b0248f..e12f4dc326db 100644 --- a/tests/ovsdb-monitor.at +++ b/tests/ovsdb-monitor.at @@ -29,11 +29,11 @@ m4_define([OVSDB_CHECK_MONITOR], on_exit 'kill `cat ovsdb-server.pid`' AT_CAPTURE_FILE([ovsdb-client-log]) if test "$IS_WIN32" = "yes"; then - AT_CHECK([ovsdb-client -vjsonrpc --pidfile --log-file="`pwd`"/ovsdb-client-log -d json monitor --format=csv unix:socket $4 $5 $8 > output 2>/dev/null &], + AT_CHECK([ovsdb-client -vjsonrpc --detach --pidfile --log-file="`pwd`"/ovsdb-client-log -d json monitor --format=csv unix:socket $4 $5 $8 > output], [0], [ignore], [ignore]) sleep 1 else - AT_CHECK([ovsdb-client -vjsonrpc --detach --no-chdir --pidfile --log-file="`pwd`"/ovsdb-client-log -d json monitor --format=csv unix:socket $4 $5 $8 > output], + AT_CHECK([ovsdb-client -vjsonrpc --detach --no-chdir --pidfile --log-file="`pwd`"/ovsdb-client-log -d json monitor --format=csv unix:socket $4 $5 $8 > output 2>/dev/null], [0], [ignore], [ignore]) fi on_exit 'kill `cat ovsdb-client.pid`' diff --git a/tests/ovsdb-server.at b/tests/ovsdb-server.at index 795427c5b1e5..280e37fd8525 100644 --- a/tests/ovsdb-server.at +++ b/tests/ovsdb-server.at @@ -176,7 +176,7 @@ AT_CHECK([ovsdb-tool create db1 schema1], [0], [ignore], [ignore]) AT_CHECK([ovsdb-tool create db2 schema2], [0], [ignore], [ignore]) # Start ovsdb-server with just a single database - db1. -AT_CHECK([ovsdb-server --detach --no-chdir --pidfile --remote=punix:db.sock db1], [0]) +AT_CHECK([ovsdb-server -vfile -vvlog:off --log-file --detach --no-chdir --pidfile --remote=punix:db.sock db1], [0]) CHECK_DBS([ordinals ]) @@ -185,8 +185,8 @@ AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/remove-db ordinals], [0]) CHECK_DBS([]) # Start monitoring processes. -AT_CHECK([ovsdb-client --detach --pidfile=ovsdb-client-1.pid --no-headings monitor _Server Database name > db-changes-unaware]) -AT_CHECK([ovsdb-client --detach --pidfile=ovsdb-client-2.pid --db-change-aware --no-headings monitor _Server Database name > db-changes-aware]) +AT_CHECK([ovsdb-client --detach --pidfile=ovsdb-client-1.pid --no-db-change-aware --no-headings monitor _Server Database name > db-change-unaware 2>/dev/null]) +AT_CHECK([ovsdb-client --detach --pidfile=ovsdb-client-2.pid --db-change-aware --no-headings monitor _Server Database name > db-change-aware 2>/dev/null]) # Add the first database back. AT_CHECK([ovs-appctl -t ovsdb-server ovsdb-server/add-db db1], [0]) @@ -258,7 +258,7 @@ CHECK_DBS([constraints AT_CHECK([ovsdb-client list-tables unix:db.sock constraints], [0], [ignore], [ignore]) # Check the monitoring results. -AT_CHECK([${PERL} $srcdir/uuidfilt.pl db-changes-aware], [0], [dnl +AT_CHECK([${PERL} $srcdir/uuidfilt.pl db-change-aware], [0], [dnl <0> initial _Server <1> insert ordinals @@ -271,7 +271,7 @@ AT_CHECK([${PERL} $srcdir/uuidfilt.pl db-changes-aware], [0], [dnl <3> insert constraints ]) -AT_CHECK([${PERL} $srcdir/uuidfilt.pl db-changes-unaware], [0], [dnl +AT_CHECK([${PERL} $srcdir/uuidfilt.pl db-change-unaware], [0], [dnl <0> initial _Server ]) @@ -781,6 +781,239 @@ _uuid name number OVSDB_SERVER_SHUTDOWN AT_CLEANUP +AT_SETUP([schema conversion online -- removing a column]) +AT_KEYWORDS([ovsdb server convert]) +on_exit 'kill `cat *.pid`' +ordinal_schema > schema +AT_DATA([new-schema], + [[{"name": "ordinals", + "tables": { + "ordinals": { + "columns": { + "number": {"type": "integer"}}}}} +]]) +touch .db.~lock~ +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 | ${PERL} $srcdir/uuidfilt.pl]], [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 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([${PERL} $srcdir/uuidfilt.pl 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 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 | ${PERL} $srcdir/uuidfilt.pl], [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 | ${PERL} $srcdir/uuidfilt.pl], [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 | ${PERL} $srcdir/uuidfilt.pl], [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"}]' | ${PERL} $srcdir/uuidfilt.pl]], [0], + [[[{"uuid":["uuid","<0>"]},{}] +]]) +AT_CHECK([test `wc -l < db` -eq 6]) +AT_CHECK([ovsdb-client dump unix:db.sock ordinals | ${PERL} $srcdir/uuidfilt.pl], [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 | ${PERL} $srcdir/uuidfilt.pl], [0], [dnl +ordinals table +_uuid number +------------------------------------ ------ +<0> 0 +<1> 1 +<2> 2 +<3> 3 +<4> 4 +<5> 5 +<6> 6 +]) +OVS_APP_EXIT_AND_WAIT([ovsdb-server]) +AT_CLEANUP + AT_SETUP([ovsdb-server combines updates on backlogged connections]) on_exit 'kill `cat *.pid`' diff --git a/tests/test-ovsdb.c b/tests/test-ovsdb.c index 9527c60d3620..1b0ec094c5e1 100644 --- a/tests/test-ovsdb.c +++ b/tests/test-ovsdb.c @@ -321,6 +321,8 @@ do_log_io(struct ovs_cmdl_context *ctx) mode = OVSDB_LOG_READ_WRITE; } else if (!strcmp(mode_string, "create")) { mode = OVSDB_LOG_CREATE; + } else if (!strcmp(mode_string, "create-excl")) { + mode = OVSDB_LOG_CREATE_EXCL; } else { ovs_fatal(0, "unknown log-io open mode \"%s\"", mode_string); } @@ -1477,14 +1479,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); } @@ -1524,8 +1526,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"); -- 2.10.2 _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
