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:
parent
10621d7953
commit
53178986d7
@ -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
|
||||
------------
|
||||
|
||||
|
@ -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
2
NEWS
@ -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.
|
||||
|
@ -1684,6 +1684,19 @@ ovsdb_datum_from_smap(struct ovsdb_datum *datum, const struct smap *smap)
|
||||
ovsdb_datum_sort_unique(datum, OVSDB_TYPE_STRING, OVSDB_TYPE_STRING);
|
||||
}
|
||||
|
||||
struct ovsdb_error * OVS_WARN_UNUSED_RESULT
|
||||
ovsdb_datum_convert(struct ovsdb_datum *dst,
|
||||
const struct ovsdb_type *dst_type,
|
||||
const struct ovsdb_datum *src,
|
||||
const struct ovsdb_type *src_type)
|
||||
{
|
||||
struct json *json = ovsdb_datum_to_json(src, src_type);
|
||||
struct ovsdb_error *error = ovsdb_datum_from_json(dst, dst_type, json,
|
||||
NULL);
|
||||
json_destroy(json);
|
||||
return error;
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
hash_atoms(enum ovsdb_atomic_type type, const union ovsdb_atom *atoms,
|
||||
unsigned int n, uint32_t basis)
|
||||
|
@ -192,6 +192,12 @@ void ovsdb_datum_to_bare(const struct ovsdb_datum *,
|
||||
|
||||
void ovsdb_datum_from_smap(struct ovsdb_datum *, const struct smap *);
|
||||
|
||||
struct ovsdb_error *ovsdb_datum_convert(struct ovsdb_datum *dst,
|
||||
const struct ovsdb_type *dst_type,
|
||||
const struct ovsdb_datum *src,
|
||||
const struct ovsdb_type *src_type)
|
||||
OVS_WARN_UNUSED_RESULT;
|
||||
|
||||
/* Comparison. */
|
||||
uint32_t ovsdb_datum_hash(const struct ovsdb_datum *,
|
||||
const struct ovsdb_type *, uint32_t basis);
|
||||
|
140
ovsdb/file.c
140
ovsdb/file.c
@ -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;
|
||||
}
|
||||
|
@ -51,4 +51,8 @@ void ovsdb_file_destroy(struct ovsdb_file *);
|
||||
|
||||
struct json *ovsdb_file_txn_annotate(struct json *, const char *comment);
|
||||
|
||||
struct ovsdb_error *ovsdb_file_convert(const struct ovsdb_file *,
|
||||
const struct ovsdb_schema *)
|
||||
OVS_WARN_UNUSED_RESULT;
|
||||
|
||||
#endif /* ovsdb/file.h */
|
||||
|
@ -82,7 +82,7 @@ static void ovsdb_jsonrpc_session_send(struct ovsdb_jsonrpc_session *,
|
||||
/* Triggers. */
|
||||
static void ovsdb_jsonrpc_trigger_create(struct ovsdb_jsonrpc_session *,
|
||||
struct ovsdb *,
|
||||
struct json *id, struct json *params);
|
||||
struct jsonrpc_msg *request);
|
||||
static struct ovsdb_jsonrpc_trigger *ovsdb_jsonrpc_trigger_find(
|
||||
struct ovsdb_jsonrpc_session *, const struct json *id, size_t hash);
|
||||
static void ovsdb_jsonrpc_trigger_complete(struct ovsdb_jsonrpc_trigger *);
|
||||
@ -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);
|
||||
|
@ -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 */
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -49,6 +49,8 @@ struct ovsdb_monitor *ovsdb_monitor_create(struct ovsdb *db,
|
||||
void ovsdb_monitors_remove(struct ovsdb *);
|
||||
void ovsdb_monitors_commit(struct ovsdb *, const struct ovsdb_txn *);
|
||||
|
||||
void ovsdb_monitor_prereplace_db(struct ovsdb *);
|
||||
|
||||
struct ovsdb_monitor *ovsdb_monitor_add(struct ovsdb_monitor *dbmon);
|
||||
|
||||
void ovsdb_monitor_add_jsonrpc_monitor(struct ovsdb_monitor *dbmon,
|
||||
|
@ -22,6 +22,9 @@ ovsdb\-client \- command-line interface to \fBovsdb-server\fR(1)
|
||||
.br
|
||||
\fBovsdb\-client\fR [\fIoptions\fR] \fBlist\-columns\fR [\fIserver\fR] [\fIdatabase\fR] [\fItable\fR]
|
||||
.IP "Database Version Management Commands:"
|
||||
\fBovsdb\-client \fR[\fIoptions\fR] \fBconvert \fR[\fIserver\fR] \fIschema\fR
|
||||
.br
|
||||
\fBovsdb\-client \fR[\fIoptions\fR] \fBneeds\-conversion \fR[\fIserver\fR] \fIschema\fR
|
||||
.br
|
||||
\fBovsdb\-client\fR [\fIoptions\fR] \fBget\-schema\-version\fR [\fIserver\fR] [\fIdatabase\fR]
|
||||
.IP "Data Management Commands:"
|
||||
@ -117,7 +120,43 @@ listed; otherwise, the tables include columns in all tables.
|
||||
These commands work with different versions of OVSDB schemas and
|
||||
databases.
|
||||
.
|
||||
.IP "\fBget\-schema\-version\fR [\fIserver\fR] [\fIdatabase\fR]"
|
||||
.IP "\fBconvert \fR[\fIserver\fR] \fIschema\fR"
|
||||
Reads an OVSDB schema in JSON format, as specified in the OVSDB
|
||||
specification, from \fIschema\fR, then connects to \fIserver\fR and
|
||||
requests the server to convert the database whose name is specified in
|
||||
\fIschema\fR to the schema also specified in \fIschema\fR.
|
||||
.IP
|
||||
The conversion is atomic, consistent, isolated, and durable.
|
||||
Following the schema change, the server notifies clients that use the
|
||||
\fBset_db_change_aware\fR RPC introduced in Open vSwitch 2.9 and
|
||||
cancels their outstanding transactions and monitors. The server
|
||||
disconnects other clients, enabling them to notice the change when
|
||||
they reconnect.
|
||||
.IP
|
||||
This command can do simple ``upgrades'' and ``downgrades'' on a
|
||||
database's schema. The data in the database must be valid when
|
||||
interpreted under \fIschema\fR, with only one exception: data for
|
||||
tables and columns that do not exist in \fIschema\fR are ignored.
|
||||
Columns that exist in \fIschema\fR but not in the database are set to
|
||||
their default values. All of \fIschema\fR's constraints apply in
|
||||
full.
|
||||
.IP
|
||||
Some uses of this command can cause unrecoverable data loss. For
|
||||
example, converting a database from a schema that has a given column
|
||||
or table to one that does not will delete all data in that column or
|
||||
table. Back up critical databases before converting them.
|
||||
.IP
|
||||
This command works with clustered and standalone databases.
|
||||
Standalone databases may also be converted (offline) with
|
||||
\fBovsdb\-tool\fR's \fBconvert\fR command.
|
||||
.
|
||||
.IP "\fBneeds\-conversion \fR[\fIserver\fR] \fIschema\fR"
|
||||
Reads the schema from \fIschema\fR, then connects to \fIserver\fR and
|
||||
requests the schema from the database whose name is specified in
|
||||
\fIschema\fR. If the two schemas are the same, prints \fBno\fR on
|
||||
stdout; if they differ, prints \fByes\fR.
|
||||
.
|
||||
.IP "\fBget\-schema\-version \fR[\fIserver\fR] [\fIdatabase\fR]"
|
||||
Connects to \fIserver\fR, retrieves the schema for \fIdatabase\fR, and
|
||||
prints its version number on stdout.
|
||||
If \fIdatabase\fR was created before schema versioning was introduced,
|
||||
|
@ -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 },
|
||||
|
@ -111,10 +111,11 @@ static char *open_db(struct server_config *config, const char *filename);
|
||||
static void add_server_db(struct server_config *);
|
||||
static void close_db(struct db *db);
|
||||
|
||||
static void parse_options(int *argc, char **argvp[],
|
||||
struct sset *remotes, char **unixctl_pathp,
|
||||
char **run_command, char **sync_from,
|
||||
char **sync_exclude, bool *is_backup);
|
||||
static void parse_options(int argc, char *argvp[],
|
||||
struct sset *db_filenames, struct sset *remotes,
|
||||
char **unixctl_pathp, char **run_command,
|
||||
char **sync_from, char **sync_exclude,
|
||||
bool *is_backup);
|
||||
OVS_NO_RETURN static void usage(void);
|
||||
|
||||
static char *reconfigure_remotes(struct ovsdb_jsonrpc_server *,
|
||||
@ -202,7 +203,9 @@ main_loop(struct ovsdb_jsonrpc_server *jsonrpc, struct shash *all_dbs,
|
||||
|
||||
SHASH_FOR_EACH(node, all_dbs) {
|
||||
struct db *db = node->data;
|
||||
ovsdb_trigger_run(db->db, time_msec());
|
||||
if (ovsdb_trigger_run(db->db, time_msec())) {
|
||||
ovsdb_jsonrpc_server_reconnect(jsonrpc, false);
|
||||
}
|
||||
}
|
||||
if (run_process) {
|
||||
process_run();
|
||||
@ -265,7 +268,6 @@ main(int argc, char *argv[])
|
||||
struct shash all_dbs;
|
||||
struct shash_node *node, *next;
|
||||
char *error;
|
||||
int i;
|
||||
|
||||
ovs_cmdl_proctitle_init(argc, argv);
|
||||
set_program_name(argv[0]);
|
||||
@ -274,8 +276,8 @@ main(int argc, char *argv[])
|
||||
process_init();
|
||||
|
||||
bool active = false;
|
||||
parse_options(&argc, &argv, &remotes, &unixctl_path, &run_command,
|
||||
&sync_from, &sync_exclude, &active);
|
||||
parse_options(argc, argv, &db_filenames, &remotes, &unixctl_path,
|
||||
&run_command, &sync_from, &sync_exclude, &active);
|
||||
is_backup = sync_from && !active;
|
||||
|
||||
daemon_become_new_user(false);
|
||||
@ -290,17 +292,6 @@ main(int argc, char *argv[])
|
||||
ovs_fatal(errno, "failed to create temporary file");
|
||||
}
|
||||
|
||||
sset_init(&db_filenames);
|
||||
if (argc > 0) {
|
||||
for (i = 0; i < argc; i++) {
|
||||
sset_add(&db_filenames, argv[i]);
|
||||
}
|
||||
} else {
|
||||
char *default_db = xasprintf("%s/conf.db", ovs_dbdir());
|
||||
sset_add(&db_filenames, default_db);
|
||||
free(default_db);
|
||||
}
|
||||
|
||||
server_config.remotes = &remotes;
|
||||
server_config.config_tmpfile = config_tmpfile;
|
||||
|
||||
@ -1477,8 +1468,9 @@ ovsdb_server_get_sync_status(struct unixctl_conn *conn, int argc OVS_UNUSED,
|
||||
}
|
||||
|
||||
static void
|
||||
parse_options(int *argcp, char **argvp[],
|
||||
struct sset *remotes, char **unixctl_pathp, char **run_command,
|
||||
parse_options(int argc, char *argv[],
|
||||
struct sset *db_filenames, struct sset *remotes,
|
||||
char **unixctl_pathp, char **run_command,
|
||||
char **sync_from, char **sync_exclude, bool *active)
|
||||
{
|
||||
enum {
|
||||
@ -1490,10 +1482,12 @@ parse_options(int *argcp, char **argvp[],
|
||||
OPT_SYNC_FROM,
|
||||
OPT_SYNC_EXCLUDE,
|
||||
OPT_ACTIVE,
|
||||
OPT_NO_DBS,
|
||||
VLOG_OPTION_ENUMS,
|
||||
DAEMON_OPTION_ENUMS,
|
||||
SSL_OPTION_ENUMS,
|
||||
};
|
||||
|
||||
static const struct option long_options[] = {
|
||||
{"remote", required_argument, NULL, OPT_REMOTE},
|
||||
{"unixctl", required_argument, NULL, OPT_UNIXCTL},
|
||||
@ -1510,14 +1504,15 @@ parse_options(int *argcp, char **argvp[],
|
||||
{"sync-from", required_argument, NULL, OPT_SYNC_FROM},
|
||||
{"sync-exclude-tables", required_argument, NULL, OPT_SYNC_EXCLUDE},
|
||||
{"active", no_argument, NULL, OPT_ACTIVE},
|
||||
{"no-dbs", no_argument, NULL, OPT_NO_DBS},
|
||||
{NULL, 0, NULL, 0},
|
||||
};
|
||||
char *short_options = ovs_cmdl_long_options_to_short_options(long_options);
|
||||
int argc = *argcp;
|
||||
char **argv = *argvp;
|
||||
bool add_default_db = true;
|
||||
|
||||
*sync_from = NULL;
|
||||
*sync_exclude = NULL;
|
||||
sset_init(db_filenames);
|
||||
sset_init(remotes);
|
||||
for (;;) {
|
||||
int c;
|
||||
@ -1596,6 +1591,10 @@ parse_options(int *argcp, char **argvp[],
|
||||
*active = true;
|
||||
break;
|
||||
|
||||
case OPT_NO_DBS:
|
||||
add_default_db = false;
|
||||
break;
|
||||
|
||||
case '?':
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
@ -1605,8 +1604,15 @@ parse_options(int *argcp, char **argvp[],
|
||||
}
|
||||
free(short_options);
|
||||
|
||||
*argcp -= optind;
|
||||
*argvp += optind;
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
if (argc > 0) {
|
||||
for (int i = 0; i < argc; i++) {
|
||||
sset_add(db_filenames, argv[i]);
|
||||
}
|
||||
} else if (add_default_db) {
|
||||
sset_add_and_free(db_filenames, xasprintf("%s/conf.db", ovs_dbdir()));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include "simap.h"
|
||||
#include "table.h"
|
||||
#include "transaction.h"
|
||||
#include "trigger.h"
|
||||
|
||||
struct ovsdb_schema *
|
||||
ovsdb_schema_create(const char *name, const char *version, const char *cksum)
|
||||
@ -162,7 +163,7 @@ root_set_size(const struct ovsdb_schema *schema)
|
||||
}
|
||||
|
||||
struct ovsdb_error *
|
||||
ovsdb_schema_from_json(struct json *json, struct ovsdb_schema **schemap)
|
||||
ovsdb_schema_from_json(const struct json *json, struct ovsdb_schema **schemap)
|
||||
{
|
||||
struct ovsdb_schema *schema;
|
||||
const struct json *name, *tables, *version_json, *cksum;
|
||||
@ -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)
|
||||
{
|
||||
|
@ -45,7 +45,7 @@ void ovsdb_schema_destroy(struct ovsdb_schema *);
|
||||
struct ovsdb_error *ovsdb_schema_from_file(const char *file_name,
|
||||
struct ovsdb_schema **)
|
||||
OVS_WARN_UNUSED_RESULT;
|
||||
struct ovsdb_error *ovsdb_schema_from_json(struct json *,
|
||||
struct ovsdb_error *ovsdb_schema_from_json(const struct json *,
|
||||
struct ovsdb_schema **)
|
||||
OVS_WARN_UNUSED_RESULT;
|
||||
struct json *ovsdb_schema_to_json(const struct ovsdb_schema *);
|
||||
@ -68,6 +68,7 @@ struct ovsdb {
|
||||
};
|
||||
|
||||
struct ovsdb *ovsdb_create(struct ovsdb_schema *);
|
||||
void ovsdb_replace(struct ovsdb *dst, struct ovsdb *src);
|
||||
void ovsdb_destroy(struct ovsdb *);
|
||||
|
||||
void ovsdb_get_memory_usage(const struct ovsdb *, struct simap *usage);
|
||||
|
@ -806,8 +806,14 @@ update_version(struct ovsdb_txn *txn OVS_UNUSED, struct ovsdb_txn_row *txn_row)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct ovsdb_error *
|
||||
ovsdb_txn_commit_(struct ovsdb_txn *txn, bool durable)
|
||||
static bool
|
||||
ovsdb_txn_is_empty(const struct ovsdb_txn *txn)
|
||||
{
|
||||
return ovs_list_is_empty(&txn->txn_tables);
|
||||
}
|
||||
|
||||
struct ovsdb_error * OVS_WARN_UNUSED_RESULT
|
||||
ovsdb_txn_start_commit(struct ovsdb_txn *txn)
|
||||
{
|
||||
struct ovsdb_error *error;
|
||||
|
||||
@ -818,29 +824,25 @@ ovsdb_txn_commit_(struct ovsdb_txn *txn, bool durable)
|
||||
ovsdb_txn_abort(txn);
|
||||
return OVSDB_WRAP_BUG("can't happen", error);
|
||||
}
|
||||
if (ovs_list_is_empty(&txn->txn_tables)) {
|
||||
ovsdb_txn_abort(txn);
|
||||
if (ovsdb_txn_is_empty(txn)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Update reference counts and check referential integrity. */
|
||||
error = update_ref_counts(txn);
|
||||
if (error) {
|
||||
ovsdb_txn_abort(txn);
|
||||
return error;
|
||||
}
|
||||
|
||||
/* Delete unreferenced, non-root rows. */
|
||||
error = for_each_txn_row(txn, collect_garbage);
|
||||
if (error) {
|
||||
ovsdb_txn_abort(txn);
|
||||
return OVSDB_WRAP_BUG("can't happen", error);
|
||||
}
|
||||
|
||||
/* Check maximum rows table constraints. */
|
||||
error = check_max_rows(txn);
|
||||
if (error) {
|
||||
ovsdb_txn_abort(txn);
|
||||
return error;
|
||||
}
|
||||
|
||||
@ -848,14 +850,12 @@ ovsdb_txn_commit_(struct ovsdb_txn *txn, bool durable)
|
||||
* integrity. */
|
||||
error = for_each_txn_row(txn, assess_weak_refs);
|
||||
if (error) {
|
||||
ovsdb_txn_abort(txn);
|
||||
return error;
|
||||
}
|
||||
|
||||
/* Verify that the indexes will still be unique post-transaction. */
|
||||
error = for_each_txn_row(txn, check_index_uniqueness);
|
||||
if (error) {
|
||||
ovsdb_txn_abort(txn);
|
||||
return error;
|
||||
}
|
||||
|
||||
@ -865,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
|
||||
|
@ -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;
|
||||
|
||||
|
144
ovsdb/trigger.c
144
ovsdb/trigger.c
@ -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);
|
||||
}
|
||||
|
@ -25,8 +25,8 @@ struct ovsdb_trigger {
|
||||
struct ovsdb *db; /* Database on which trigger acts. */
|
||||
struct ovs_list node; /* !result: in db->triggers;
|
||||
* result: in session->completions. */
|
||||
struct json *request; /* Database request. */
|
||||
struct json *result; /* Result (null if none yet). */
|
||||
struct jsonrpc_msg *request; /* Database request. */
|
||||
struct jsonrpc_msg *reply; /* Result (null if none yet). */
|
||||
long long int created; /* Time created. */
|
||||
long long int timeout_msec; /* Max wait duration. */
|
||||
bool read_only; /* Database is in read only mode. */
|
||||
@ -34,17 +34,18 @@ struct ovsdb_trigger {
|
||||
char *id; /* ID, for role-based access controls. */
|
||||
};
|
||||
|
||||
void ovsdb_trigger_init(struct ovsdb_session *, struct ovsdb *,
|
||||
bool ovsdb_trigger_init(struct ovsdb_session *, struct ovsdb *,
|
||||
struct ovsdb_trigger *,
|
||||
struct json *request, long long int now,
|
||||
bool read_only, const char *role,
|
||||
const char *id);
|
||||
struct jsonrpc_msg *request, long long int now,
|
||||
bool read_only, const char *role, const char *id);
|
||||
void ovsdb_trigger_destroy(struct ovsdb_trigger *);
|
||||
|
||||
bool ovsdb_trigger_is_complete(const struct ovsdb_trigger *);
|
||||
struct json *ovsdb_trigger_steal_result(struct ovsdb_trigger *);
|
||||
struct jsonrpc_msg *ovsdb_trigger_steal_reply(struct ovsdb_trigger *);
|
||||
|
||||
void ovsdb_trigger_run(struct ovsdb *, long long int now);
|
||||
void ovsdb_trigger_prereplace_db(struct ovsdb_trigger *);
|
||||
|
||||
bool ovsdb_trigger_run(struct ovsdb *, long long int now);
|
||||
void ovsdb_trigger_wait(struct ovsdb *, long long int now);
|
||||
|
||||
#endif /* ovsdb/trigger.h */
|
||||
|
@ -29,11 +29,11 @@ m4_define([OVSDB_CHECK_MONITOR],
|
||||
on_exit 'kill `cat ovsdb-server.pid`'
|
||||
AT_CAPTURE_FILE([ovsdb-client-log])
|
||||
if test "$IS_WIN32" = "yes"; then
|
||||
AT_CHECK([ovsdb-client -vjsonrpc --pidfile --log-file="`pwd`"/ovsdb-client-log -d json monitor --format=csv unix:socket $4 $5 $8 > output 2>/dev/null &],
|
||||
AT_CHECK([ovsdb-client -vjsonrpc --detach --pidfile --log-file="`pwd`"/ovsdb-client-log -d json monitor --format=csv unix:socket $4 $5 $8 > output],
|
||||
[0], [ignore], [ignore])
|
||||
sleep 1
|
||||
else
|
||||
AT_CHECK([ovsdb-client -vjsonrpc --detach --no-chdir --pidfile --log-file="`pwd`"/ovsdb-client-log -d json monitor --format=csv unix:socket $4 $5 $8 > output],
|
||||
AT_CHECK([ovsdb-client -vjsonrpc --detach --no-chdir --pidfile --log-file="`pwd`"/ovsdb-client-log -d json monitor --format=csv unix:socket $4 $5 $8 > output 2>/dev/null],
|
||||
[0], [ignore], [ignore])
|
||||
fi
|
||||
on_exit 'kill `cat ovsdb-client.pid`'
|
||||
|
@ -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`'
|
||||
|
||||
|
@ -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");
|
||||
|
Loading…
x
Reference in New Issue
Block a user