From 7360012bdf64effd898242a58634267e203a2795 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Mon, 15 Mar 2010 15:41:54 -0700 Subject: [PATCH] ovsdb: Add support for weak references. --- lib/ovsdb-types.c | 23 +++++ lib/ovsdb-types.h | 33 +++++++- ovsdb/OVSDB.py | 13 ++- ovsdb/SPECS | 33 ++++++-- ovsdb/row.c | 17 ++++ ovsdb/row.h | 34 ++++++-- ovsdb/transaction.c | 146 +++++++++++++++++++++++++++++++- tests/automake.mk | 2 +- tests/ovsdb-execution.at | 162 ++++++++++++++++++++++++++++++++++++ tests/ovsdb-monitor-sort.pl | 49 +++++++++++ tests/ovsdb-monitor.at | 62 ++++++++++---- tests/uuidfilt.pl | 12 +++ vswitchd/vswitch.ovsschema | 11 ++- 13 files changed, 561 insertions(+), 36 deletions(-) create mode 100755 tests/ovsdb-monitor-sort.pl diff --git a/lib/ovsdb-types.c b/lib/ovsdb-types.c index df18ee54c..b3452dd83 100644 --- a/lib/ovsdb-types.c +++ b/lib/ovsdb-types.c @@ -412,10 +412,30 @@ ovsdb_base_type_from_json(struct ovsdb_base_type *base, refTable = ovsdb_parser_member(&parser, "refTable", OP_ID | OP_OPTIONAL); if (refTable) { + const struct json *refType; + base->u.uuid.refTableName = xstrdup(refTable->u.string); + /* We can't set base->u.uuid.refTable here because we don't have * enough context (we might not even be running in ovsdb-server). * ovsdb_create() will set refTable later. */ + + refType = ovsdb_parser_member(&parser, "refType", + OP_ID | OP_OPTIONAL); + if (refType) { + const char *refType_s = json_string(refType); + if (!strcmp(refType_s, "strong")) { + base->u.uuid.refType = OVSDB_REF_STRONG; + } else if (!strcmp(refType_s, "weak")) { + base->u.uuid.refType = OVSDB_REF_WEAK; + } else { + error = ovsdb_syntax_error(json, NULL, "refType must be " + "\"strong\" or \"weak\" (not " + "\"%s\")", refType_s); + } + } else { + base->u.uuid.refType = OVSDB_REF_STRONG; + } } } @@ -495,6 +515,9 @@ ovsdb_base_type_to_json(const struct ovsdb_base_type *base) if (base->u.uuid.refTableName) { json_object_put_string(json, "refTable", base->u.uuid.refTableName); + if (base->u.uuid.refType == OVSDB_REF_WEAK) { + json_object_put_string(json, "refType", "weak"); + } } break; diff --git a/lib/ovsdb-types.h b/lib/ovsdb-types.h index 6f1727ed1..6903aa83f 100644 --- a/lib/ovsdb-types.h +++ b/lib/ovsdb-types.h @@ -44,6 +44,11 @@ struct json *ovsdb_atomic_type_to_json(enum ovsdb_atomic_type); /* An atomic type plus optional constraints. */ +enum ovsdb_ref_type { + OVSDB_REF_STRONG, /* Target must exist. */ + OVSDB_REF_WEAK /* Delete reference if target disappears. */ +}; + struct ovsdb_base_type { enum ovsdb_atomic_type type; @@ -72,6 +77,7 @@ struct ovsdb_base_type { struct ovsdb_uuid_constraints { char *refTableName; /* Name of referenced table, or NULL. */ struct ovsdb_table *refTable; /* Referenced table, if available. */ + enum ovsdb_ref_type refType; /* Reference type. */ } uuid; } u; }; @@ -85,7 +91,7 @@ struct ovsdb_base_type { #define OVSDB_BASE_STRING_INIT { .type = OVSDB_TYPE_STRING, \ .u.string = { 0, UINT_MAX } } #define OVSDB_BASE_UUID_INIT { .type = OVSDB_TYPE_UUID, \ - .u.uuid = { NULL, NULL } } + .u.uuid = { NULL, NULL, 0 } } void ovsdb_base_type_init(struct ovsdb_base_type *, enum ovsdb_atomic_type); void ovsdb_base_type_clone(struct ovsdb_base_type *, @@ -101,6 +107,11 @@ struct ovsdb_error *ovsdb_base_type_from_json(struct ovsdb_base_type *, const struct json *) WARN_UNUSED_RESULT; struct json *ovsdb_base_type_to_json(const struct ovsdb_base_type *); + +static inline bool ovsdb_base_type_is_ref(const struct ovsdb_base_type *); +static inline bool ovsdb_base_type_is_strong_ref( + const struct ovsdb_base_type *); +static inline bool ovsdb_base_type_is_weak_ref(const struct ovsdb_base_type *); /* An OVSDB type. * @@ -160,6 +171,26 @@ ovsdb_atomic_type_is_valid(enum ovsdb_atomic_type atomic_type) return atomic_type >= 0 && atomic_type < OVSDB_N_TYPES; } +static inline bool +ovsdb_base_type_is_ref(const struct ovsdb_base_type *base) +{ + return base->type == OVSDB_TYPE_UUID && base->u.uuid.refTable; +} + +static inline bool +ovsdb_base_type_is_strong_ref(const struct ovsdb_base_type *base) +{ + return (ovsdb_base_type_is_ref(base) + && base->u.uuid.refType == OVSDB_REF_STRONG); +} + +static inline bool +ovsdb_base_type_is_weak_ref(const struct ovsdb_base_type *base) +{ + return (ovsdb_base_type_is_ref(base) + && base->u.uuid.refType == OVSDB_REF_WEAK); +} + static inline bool ovsdb_type_is_scalar(const struct ovsdb_type *type) { return (type->value.type == OVSDB_TYPE_VOID diff --git a/ovsdb/OVSDB.py b/ovsdb/OVSDB.py index 5297229ff..f01c45b68 100644 --- a/ovsdb/OVSDB.py +++ b/ovsdb/OVSDB.py @@ -196,13 +196,14 @@ class Atom: class BaseType: def __init__(self, type, enum=None, - refTable=None, + refTable=None, refType="strong", minInteger=None, maxInteger=None, minReal=None, maxReal=None, minLength=None, maxLength=None): self.type = type self.enum = enum self.refTable = refTable + self.refType = refType self.minInteger = minInteger self.maxInteger = maxInteger self.minReal = minReal @@ -221,17 +222,23 @@ class BaseType: enumType = Type(atomicType, None, 0, 'unlimited') enum = Datum.fromJson(enumType, enum) refTable = getMember(json, 'refTable', [unicode], description) + refType = getMember(json, 'refType', [unicode], description) + if refType == None: + refType = "strong" minInteger = getMember(json, 'minInteger', [int, long], description) maxInteger = getMember(json, 'maxInteger', [int, long], description) minReal = getMember(json, 'minReal', [int, long, float], description) maxReal = getMember(json, 'maxReal', [int, long, float], description) minLength = getMember(json, 'minLength', [int], description) maxLength = getMember(json, 'minLength', [int], description) - return BaseType(atomicType, enum, refTable, minInteger, maxInteger, minReal, maxReal, minLength, maxLength) + return BaseType(atomicType, enum, refTable, refType, minInteger, maxInteger, minReal, maxReal, minLength, maxLength) def toEnglish(self, escapeLiteral=returnUnchanged): if self.type == 'uuid' and self.refTable: - return escapeLiteral(self.refTable) + s = escapeLiteral(self.refTable) + if self.refType == 'weak': + s = "weak reference to " + s + return s else: return self.type diff --git a/ovsdb/SPECS b/ovsdb/SPECS index db504172d..f5d748c03 100644 --- a/ovsdb/SPECS +++ b/ovsdb/SPECS @@ -175,6 +175,7 @@ is represented by , as described below. "minLength": optional, strings only "maxLength": optional, strings only "refTable": optional, uuids only + "refType": "strong" or "weak" optional, only with "refTable" An by itself is equivalent to a JSON object with a single member "type" whose value is the . @@ -203,8 +204,17 @@ is represented by , as described below. bytes or UTF-16 code units). If "type" is "uuid", then "refTable", if present, must be the name - of a table within this database. If "refTable" is set, the - allowed UUIDs are limited to UUIDs for rows in the named table. + of a table within this database. If "refTable" is specified, then + "refType" may also be specified. If "refTable" is set, the effect + depends on "refType": + + - If "refType" is "strong" or if "refType" is omitted, the + allowed UUIDs are limited to UUIDs for rows in the named + table. + + - If "refType" is "weak", then any UUIDs are allowed, but + UUIDs that do not correspond to rows in the named table will + be automatically deleted. "refTable" constraints are "deferred" constraints: they are enforced only at transaction commit time (see the "transact" @@ -337,11 +347,20 @@ include at least the following: When the commit was attempted, a column's value referenced the UUID for a row that did not exist in the table named by the - column's key or value "refTable". (This can be - caused by inserting a row that references a nonexistent row, - by deleting a row that is still referenced by another row, by - specifying the UUID for a row in the wrong table, and other - ways.) + column's key or value "refTable" that has a + "refType" of "strong". (This can be caused by inserting a row + that references a nonexistent row, by deleting a row that is + still referenced by another row, by specifying the UUID for a + row in the wrong table, and other ways.) + + "error": "constraint violation" + + A column with a key or value "refTable" whose + "refType" is "weak" became empty due to deletion(s) caused + because the rows that it referenced were deleted (or never + existed, if the column's row was inserted within the + transaction), and this column is not allowed to be empty + because its has a "min" of 1. If "params" contains one or more "wait" operations, then the transaction may take an arbitrary amount of time to complete. The diff --git a/ovsdb/row.c b/ovsdb/row.c index d088ff98a..5043cbc04 100644 --- a/ovsdb/row.c +++ b/ovsdb/row.c @@ -35,6 +35,8 @@ allocate_row(const struct ovsdb_table *table) struct ovsdb_row *row = xmalloc(row_size); row->table = (struct ovsdb_table *) table; row->txn_row = NULL; + list_init(&row->src_refs); + list_init(&row->dst_refs); row->n_refs = 0; return row; } @@ -77,8 +79,23 @@ ovsdb_row_destroy(struct ovsdb_row *row) { if (row) { const struct ovsdb_table *table = row->table; + struct ovsdb_weak_ref *weak, *next; const struct shash_node *node; + LIST_FOR_EACH_SAFE (weak, next, struct ovsdb_weak_ref, dst_node, + &row->dst_refs) { + list_remove(&weak->src_node); + list_remove(&weak->dst_node); + free(weak); + } + + LIST_FOR_EACH_SAFE (weak, next, struct ovsdb_weak_ref, src_node, + &row->src_refs) { + list_remove(&weak->src_node); + list_remove(&weak->dst_node); + free(weak); + } + SHASH_FOR_EACH (node, &table->schema->columns) { const struct ovsdb_column *column = node->data; ovsdb_datum_destroy(&row->fields[column->index], &column->type); diff --git a/ovsdb/row.h b/ovsdb/row.h index 302f61ab1..6c249a184 100644 --- a/ovsdb/row.h +++ b/ovsdb/row.h @@ -20,20 +20,42 @@ #include #include "column.h" #include "hmap.h" +#include "list.h" #include "ovsdb-data.h" struct ovsdb_column_set; +/* A weak reference. + * + * When a column in row A contains a weak reference to UUID of a row B this + * constitutes a weak reference from A (the source) to B (the destination). + * + * Rows A and B may be in the same table or different tables. + * + * Weak references from a row to itself are allowed, but no "struct + * ovsdb_weak_ref" structures are created for them. + */ +struct ovsdb_weak_ref { + struct list src_node; /* In src->src_refs list. */ + struct list dst_node; /* In destination row's dst_refs list. */ + struct ovsdb_row *src; /* Source row. */ +}; + /* A row in a database table. */ struct ovsdb_row { - struct ovsdb_table *table; /* Table to which this belongs. */ - struct hmap_node hmap_node; /* Element in ovsdb_table's 'rows' hmap. */ + struct ovsdb_table *table; /* Table to which this belongs. */ + struct hmap_node hmap_node; /* Element in ovsdb_table's 'rows' hmap. */ struct ovsdb_txn_row *txn_row; /* Transaction that row is in, if any. */ - /* Number of refs to this row from other rows, in this table or other - * tables, through 'uuid' columns that have a 'refTable' constraint - * pointing to this table. A row with nonzero 'n_refs' cannot be deleted. - * Updated and checked only at transaction commit. */ + /* Weak references. */ + struct list src_refs; /* Weak references from this row. */ + struct list dst_refs; /* Weak references to this row. */ + + /* Number of strong refs to this row from other rows, in this table or + * other tables, through 'uuid' columns that have a 'refTable' constraint + * pointing to this table and a 'refType' of "strong". A row with nonzero + * 'n_refs' cannot be deleted. Updated and checked only at transaction + * commit. */ size_t n_refs; struct ovsdb_datum fields[]; diff --git a/ovsdb/transaction.c b/ovsdb/transaction.c index 8a10d1ed0..2e4c73a82 100644 --- a/ovsdb/transaction.c +++ b/ovsdb/transaction.c @@ -160,7 +160,7 @@ ovsdb_txn_adjust_atom_refs(struct ovsdb_txn *txn, const struct ovsdb_table *table; unsigned int i; - if (base->type != OVSDB_TYPE_UUID || !base->u.uuid.refTable) { + if (!ovsdb_base_type_is_strong_ref(base)) { return NULL; } @@ -270,6 +270,142 @@ ovsdb_txn_row_commit(struct ovsdb_txn *txn OVS_UNUSED, return NULL; } +static void +add_weak_ref(struct ovsdb_txn *txn, + const struct ovsdb_row *src_, const struct ovsdb_row *dst_) +{ + struct ovsdb_row *src = (struct ovsdb_row *) src_; + struct ovsdb_row *dst = (struct ovsdb_row *) dst_; + struct ovsdb_weak_ref *weak; + + if (src == dst) { + return; + } + + dst = ovsdb_txn_row_modify(txn, dst); + + if (!list_is_empty(&dst->dst_refs)) { + /* Omit duplicates. */ + weak = CONTAINER_OF(list_back(&dst->dst_refs), + struct ovsdb_weak_ref, dst_node); + if (weak->src == src) { + return; + } + } + + weak = xmalloc(sizeof *weak); + weak->src = src; + list_push_back(&dst->dst_refs, &weak->dst_node); + list_push_back(&src->src_refs, &weak->src_node); +} + +static struct ovsdb_error * WARN_UNUSED_RESULT +assess_weak_refs(struct ovsdb_txn *txn, struct ovsdb_txn_row *txn_row) +{ + struct ovsdb_table *table; + struct shash_node *node; + + if (txn_row->old) { + /* Mark rows that have weak references to 'txn_row' as modified, so + * that their weak references will get reassessed. */ + struct ovsdb_weak_ref *weak, *next; + + LIST_FOR_EACH_SAFE (weak, next, struct ovsdb_weak_ref, dst_node, + &txn_row->old->dst_refs) { + if (!weak->src->txn_row) { + ovsdb_txn_row_modify(txn, weak->src); + } + } + } + + if (!txn_row->new) { + /* We don't have to do anything about references that originate at + * 'txn_row', because ovsdb_row_destroy() will remove those weak + * references. */ + return NULL; + } + + table = txn_row->new->table; + SHASH_FOR_EACH (node, &table->schema->columns) { + const struct ovsdb_column *column = node->data; + struct ovsdb_datum *datum = &txn_row->new->fields[column->index]; + unsigned int orig_n, i; + bool zero = false; + + orig_n = datum->n; + + if (ovsdb_base_type_is_weak_ref(&column->type.key)) { + for (i = 0; i < datum->n; ) { + const struct ovsdb_row *row; + + row = ovsdb_table_get_row(column->type.key.u.uuid.refTable, + &datum->keys[i].uuid); + if (row) { + add_weak_ref(txn, txn_row->new, row); + i++; + } else { + if (uuid_is_zero(&datum->keys[i].uuid)) { + zero = true; + } + ovsdb_datum_remove_unsafe(datum, i, &column->type); + } + } + } + + if (ovsdb_base_type_is_weak_ref(&column->type.value)) { + for (i = 0; i < datum->n; ) { + const struct ovsdb_row *row; + + row = ovsdb_table_get_row(column->type.value.u.uuid.refTable, + &datum->values[i].uuid); + if (row) { + add_weak_ref(txn, txn_row->new, row); + i++; + } else { + if (uuid_is_zero(&datum->values[i].uuid)) { + zero = true; + } + ovsdb_datum_remove_unsafe(datum, i, &column->type); + } + } + } + + if (datum->n != orig_n) { + bitmap_set1(txn_row->changed, column->index); + ovsdb_datum_sort_assert(datum, column->type.key.type); + if (datum->n < column->type.n_min) { + const struct uuid *row_uuid = ovsdb_row_get_uuid(txn_row->new); + if (zero && !txn_row->old) { + return ovsdb_error( + "constraint violation", + "Weak reference column \"%s\" in \"%s\" row "UUID_FMT + " (inserted within this transaction) contained " + "all-zeros UUID (probably as the default value for " + "this column) but deleting this value caused a " + "constraint volation because this column is not " + "allowed to be empty.", column->name, + table->schema->name, UUID_ARGS(row_uuid)); + } else { + return ovsdb_error( + "constraint violation", + "Deletion of %u weak reference(s) to deleted (or " + "never-existing) rows from column \"%s\" in \"%s\" " + "row "UUID_FMT" %scaused this column to become empty, " + "but constraints on this column disallow an " + "empty column.", + orig_n - datum->n, column->name, table->schema->name, + UUID_ARGS(row_uuid), + (txn_row->old + ? "" + : "(inserted within this transaction) ")); + } + } + } + } + + return NULL; +} + static struct ovsdb_error * WARN_UNUSED_RESULT determine_changes(struct ovsdb_txn *txn, struct ovsdb_txn_row *txn_row) { @@ -330,6 +466,14 @@ ovsdb_txn_commit(struct ovsdb_txn *txn, bool durable) return error; } + /* Check reference counts and remove bad reference for "weak" referential + * integrity. */ + error = for_each_txn_row(txn, assess_weak_refs); + if (error) { + ovsdb_txn_abort(txn); + return error; + } + /* Send the commit to each replica. */ LIST_FOR_EACH (replica, struct ovsdb_replica, node, &txn->db->replicas) { error = (replica->class->commit)(replica, txn, durable); diff --git a/tests/automake.mk b/tests/automake.mk index d6f678391..ebf2a018c 100644 --- a/tests/automake.mk +++ b/tests/automake.mk @@ -215,7 +215,7 @@ tests_test_ovsdb_SOURCES = \ tests/test-ovsdb.c \ tests/idltest.c \ tests/idltest.h -EXTRA_DIST += tests/uuidfilt.pl +EXTRA_DIST += tests/uuidfilt.pl tests/ovsdb-monitor-sort.pl tests_test_ovsdb_LDADD = ovsdb/libovsdb.a lib/libopenvswitch.a $(SSL_LIBS) # idltest schema and IDL diff --git a/tests/ovsdb-execution.at b/tests/ovsdb-execution.at index ed28b2a9d..dc4f3e883 100644 --- a/tests/ovsdb-execution.at +++ b/tests/ovsdb-execution.at @@ -30,6 +30,30 @@ m4_define([CONSTRAINT_SCHEMA], "positive": {"type": {"key": {"type": "integer", "minInteger": 1}}}}}}}]]) +m4_define([WEAK_SCHEMA], + [[{"name": "weak", + "tables": { + "a": { + "columns": { + "a": {"type": "integer"}, + "a2a": {"type": {"key": {"type": "uuid", + "refTable": "a", + "refType": "weak"}, + "min": 0, "max": "unlimited"}}, + "a2a1": {"type": {"key": {"type": "uuid", + "refTable": "a", + "refType": "weak"}}}, + "a2b": {"type": {"key": {"type": "uuid", + "refTable": "b", + "refType": "weak"}}}}}, + "b": { + "columns": { + "b": {"type": "integer"}, + "b2a": {"type": {"key": {"type": "uuid", + "refTable": "a", + "refType": "weak"}, + "min": 0, "max": "unlimited"}}}}}}]]) + # OVSDB_CHECK_EXECUTION(TITLE, SCHEMA, TRANSACTIONS, OUTPUT, [KEYWORDS]) # # Runs "test-ovsdb execute" with the given SCHEMA and each of the @@ -551,6 +575,144 @@ OVSDB_CHECK_EXECUTION([referential integrity -- mutual references], [{"count":1},{"details":"cannot delete a row <0> because of 1 remaining reference(s)","error":"referential integrity violation"}] [{"count":1},{"details":"cannot delete b row <1> because of 1 remaining reference(s)","error":"referential integrity violation"}] [{"count":1},{"count":1}] +]]) + +OVSDB_CHECK_EXECUTION([weak references], + [WEAK_SCHEMA], + [[[["weak", + {"op": "insert", + "table": "a", + "row": {"a": 0, + "a2a": ["set", [["named-uuid", "row1"], + ["named-uuid", "row2"], + ["uuid", "0e767b36-6822-4044-8307-d58467e04669"]]], + "a2a1": ["named-uuid", "row1"], + "a2b": ["named-uuid", "row3"]}, + "uuid-name": "row1"}, + {"op": "insert", + "table": "a", + "row": {"a": 1, + "a2a": ["set", [["named-uuid", "row1"], + ["named-uuid", "row2"]]], + "a2a1": ["named-uuid", "row2"], + "a2b": ["named-uuid", "row3"]}, + "uuid-name": "row2"}, + {"op": "insert", + "table": "a", + "row": {"a": 2, + "a2a": ["set", [["named-uuid", "row1"], + ["named-uuid", "row2"]]], + "a2a1": ["named-uuid", "row2"], + "a2b": ["named-uuid", "row4"]}}, + {"op": "insert", + "table": "b", + "row": {"b": 2, + "b2a": ["named-uuid", "row1"]}, + "uuid-name": "row3"}, + {"op": "insert", + "table": "b", + "row": {"b": 3, + "b2a": ["named-uuid", "row2"]}, + "uuid-name": "row4"}]]], + dnl Check that the nonexistent row UUID we added to row a0 was deleted, + dnl and that other rows were inserted as requested. + [[["weak", + {"op": "select", + "table": "a", + "where": [], + "columns": ["_uuid", "a2a", "a2a1", "a2b"], + "sort": ["a"]}]]], + [[["weak", + {"op": "select", + "table": "b", + "where": [], + "columns": ["_uuid", "b", "b2a"], + "sort": ["b"]}]]], + dnl Try to insert invalid all-zeros weak reference (the default) into + dnl "a2b", which requires exactly one value. + [[["weak", + {"op": "insert", + "table": "a", + "row": {}}]]], + dnl Try to delete row from "b" that is referred to by weak references + dnl from "a" table "a2b" column that requires exactly one value. + [[["weak", + {"op": "delete", + "table": "b", + "where": [["b", "==", 3]]}]]], + dnl Try to delete row from "a" that is referred to by weak references + dnl from "a" table "a2a1" column that requires exactly one value. + [[["weak", + {"op": "delete", + "table": "a", + "where": [["a", "==", 1]]}]]], + dnl Delete the row that had the reference that caused the previous + dnl deletion to fail, then check that other rows are unchanged. + [[["weak", + {"op": "delete", + "table": "a", + "where": [["a", "==", 2]]}]]], + [[["weak", + {"op": "select", + "table": "a", + "where": [], + "columns": ["_uuid", "a2a", "a2a1", "a2b"], + "sort": ["a"]}]]], + [[["weak", + {"op": "select", + "table": "b", + "where": [], + "columns": ["_uuid", "b", "b2a"], + "sort": ["b"]}]]], + dnl Delete row a0 then check that references to it were removed. + [[["weak", + {"op": "delete", + "table": "a", + "where": [["a", "==", 0]]}]]], + [[["weak", + {"op": "select", + "table": "a", + "where": [], + "columns": ["_uuid", "a2a", "a2a1", "a2b"], + "sort": ["a"]}]]], + [[["weak", + {"op": "select", + "table": "b", + "where": [], + "columns": ["_uuid", "b", "b2a"], + "sort": ["b"]}]]], + dnl Delete row a1 then check that references to it were removed. + [[["weak", + {"op": "delete", + "table": "a", + "where": [["a", "==", 1]]}]]], + [[["weak", + {"op": "select", + "table": "a", + "where": [], + "columns": ["_uuid", "a2a", "a2a1", "a2b"], + "sort": ["a"]}]]], + [[["weak", + {"op": "select", + "table": "b", + "where": [], + "columns": ["_uuid", "b", "b2a"], + "sort": ["b"]}]]]], + [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"uuid":["uuid","<2>"]},{"uuid":["uuid","<3>"]},{"uuid":["uuid","<4>"]}] +[{"rows":[{"_uuid":["uuid","<0>"],"a2a":["set",[["uuid","<0>"],["uuid","<1>"]]],"a2a1":["uuid","<0>"],"a2b":["uuid","<3>"]},{"_uuid":["uuid","<1>"],"a2a":["set",[["uuid","<0>"],["uuid","<1>"]]],"a2a1":["uuid","<1>"],"a2b":["uuid","<3>"]},{"_uuid":["uuid","<2>"],"a2a":["set",[["uuid","<0>"],["uuid","<1>"]]],"a2a1":["uuid","<1>"],"a2b":["uuid","<4>"]}]}] +[{"rows":[{"_uuid":["uuid","<3>"],"b":2,"b2a":["uuid","<0>"]},{"_uuid":["uuid","<4>"],"b":3,"b2a":["uuid","<1>"]}]}] +[{"uuid":["uuid","<5>"]},{"details":"Weak reference column \"a2b\" in \"a\" row <5> (inserted within this transaction) contained all-zeros UUID (probably as the default value for this column) but deleting this value caused a constraint volation because this column is not allowed to be empty.","error":"constraint violation"}] +[{"count":1},{"details":"Deletion of 1 weak reference(s) to deleted (or never-existing) rows from column \"a2b\" in \"a\" row <2> caused this column to become empty, but constraints on this column disallow an empty column.","error":"constraint violation"}] +[{"count":1},{"details":"Deletion of 1 weak reference(s) to deleted (or never-existing) rows from column \"a2a1\" in \"a\" row <2> caused this column to become empty, but constraints on this column disallow an empty column.","error":"constraint violation"}] +[{"count":1}] +[{"rows":[{"_uuid":["uuid","<0>"],"a2a":["set",[["uuid","<0>"],["uuid","<1>"]]],"a2a1":["uuid","<0>"],"a2b":["uuid","<3>"]},{"_uuid":["uuid","<1>"],"a2a":["set",[["uuid","<0>"],["uuid","<1>"]]],"a2a1":["uuid","<1>"],"a2b":["uuid","<3>"]}]}] +[{"rows":[{"_uuid":["uuid","<3>"],"b":2,"b2a":["uuid","<0>"]},{"_uuid":["uuid","<4>"],"b":3,"b2a":["uuid","<1>"]}]}] +[{"count":1}] +[{"rows":[{"_uuid":["uuid","<1>"],"a2a":["uuid","<1>"],"a2a1":["uuid","<1>"],"a2b":["uuid","<3>"]}]}] +[{"rows":[{"_uuid":["uuid","<3>"],"b":2,"b2a":["set",[]]},{"_uuid":["uuid","<4>"],"b":3,"b2a":["uuid","<1>"]}]}] +[{"count":1}] +[{"rows":[]}] +[{"rows":[{"_uuid":["uuid","<3>"],"b":2,"b2a":["set",[]]},{"_uuid":["uuid","<4>"],"b":3,"b2a":["set",[]]}]}] ]])]) EXECUTION_EXAMPLES diff --git a/tests/ovsdb-monitor-sort.pl b/tests/ovsdb-monitor-sort.pl new file mode 100755 index 000000000..12034f746 --- /dev/null +++ b/tests/ovsdb-monitor-sort.pl @@ -0,0 +1,49 @@ +#! /usr/bin/perl + +use strict; +use warnings; + +# Breaks lines read from into groups using blank lines as +# group separators, then sorts lines within the groups for +# reproducibility. + +sub compare_lines { + my ($a, $b) = @_; + + my $u = '[0-9a-fA-F]'; + my $uuid_re = "${u}{8}-${u}{4}-${u}{4}-${u}{4}-${u}{12}"; + if ($a =~ /^$uuid_re/) { + if ($b =~ /^$uuid_re/) { + return substr($a, 36) cmp substr($b, 36); + } else { + return 1; + } + } elsif ($b =~ /^$uuid_re/) { + return -1; + } else { + return $a cmp $b; + } +} + +sub output_group { + my (@group) = @_; + print "$_\n" foreach sort { compare_lines($a, $b) } @group; +} + +my @group = (); +while () { + chomp; + if ($_ eq '') { + output_group(@group); + @group = (); + print "\n"; + } else { + if (/^,/ && @group) { + $group[$#group] .= "\n" . $_; + } else { + push(@group, $_); + } + } +} + +output_group(@group) if @group; diff --git a/tests/ovsdb-monitor.at b/tests/ovsdb-monitor.at index 16e247196..0f29a05bf 100644 --- a/tests/ovsdb-monitor.at +++ b/tests/ovsdb-monitor.at @@ -1,6 +1,6 @@ AT_BANNER([OVSDB -- ovsdb-server monitors]) -# OVSDB_CHECK_MONITOR(TITLE, SCHEMA, [PRE-MONITOR-TXN], MONITOR-ARGS, +# OVSDB_CHECK_MONITOR(TITLE, SCHEMA, [PRE-MONITOR-TXN], DB, TABLE, # TRANSACTIONS, OUTPUT, [KEYWORDS]) # # Creates a database with the given SCHEMA, starts an ovsdb-server on @@ -17,29 +17,29 @@ AT_BANNER([OVSDB -- ovsdb-server monitors]) # TITLE is provided to AT_SETUP and KEYWORDS to AT_KEYWORDS. m4_define([OVSDB_CHECK_MONITOR], [AT_SETUP([$1]) - AT_KEYWORDS([ovsdb server monitor positive $7]) + AT_KEYWORDS([ovsdb server monitor positive $8]) AT_DATA([schema], [$2 ]) AT_CHECK([ovsdb-tool create db schema], [0], [stdout], [ignore]) m4_foreach([txn], [$3], [AT_CHECK([ovsdb-tool transact db 'txn'], [0], [ignore], [ignore])]) AT_CHECK([ovsdb-server --detach --pidfile=$PWD/server-pid --remote=punix:socket --unixctl=$PWD/unixctl db], [0], [ignore], [ignore]) - AT_CHECK([ovsdb-client --detach --pidfile=$PWD/client-pid -d json monitor --format=csv unix:socket ordinals $4 > output], + AT_CHECK([ovsdb-client --detach --pidfile=$PWD/client-pid -d json monitor --format=csv unix:socket $4 $5 > output], [0], [ignore], [ignore], [kill `cat server-pid`]) - m4_foreach([txn], [$5], + m4_foreach([txn], [$6], [AT_CHECK([ovsdb-client transact unix:socket 'txn'], [0], [ignore], [ignore], [kill `cat server-pid client-pid`])]) - AT_CHECK([ovsdb-client transact unix:socket '[[]]'], [0], + AT_CHECK([ovsdb-client transact unix:socket '[["$4"]]'], [0], [ignore], [ignore], [kill `cat server-pid client-pid`]) AT_CHECK([ovs-appctl -t $PWD/unixctl -e exit], [0], [ignore], [ignore]) OVS_WAIT_UNTIL([test ! -e server-pid && test ! -e client-pid]) - AT_CHECK([perl $srcdir/uuidfilt.pl output], [0], [$6], [ignore]) + AT_CHECK([perl $srcdir/ovsdb-monitor-sort.pl < output | perl $srcdir/uuidfilt.pl], [0], [$7], [ignore]) AT_CLEANUP]) OVSDB_CHECK_MONITOR([monitor insert into empty table], [ORDINAL_SCHEMA], [], - [ordinals], + [ordinals], [ordinals], [[[["ordinals", {"op": "insert", "table": "ordinals", @@ -54,7 +54,7 @@ OVSDB_CHECK_MONITOR([monitor insert into populated table], {"op": "insert", "table": "ordinals", "row": {"number": 10, "name": "ten"}}]]]], - [ordinals], + [ordinals], [ordinals], [[[["ordinals", {"op": "insert", "table": "ordinals", @@ -72,7 +72,7 @@ OVSDB_CHECK_MONITOR([monitor delete], {"op": "insert", "table": "ordinals", "row": {"number": 10, "name": "ten"}}]]]], - [ordinals], + [ordinals], [ordinals], [[[["ordinals", {"op": "delete", "table": "ordinals", @@ -90,7 +90,7 @@ OVSDB_CHECK_MONITOR([monitor row update], {"op": "insert", "table": "ordinals", "row": {"number": 10, "name": "ten"}}]]]], - [ordinals], + [ordinals], [ordinals], [[[["ordinals", {"op": "update", "table": "ordinals", @@ -110,7 +110,7 @@ OVSDB_CHECK_MONITOR([monitor no-op row updates], {"op": "insert", "table": "ordinals", "row": {"number": 10, "name": "ten"}}]]]], - [ordinals], + [ordinals], [ordinals], [[[["ordinals", {"op": "update", "table": "ordinals", @@ -133,7 +133,7 @@ OVSDB_CHECK_MONITOR([monitor insert-and-update transaction], {"op": "insert", "table": "ordinals", "row": {"number": 10, "name": "ten"}}]]]], - [ordinals], + [ordinals], [ordinals], [[[["ordinals", {"op": "insert", "table": "ordinals", @@ -150,14 +150,13 @@ row,action,name,number,_version <2>,insert,"""three squared""",9,"[""uuid"",""<3>""]" ]]) - OVSDB_CHECK_MONITOR([monitor insert-update-and-delete transaction], [ORDINAL_SCHEMA], [[[["ordinals", {"op": "insert", "table": "ordinals", "row": {"number": 10, "name": "ten"}}]]]], - [ordinals], + [ordinals], [ordinals], [[[["ordinals", {"op": "insert", "table": "ordinals", @@ -180,3 +179,38 @@ row,action,name,number,_version <2>,insert,"""seven""",7,"[""uuid"",""<3>""]" ]]) +OVSDB_CHECK_MONITOR([monitor weak reference change], + [WEAK_SCHEMA], + [[[["weak", + {"op": "insert", + "table": "a", + "row": {"a": 0, + "a2a1": ["named-uuid", "a0"], + "a2b": ["named-uuid", "b2"]}, + "uuid-name": "a0"}, + {"op": "insert", + "table": "a", + "row": {"a": 1, + "a2a": ["named-uuid", "a0"], + "a2a1": ["named-uuid", "a1"], + "a2b": ["named-uuid", "b2"]}, + "uuid-name": "a1"}, + {"op": "insert", + "table": "b", + "row": {"b": 2}, + "uuid-name": "b2"}]]]], + [weak], [a], + [[[["weak", + {"op": "delete", + "table": "a", + "where": [["a", "==", 0]]}]]]], + [[row,action,a,a2a,a2b,a2a1,_version +<0>,initial,0,"[""set"",[]]","[""uuid"",""<1>""]","[""uuid"",""<0>""]","[""uuid"",""<2>""]" +<3>,initial,1,"[""uuid"",""<0>""]","[""uuid"",""<1>""]","[""uuid"",""<3>""]","[""uuid"",""<4>""]" + +row,action,a,a2a,a2b,a2a1,_version +<0>,delete,0,"[""set"",[]]","[""uuid"",""<1>""]","[""uuid"",""<0>""]","[""uuid"",""<2>""]" +<3>,old,,"[""uuid"",""<0>""]",,, +,new,1,"[""set"",[]]","[""uuid"",""<1>""]","[""uuid"",""<3>""]","[""uuid"",""<5>""]" +]]) + diff --git a/tests/uuidfilt.pl b/tests/uuidfilt.pl index 6f003a52e..835f13b53 100755 --- a/tests/uuidfilt.pl +++ b/tests/uuidfilt.pl @@ -13,9 +13,21 @@ sub lookup_uuid { return "<$uuids{$uuid}>"; } +sub sort_set { + my ($s) = @_; + my (@uuids) = sort { $a <=> $b } (grep(/\d+/, split(/(\d+)/, $s))); + return '["set",[' . join(',', map('["uuid","<' . $_ . '>"]', @uuids)) . ']]'; +} + my $u = '[0-9a-fA-F]'; my $uuid_re = "${u}{8}-${u}{4}-${u}{4}-${u}{4}-${u}{12}"; while (<>) { s/($uuid_re)/lookup_uuid($1)/eg; + + # Sort sets like this: + # [["uuid","<1>"],["uuid","<0>"]] + # to look like this: + # [["uuid","<0>"],["uuid","<1>"]] + s/(\["set",\[(,?\["uuid","<\d+>"\])+\]\])/sort_set($1)/ge; print $_; } diff --git a/vswitchd/vswitch.ovsschema b/vswitchd/vswitch.ovsschema index f0217cdaa..c148b6e17 100644 --- a/vswitchd/vswitch.ovsschema +++ b/vswitchd/vswitch.ovsschema @@ -122,11 +122,14 @@ "type": "string"}, "select_src_port": { "type": {"key": {"type": "uuid", - "refTable": "Port"}, + "refTable": "Port", + "refType": "weak"}, "min": 0, "max": "unlimited"}}, "select_dst_port": { "type": {"key": {"type": "uuid", - "refTable": "Port"}, "min": 0, "max": "unlimited"}}, + "refTable": "Port", + "refType": "weak"}, + "min": 0, "max": "unlimited"}}, "select_vlan": { "type": {"key": {"type": "integer", "minInteger": 0, @@ -134,7 +137,9 @@ "min": 0, "max": 4096}}, "output_port": { "type": {"key": {"type": "uuid", - "refTable": "Port"}, "min": 0, "max": 1}}, + "refTable": "Port", + "refType": "weak"}, + "min": 0, "max": 1}}, "output_vlan": { "type": {"key": {"type": "integer", "minInteger": 1, -- 2.43.0