ovsdb: Add support for weak references.
authorBen Pfaff <blp@nicira.com>
Mon, 15 Mar 2010 22:41:54 +0000 (15:41 -0700)
committerBen Pfaff <blp@nicira.com>
Wed, 17 Mar 2010 21:24:56 +0000 (14:24 -0700)
13 files changed:
lib/ovsdb-types.c
lib/ovsdb-types.h
ovsdb/OVSDB.py
ovsdb/SPECS
ovsdb/row.c
ovsdb/row.h
ovsdb/transaction.c
tests/automake.mk
tests/ovsdb-execution.at
tests/ovsdb-monitor-sort.pl [new file with mode: 0755]
tests/ovsdb-monitor.at
tests/uuidfilt.pl
vswitchd/vswitch.ovsschema

index df18ee54c1341a8793272e6eefcccc9dc11c6135..b3452dd831b67e4ff2e6d8166554712a583739e6 100644 (file)
@@ -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;
 
index 6f1727ed158d5ab2bd0069bf832dcbc641b6fc33..6903aa83f165fb3eb8b42e3cdfb415cffd737beb 100644 (file)
@@ -44,6 +44,11 @@ struct json *ovsdb_atomic_type_to_json(enum ovsdb_atomic_type);
 \f
 /* 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 *);
 \f
 /* 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
index 5297229ff8a6cb0a6a8365a42031bd129ee9f178..f01c45b681502358657a030c6c93b820a2406680 100644 (file)
@@ -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
 
index db504172dd09a599eda7eca3b188684106522ef0..f5d748c03cb5dd9f33903611adf56222066ebc04 100644 (file)
@@ -175,6 +175,7 @@ is represented by <database-schema>, as described below.
         "minLength": <integer>             optional, strings only
         "maxLength": <integer>             optional, strings only
         "refTable": <id>                   optional, uuids only
+        "refType": "strong" or "weak"      optional, only with "refTable"
 
     An <atomic-type> by itself is equivalent to a JSON object with a
     single member "type" whose value is the <atomic-type>.
@@ -203,8 +204,17 @@ is represented by <database-schema>, 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 <base-type> 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 <base-type> 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 <base-type> 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 <type> 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
index d088ff98a1c6a7e957c7549fbed30dc3cf31448d..5043cbc04bc84037c01d879c9cc1dae403e0fd50 100644 (file)
@@ -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);
index 302f61ab1712f5d316c2799590a591f88a9a28e5..6c249a18400413913d9a85797278e4e786abf4f1 100644 (file)
 #include <stdint.h>
 #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[];
index 8a10d1ed05aa018de7b7701fbf588440f4cc25b6..2e4c73a82b867a922233997b506aad3573fdae29 100644 (file)
@@ -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);
index d6f6783911660942bc320cdf6da94dedbe1c8e3d..ebf2a018c438f8791471d5725e7cfa157035e8b0 100644 (file)
@@ -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
index ed28b2a9d39cac2fe066b78c8bc5eaf4fae92f0b..dc4f3e883cc313948ca1608b95f376bf255ebf2f 100644 (file)
@@ -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 (executable)
index 0000000..12034f7
--- /dev/null
@@ -0,0 +1,49 @@
+#! /usr/bin/perl
+
+use strict;
+use warnings;
+
+# Breaks lines read from <STDIN> 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 (<STDIN>) {
+    chomp;
+    if ($_ eq '') {
+        output_group(@group);
+        @group = ();
+        print "\n";
+    } else {
+        if (/^,/ && @group) {
+            $group[$#group] .= "\n" . $_;
+        } else {
+            push(@group, $_);
+        }
+    }
+}
+
+output_group(@group) if @group;
index 16e2471969a3057064d9ba9f29ee229dc1e439f8..0f29a05bf1ce2a2525406b130f1f0d1f5a014965 100644 (file)
@@ -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>""]"
+]])
+
index 6f003a52eb530c156cde3d267014a09c786f4af5..835f13b5344e02a9316b7bef75e83948016cafab 100755 (executable)
@@ -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 $_;
 }
index f0217cdaad7feb462016b5202c88635415194a5a..c148b6e178866d9103e8d940d3520ff219db2a06 100644 (file)
          "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,
                   "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,