/* Checks whether 'atom' meets the constraints (if any) defined in 'base'.
* (base->type must specify 'atom''s type.) Returns a null pointer if the
- * constraints are met, otherwise an error that explains the violation. */
+ * constraints are met, otherwise an error that explains the violation.
+ *
+ * Checking UUID constraints is deferred to transaction commit time, so this
+ * function does nothing for UUID constraints. */
struct ovsdb_error *
ovsdb_atom_check_constraints(const union ovsdb_atom *atom,
const struct ovsdb_base_type *base)
break;
case OVSDB_TYPE_UUID:
+ base->u.uuid.refTableName = NULL;
+ base->u.uuid.refTable = NULL;
break;
case OVSDB_N_TYPES:
break;
case OVSDB_TYPE_UUID:
+ if (dst->u.uuid.refTableName) {
+ dst->u.uuid.refTableName = xstrdup(dst->u.uuid.refTableName);
+ }
break;
case OVSDB_N_TYPES:
break;
case OVSDB_TYPE_UUID:
+ free(base->u.uuid.refTableName);
break;
case OVSDB_N_TYPES:
|| base->u.string.maxLen != UINT_MAX);
case OVSDB_TYPE_UUID:
- return false;
+ return base->u.uuid.refTableName != NULL;
case OVSDB_N_TYPES:
NOT_REACHED();
error = ovsdb_syntax_error(json, NULL,
"minLength exceeds maxLength");
}
+ } else if (base->type == OVSDB_TYPE_UUID) {
+ const struct json *refTable;
+
+ refTable = ovsdb_parser_member(&parser, "refTable",
+ OP_ID | OP_OPTIONAL);
+ if (refTable) {
+ 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. */
+ }
}
if (error) {
break;
case OVSDB_TYPE_UUID:
+ if (base->u.uuid.refTableName) {
+ json_object_put_string(json, "refTable",
+ base->u.uuid.refTableName);
+ }
break;
case OVSDB_N_TYPES:
unsigned int minLen; /* minLength or 0. */
unsigned int maxLen; /* maxLength or UINT_MAX. */
} string;
+
+ struct ovsdb_uuid_constraints {
+ char *refTableName; /* Name of referenced table, or NULL. */
+ struct ovsdb_table *refTable; /* Referenced table, if available. */
+ } uuid;
} u;
};
#define OVSDB_BASE_STRING_INIT { .type = OVSDB_TYPE_STRING, \
.u.string = { NULL, NULL, NULL, \
0, UINT_MAX } }
-#define OVSDB_BASE_UUID_INIT { .type = OVSDB_TYPE_UUID }
+#define OVSDB_BASE_UUID_INIT { .type = OVSDB_TYPE_UUID, \
+ .u.uuid = { NULL, NULL } }
void ovsdb_base_type_init(struct ovsdb_base_type *, enum ovsdb_atomic_type);
void ovsdb_base_type_clone(struct ovsdb_base_type *,
minLength. String length is measured in characters (not bytes
or UTF-16 code units).
- The contraints on <base-type> are "immediate", enforced
- immediately by each operation.
+ 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.
+
+ "refTable" constraints are "deferred" constraints: they are
+ enforced only at transaction commit time (see the "transact"
+ request below). The other contraints on <base-type> are
+ "immediate", enforced immediately by each operation.
<atomic-type>
possibly followed by an error, in turn followed by enough JSON null
values to match the number of elements in "params". There is one
exception: if all of the operations succeed, but the results cannot be
-committed (e.g. due to I/O errors), then "result" will have one more
-element than "params", with the additional element describing the
-error.
+committed, then "result" will have one more element than "params",
+with the additional element an <error>. The possible "error" strings
+include at least the following:
+
+ "error": "referential integrity violation"
+
+ 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.)
If "params" contains one or more "wait" operations, then the
transaction may take an arbitrary amount of time to complete. The
generated by the C bindings. It should include the bracketing
\fB""\fR or \fB<>\fR.
.
-.IP "\fB""\fBkeyRefTable\fR"" member of <type>"
-A <type> whose \fBkey\fR is \fB"uuid"\fR may have an additional member
-named \fB"keyRefTable"\fR, whose value is a table name. This
-expresses the constraint that keys of the given <type> are UUIDs that
-reference rows in the named table. This allows the IDL to supply a
-structure pointer in place of a raw UUID in its marshalled version of
-the given type.
-.
-.IP "\fB""valueRefTable""\fR member of <type>"
-Analogous to \fB"keyRefTable"\fR in meaning and effect, except that it
-applies to the \fB"value"\fR member of the <type>.
.SS "Commands"
.IP "\fBannotate\fI schema annotations\fR"
Reads \fIschema\fR, which should be a file in JSON format (ordinarily
stmts.append('%s.u.string.minLen = %d;' % (var, self.minLength))
if self.maxLength != None:
stmts.append('%s.u.string.maxLen = %d;' % (var, self.maxLength))
+ elif self.type == 'uuid':
+ if self.refTable != None:
+ stmts.append('%s.u.uuid.refTableName = "%s";' % (var, escapeCString(self.refTable)))
return '\n'.join([indent + stmt for stmt in stmts])
class Type:
else:
keyJson = mustGetMember(json, 'key', [dict, unicode], description)
key = BaseType.fromJson(keyJson, 'key in %s' % description)
- keyRefTable = getMember(json, 'keyRefTable', [unicode], description)
- if keyRefTable:
- key.refTable = keyRefTable
valueJson = getMember(json, 'value', [dict, unicode], description)
if valueJson:
value = BaseType.fromJson(valueJson,
'value in %s' % description)
- valueRefTable = getMember(json, 'valueRefTable', [unicode], description)
- if valueRefTable:
- value.refTable = valueRefTable
else:
value = None
#include "ovsdb.h"
+#include "column.h"
#include "json.h"
#include "ovsdb-error.h"
#include "ovsdb-parser.h"
+#include "ovsdb-types.h"
#include "table.h"
#include "transaction.h"
return NULL;
}
+static struct ovsdb_error * WARN_UNUSED_RESULT
+ovsdb_schema_check_ref_table(const struct ovsdb_column *column,
+ const struct shash *tables,
+ const struct ovsdb_base_type *base,
+ const char *base_name)
+{
+ if (base->type == OVSDB_TYPE_UUID && base->u.uuid.refTableName
+ && !shash_find(tables, base->u.uuid.refTableName)) {
+ return ovsdb_syntax_error(NULL, NULL,
+ "column %s %s refers to undefined table %s",
+ column->name, base_name,
+ base->u.uuid.refTableName);
+ } else {
+ return NULL;
+ }
+}
+
struct ovsdb_error *
ovsdb_schema_from_json(struct json *json, struct ovsdb_schema **schemap)
{
shash_add(&schema->tables, table->name, table);
}
+
+ /* Validate that all refTables refer to the names of tables that exist. */
+ SHASH_FOR_EACH (node, &schema->tables) {
+ struct ovsdb_table_schema *table = node->data;
+ struct shash_node *node2;
+
+ SHASH_FOR_EACH (node2, &table->columns) {
+ struct ovsdb_column *column = node2->data;
+
+ error = ovsdb_schema_check_ref_table(column, &schema->tables,
+ &column->type.key, "key");
+ if (!error) {
+ error = ovsdb_schema_check_ref_table(column, &schema->tables,
+ &column->type.value,
+ "value");
+ }
+ if (error) {
+ ovsdb_schema_destroy(schema);
+ return error;
+ }
+ }
+ }
+
*schemap = schema;
return 0;
}
return json;
}
\f
+static void
+ovsdb_set_ref_table(const struct shash *tables,
+ struct ovsdb_base_type *base)
+{
+ if (base->type == OVSDB_TYPE_UUID && base->u.uuid.refTableName) {
+ struct ovsdb_table *table;
+
+ table = shash_find_data(tables, base->u.uuid.refTableName);
+ base->u.uuid.refTable = table;
+ }
+}
+
struct ovsdb *
ovsdb_create(struct ovsdb_schema *schema)
{
shash_add(&db->tables, node->name, ovsdb_table_create(ts));
}
+ /* Set all the refTables. */
+ SHASH_FOR_EACH (node, &schema->tables) {
+ struct ovsdb_table_schema *table = node->data;
+ struct shash_node *node2;
+
+ SHASH_FOR_EACH (node2, &table->columns) {
+ struct ovsdb_column *column = node2->data;
+
+ ovsdb_set_ref_table(&db->tables, &column->type.key);
+ ovsdb_set_ref_table(&db->tables, &column->type.value);
+ }
+ }
+
return db;
}
-/* Copyright (c) 2009 Nicira Networks
+/* Copyright (c) 2009, 2010 Nicira Networks
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
struct ovsdb_row *row = xmalloc(row_size);
row->table = (struct ovsdb_table *) table;
row->txn_row = NULL;
+ row->n_refs = 0;
return row;
}
-/* Copyright (c) 2009 Nicira Networks
+/* Copyright (c) 2009, 2010 Nicira Networks
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
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. */
+ size_t n_refs;
+
struct ovsdb_datum fields[];
};
ovsdb_row_destroy(txn_row->old);
}
+static struct ovsdb_txn_row *
+find_txn_row(const struct ovsdb_table *table, const struct uuid *uuid)
+{
+ struct ovsdb_txn_row *txn_row;
+
+ if (!table->txn_table) {
+ return NULL;
+ }
+
+ HMAP_FOR_EACH_WITH_HASH (txn_row, struct ovsdb_txn_row, hmap_node,
+ uuid_hash(uuid), &table->txn_table->txn_rows) {
+ const struct ovsdb_row *row;
+
+ row = txn_row->old ? txn_row->old : txn_row->new;
+ if (uuid_equals(uuid, ovsdb_row_get_uuid(row))) {
+ return txn_row;
+ }
+ }
+
+ return NULL;
+}
+
+static void
+ovsdb_txn_adjust_atom_refs(const union ovsdb_atom *atoms, unsigned int n,
+ const struct ovsdb_table *table,
+ int delta, struct ovsdb_error **errorp)
+{
+ unsigned int i;
+
+ for (i = 0; i < n; i++) {
+ const struct uuid *uuid = &atoms[i].uuid;
+ struct ovsdb_txn_row *txn_row = find_txn_row(table, uuid);
+ if (txn_row) {
+ if (txn_row->old) {
+ txn_row->old->n_refs += delta;
+ }
+ if (txn_row->new) {
+ txn_row->new->n_refs += delta;
+ }
+ } else {
+ const struct ovsdb_row *row_ = ovsdb_table_get_row(table, uuid);
+ if (row_) {
+ struct ovsdb_row *row = (struct ovsdb_row *) row_;
+ row->n_refs += delta;
+ } else if (errorp) {
+ if (!*errorp) {
+ *errorp = ovsdb_error("referential integrity violation",
+ "reference to nonexistent row "
+ UUID_FMT, UUID_ARGS(uuid));
+ }
+ } else {
+ NOT_REACHED();
+ }
+ }
+ }
+}
+
+static void
+ovsdb_txn_adjust_row_refs(const struct ovsdb_row *r,
+ const struct ovsdb_column *column, int delta,
+ struct ovsdb_error **errorp)
+{
+ const struct ovsdb_datum *field = &r->fields[column->index];
+ const struct ovsdb_type *type = &column->type;
+
+ if (type->key.type == OVSDB_TYPE_UUID && type->key.u.uuid.refTable) {
+ ovsdb_txn_adjust_atom_refs(field->keys, field->n,
+ type->key.u.uuid.refTable, delta, errorp);
+ }
+ if (type->value.type == OVSDB_TYPE_UUID && type->value.u.uuid.refTable) {
+ ovsdb_txn_adjust_atom_refs(field->values, field->n,
+ type->value.u.uuid.refTable, delta, errorp);
+ }
+}
+
+static struct ovsdb_error * WARN_UNUSED_RESULT
+ovsdb_txn_adjust_ref_counts__(struct ovsdb_txn *txn, int delta)
+{
+ struct ovsdb_txn_table *t;
+ struct ovsdb_error *error;
+
+ error = NULL;
+ LIST_FOR_EACH (t, struct ovsdb_txn_table, node, &txn->txn_tables) {
+ struct ovsdb_table *table = t->table;
+ struct ovsdb_txn_row *r;
+
+ HMAP_FOR_EACH (r, struct ovsdb_txn_row, hmap_node, &t->txn_rows) {
+ struct shash_node *node;
+
+ SHASH_FOR_EACH (node, &table->schema->columns) {
+ const struct ovsdb_column *column = node->data;
+
+ if (r->old) {
+ ovsdb_txn_adjust_row_refs(r->old, column, -delta, NULL);
+ }
+ if (r->new) {
+ ovsdb_txn_adjust_row_refs(r->new, column, delta, &error);
+ }
+ }
+ }
+ }
+ return error;
+}
+
+static void
+ovsdb_txn_rollback_counts(struct ovsdb_txn *txn)
+{
+ ovsdb_error_destroy(ovsdb_txn_adjust_ref_counts__(txn, -1));
+}
+
+static struct ovsdb_error * WARN_UNUSED_RESULT
+ovsdb_txn_commit_ref_counts(struct ovsdb_txn *txn)
+{
+ struct ovsdb_error *error = ovsdb_txn_adjust_ref_counts__(txn, 1);
+ if (error) {
+ ovsdb_txn_rollback_counts(txn);
+ }
+ return error;
+}
+
+static struct ovsdb_error * WARN_UNUSED_RESULT
+update_ref_counts(struct ovsdb_txn *txn)
+{
+ struct ovsdb_error *error;
+ struct ovsdb_txn_table *t;
+
+ error = ovsdb_txn_commit_ref_counts(txn);
+ if (error) {
+ return error;
+ }
+
+ LIST_FOR_EACH (t, struct ovsdb_txn_table, node, &txn->txn_tables) {
+ struct ovsdb_txn_row *r;
+
+ HMAP_FOR_EACH (r, struct ovsdb_txn_row, hmap_node, &t->txn_rows) {
+ if (!r->new && r->old->n_refs) {
+ error = ovsdb_error("referential integrity violation",
+ "cannot delete %s row "UUID_FMT" because "
+ "of %zu remaining reference(s)",
+ t->table->schema->name,
+ UUID_ARGS(ovsdb_row_get_uuid(r->old)),
+ r->old->n_refs);
+ ovsdb_txn_rollback_counts(txn);
+ return error;
+ }
+ }
+ }
+
+ return NULL;
+}
+
struct ovsdb_error *
ovsdb_txn_commit(struct ovsdb_txn *txn, bool durable)
{
struct ovsdb_replica *replica;
struct ovsdb_error *error;
+ error = update_ref_counts(txn);
+ if (error) {
+ ovsdb_txn_abort(txn);
+ return error;
+ }
+
LIST_FOR_EACH (replica, struct ovsdb_replica, node, &txn->db->replicas) {
error = (replica->class->commit)(replica, txn, durable);
if (error) {
struct ovsdb_row *rw_row;
rw_row = ovsdb_row_clone(ro_row);
+ rw_row->n_refs = ro_row->n_refs;
uuid_generate(ovsdb_row_get_version_rw(rw_row));
rw_row->txn_row = ovsdb_txn_row_create(txn, table, ro_row, rw_row);
hmap_replace(&table->rows, &ro_row->hmap_node, &rw_row->hmap_node);
tests/ovsdb-column.at \
tests/ovsdb-table.at \
tests/ovsdb-row.at \
+ tests/ovsdb-schema.at \
tests/ovsdb-condition.at \
tests/ovsdb-mutation.at \
tests/ovsdb-query.at \
s["idlPrefix"] = "idltest_"
s["idlHeader"] = "\"tests/idltest.h\""
-s["tables"]["link1"]["columns"]["k"]["type"]["keyRefTable"] = "link1"
-s["tables"]["link1"]["columns"]["ka"]["type"]["keyRefTable"] = "link1"
-s["tables"]["link1"]["columns"]["l2"]["type"]["keyRefTable"] = "link2"
-s["tables"]["link2"]["columns"]["l1"]["type"]["keyRefTable"] = "link1"
-{"name": "idltest",
- "tables": {
- "simple": {
- "columns": {
- "i": {"type": "integer"},
- "r": {"type": "real"},
- "b": {"type": "boolean"},
- "s": {"type": "string"},
- "u": {"type": "uuid"},
- "ia": {"type": {"key": "integer", "min": 0, "max": "unlimited"}},
- "ra": {"type": {"key": "real", "min": 0, "max": "unlimited"}},
- "ba": {"type": {"key": "boolean", "min": 0, "max": "unlimited"}},
- "sa": {"type": {"key": "string", "min": 0, "max": "unlimited"}},
- "ua": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}}}},
- "link1": {
- "columns": {
- "i": {"type": "integer"},
- "k": {"type": {"key": "uuid"}},
- "ka": {"type": {"key": "uuid",
- "min": 0, "max": "unlimited"}},
- "l2": {"type": {"key": "uuid", "min": 0, "max": 1}}}},
- "link2": {
- "columns": {
- "i": {"type": "integer"},
- "l1": {"type": {"key": "uuid", "min": 0, "max": 1}}}}}}
+{
+ "name": "idltest",
+ "tables": {
+ "link1": {
+ "columns": {
+ "i": {
+ "type": "integer"
+ },
+ "k": {
+ "type": {
+ "key": {
+ "type": "uuid",
+ "refTable": "link1"
+ }
+ }
+ },
+ "ka": {
+ "type": {
+ "key": {
+ "type": "uuid",
+ "refTable": "link1"
+ },
+ "max": "unlimited",
+ "min": 0
+ }
+ },
+ "l2": {
+ "type": {
+ "key": {
+ "type": "uuid",
+ "refTable": "link2"
+ },
+ "min": 0
+ }
+ }
+ }
+ },
+ "link2": {
+ "columns": {
+ "i": {
+ "type": "integer"
+ },
+ "l1": {
+ "type": {
+ "key": {
+ "type": "uuid",
+ "refTable": "link1"
+ },
+ "min": 0
+ }
+ }
+ }
+ },
+ "simple": {
+ "columns": {
+ "b": {
+ "type": "boolean"
+ },
+ "ba": {
+ "type": {
+ "key": "boolean",
+ "max": "unlimited",
+ "min": 0
+ }
+ },
+ "i": {
+ "type": "integer"
+ },
+ "ia": {
+ "type": {
+ "key": "integer",
+ "max": "unlimited",
+ "min": 0
+ }
+ },
+ "r": {
+ "type": "real"
+ },
+ "ra": {
+ "type": {
+ "key": "real",
+ "max": "unlimited",
+ "min": 0
+ }
+ },
+ "s": {
+ "type": "string"
+ },
+ "sa": {
+ "type": {
+ "key": "string",
+ "max": "unlimited",
+ "min": 0
+ }
+ },
+ "u": {
+ "type": "uuid"
+ },
+ "ua": {
+ "type": {
+ "key": "uuid",
+ "max": "unlimited",
+ "min": 0
+ }
+ }
+ }
+ }
+ }
+}
AT_SETUP([database commands -- positive checks])
AT_KEYWORDS([ovs-vsctl])
OVS_VSCTL_SETUP
-AT_CHECK([RUN_OVS_VSCTL([--force create b name=br0])],
+AT_CHECK([RUN_OVS_VSCTL([create b name=br0])],
[0], [stdout], [], [OVS_VSCTL_CLEANUP])
cp stdout out1
AT_CHECK([RUN_OVS_VSCTL([list b])],
AT_CHECK([RUN_OVS_VSCTL([clear br br0 external-ids -- get br br0 external_ids])],
[0], [{}
], [], [OVS_VSCTL_CLEANUP])
-AT_CHECK([RUN_OVS_VSCTL([--force destroy b br0])],
+AT_CHECK([RUN_OVS_VSCTL([destroy b br0])],
[0], [stdout], [], [OVS_VSCTL_CLEANUP])
AT_CHECK([RUN_OVS_VSCTL([list b])],
[0], [], [], [OVS_VSCTL_CLEANUP])
AT_SETUP([database commands -- negative checks])
AT_KEYWORDS([ovs-vsctl])
OVS_VSCTL_SETUP
-AT_CHECK([RUN_OVS_VSCTL([--force create b name=br0])],
+AT_CHECK([RUN_OVS_VSCTL([create b name=br0])],
[0], [ignore], [], [OVS_VSCTL_CLEANUP])
AT_CHECK([RUN_OVS_VSCTL([add-br br1])],
[0], [ignore], [], [OVS_VSCTL_CLEANUP])
AT_CHECK([RUN_OVS_VSCTL([set-controller br1 tcp:127.0.0.1])],
[0], [ignore], [], [OVS_VSCTL_CLEANUP])
-AT_CHECK([RUN_OVS_VSCTL([--force create n targets='"1.2.3.4:567"'])],
+AT_CHECK([RUN_OVS_VSCTL([create n targets='"1.2.3.4:567"'])],
[0], [stdout], [], [OVS_VSCTL_CLEANUP])
cp stdout netflow-uuid
AT_CHECK([RUN_OVS_VSCTL([list n `cat netflow-uuid`])],
AT_CHECK([RUN_OVS_VSCTL([clear n `cat netflow-uuid` targets])],
[1], [], [ovs-vsctl: "clear" operation cannot be applied to column targets of table NetFlow, which is not allowed to be empty
], [OVS_VSCTL_CLEANUP])
-AT_CHECK([RUN_OVS_VSCTL([create b name=br2])],
- [1], [], [ovs-vsctl: "create" requires --force
-], [OVS_VSCTL_CLEANUP])
-AT_CHECK([RUN_OVS_VSCTL([destroy b br0])],
- [1], [], [ovs-vsctl: "destroy" requires --force
-], [OVS_VSCTL_CLEANUP])
-AT_CHECK([RUN_OVS_VSCTL([--force destroy b br2])],
+AT_CHECK([RUN_OVS_VSCTL([destroy b br2])],
[1], [], [ovs-vsctl: no row "br2" in table Bridge
], [OVS_VSCTL_CLEANUP])
OVS_VSCTL_CLEANUP
AT_SETUP([created row UUID is wrong in same execution])
AT_KEYWORDS([ovs-vsctl])
OVS_VSCTL_SETUP
-AT_CHECK([RUN_OVS_VSCTL([--force create Bridge name=br0 -- list b])],
+AT_CHECK([RUN_OVS_VSCTL([create Bridge name=br0 -- list b])],
[0], [stdout], [], [OVS_VSCTL_CLEANUP])
AT_CHECK([perl $srcdir/uuidfilt.pl stdout], [0],
[[<0>
m4_define([CONSTRAINT_SCHEMA],
[[{"name": "constraints",
"tables": {
+ "a": {
+ "columns": {
+ "a": {"type": "integer"},
+ "a2a": {"type": {"key": {"type": "uuid", "refTable": "a"},
+ "min": 0, "max": "unlimited"}},
+ "a2b": {"type": {"key": {"type": "uuid", "refTable": "b"},
+ "min": 0, "max": "unlimited"}}}},
+ "b": {
+ "columns": {
+ "b": {"type": "integer"},
+ "b2a": {"type": {"key": {"type": "uuid", "refTable": "a"},
+ "min": 0, "max": "unlimited"}},
+ "b2b": {"type": {"key": {"type": "uuid", "refTable": "b"},
+ "min": 0, "max": "unlimited"}}}},
"constrained": {
"columns": {
"positive": {"type": {"key": {"type": "integer",
[[[{"details":"0 is less than minimum allowed value 1","error":"constraint violation"}]
[{"details":"-1 is less than minimum allowed value 1","error":"constraint violation"}]
[{"details":"-2 is less than minimum allowed value 1","error":"constraint violation"}]
+]])
+
+OVSDB_CHECK_EXECUTION([referential integrity -- simple],
+ [CONSTRAINT_SCHEMA],
+ [[[[{"op": "insert",
+ "table": "b",
+ "row": {"b": 1},
+ "uuid-name": "brow"},
+ {"op": "insert",
+ "table": "a",
+ "row": {"a": 0,
+ "a2b": ["set", [["named-uuid", "brow"]]]}},
+ {"op": "insert",
+ "table": "a",
+ "row": {"a": 1,
+ "a2b": ["set", [["named-uuid", "brow"]]]}},
+ {"op": "insert",
+ "table": "a",
+ "row": {"a": 2,
+ "a2b": ["set", [["named-uuid", "brow"]]]}}]]],
+ [[[{"op": "delete",
+ "table": "b",
+ "where": []}]]],
+ [[[{"op": "delete",
+ "table": "a",
+ "where": [["a", "==", 0]]}]]],
+ [[[{"op": "delete",
+ "table": "b",
+ "where": []}]]],
+ [[[{"op": "delete",
+ "table": "a",
+ "where": [["a", "==", 1]]}]]],
+ [[[{"op": "delete",
+ "table": "b",
+ "where": []}]]],
+ [[[{"op": "delete",
+ "table": "a",
+ "where": [["a", "==", 2]]}]]],
+ [[[{"op": "delete",
+ "table": "b",
+ "where": []}]]]],
+ [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"uuid":["uuid","<2>"]},{"uuid":["uuid","<3>"]}]
+[{"count":1},{"details":"cannot delete b row <0> because of 3 remaining reference(s)","error":"referential integrity violation"}]
+[{"count":1}]
+[{"count":1},{"details":"cannot delete b row <0> because of 2 remaining reference(s)","error":"referential integrity violation"}]
+[{"count":1}]
+[{"count":1},{"details":"cannot delete b row <0> because of 1 remaining reference(s)","error":"referential integrity violation"}]
+[{"count":1}]
+[{"count":1}]
+]])
+
+OVSDB_CHECK_EXECUTION([referential integrity -- mutual references],
+ [CONSTRAINT_SCHEMA],
+ [[[[{"op": "declare",
+ "uuid-name": "row1"},
+ {"op": "declare",
+ "uuid-name": "row2"},
+ {"op": "insert",
+ "table": "a",
+ "row": {"a": 0,
+ "a2b": ["set", [["named-uuid", "row2"]]],
+ "a2a": ["set", [["named-uuid", "row1"]]]},
+ "uuid-name": "row1"},
+ {"op": "insert",
+ "table": "b",
+ "row": {"b": 1,
+ "b2b": ["set", [["named-uuid", "row2"]]],
+ "b2a": ["set", [["named-uuid", "row1"]]]},
+ "uuid-name": "row2"}]]],
+ [[[{"op": "insert",
+ "table": "a",
+ "row": {"a2b": ["set", [["uuid", "b516b960-5b19-4fc2-bb82-fe1cbd6d0241"]]]}}]]],
+ [[[{"op": "delete",
+ "table": "a",
+ "where": [["a", "==", 0]]}]]],
+ [[[{"op": "delete",
+ "table": "b",
+ "where": [["b", "==", 1]]}]]],
+ dnl Try the deletions again to make sure that the refcounts got rolled back.
+ [[[{"op": "delete",
+ "table": "a",
+ "where": [["a", "==", 0]]}]]],
+ [[[{"op": "delete",
+ "table": "b",
+ "where": [["b", "==", 1]]}]]],
+ [[[{"op": "delete",
+ "table": "a",
+ "where": [["a", "==", 0]]},
+ {"op": "delete",
+ "table": "b",
+ "where": [["b", "==", 1]]}]]]],
+ [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]}]
+[{"uuid":["uuid","<2>"]},{"details":"reference to nonexistent row <3>","error":"referential integrity violation"}]
+[{"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},{"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}]
]])])
EXECUTION_EXAMPLES
[['[{"op": "insert",
"table": "link1",
"row": {"i": 0, "k": ["uuid", "cf197cc5-c8c9-42f5-82d5-c71a9f2cb96b"]}}]' \
- '[{"op": "update",
+ '+[{"op": "insert",
"table": "link1",
- "where": [],
- "row": {"k": ["uuid", "#0#"]}}]' \
+ "uuid-name": "one",
+ "row": {"i": 1, "k": ["named-uuid", "one"]}},
+ {"op": "insert",
+ "table": "link1",
+ "row": {"i": 2, "k": ["named-uuid", "one"]}}]' \
'[{"op": "update",
"table": "link1",
"where": [],
"row": {"k": ["uuid", "c2fca39a-e69a-42a4-9c56-5eca85839ce9"]}}]' \
- '[{"op": "insert",
+ '+[{"op": "delete",
"table": "link1",
- "row": {"i": 1, "k": ["uuid", "52d752a3-b062-4668-9446-d2e0d4a14703"]}}]' \
- '[{"op": "update",
+ "where": [["_uuid", "==", ["uuid", "#1#"]]]}]' \
+ '+[{"op": "delete",
"table": "link1",
- "where": [],
- "row": {"k": ["uuid", "#1#"]}}]' \
+ "where": [["_uuid", "==", ["uuid", "#2#"]]]}]' \
+ '[{"op": "delete",
+ "table": "link1",
+ "where": []}]' \
]],
[[000: empty
-001: {"error":null,"result":[{"uuid":["uuid","<0>"]}]}
-002: i=0 k= ka=[] l2= uuid=<0>
-003: {"error":null,"result":[{"count":1}]}
-004: i=0 k=0 ka=[] l2= uuid=<0>
-005: {"error":null,"result":[{"count":1}]}
-006: i=0 k= ka=[] l2= uuid=<0>
-007: {"error":null,"result":[{"uuid":["uuid","<1>"]}]}
-008: i=0 k= ka=[] l2= uuid=<0>
-008: i=1 k= ka=[] l2= uuid=<1>
-009: {"error":null,"result":[{"count":2}]}
-010: i=0 k=1 ka=[] l2= uuid=<0>
-010: i=1 k=1 ka=[] l2= uuid=<1>
-011: done
+001: {"error":null,"result":[{"uuid":["uuid","<0>"]},{"details":"reference to nonexistent row <1>","error":"referential integrity violation"}]}
+002: {"error":null,"result":[{"uuid":["uuid","<2>"]},{"uuid":["uuid","<3>"]}]}
+003: i=1 k=1 ka=[] l2= uuid=<2>
+003: i=2 k=1 ka=[] l2= uuid=<3>
+004: {"error":null,"result":[{"count":2},{"details":"reference to nonexistent row <4>","error":"referential integrity violation"}]}
+005: {"error":null,"result":[{"count":1},{"details":"cannot delete link1 row <2> because of 1 remaining reference(s)","error":"referential integrity violation"}]}
+006: {"error":null,"result":[{"count":1}]}
+007: i=1 k=1 ka=[] l2= uuid=<2>
+008: {"error":null,"result":[{"count":1}]}
+009: empty
+010: done
]])
OVSDB_CHECK_IDL([self-linking idl, sets],
[],
[['[{"op": "insert",
"table": "link1",
- "row": {"i": 0, "ka": ["set", [["named-uuid", "i0"]]]},
+ "row": {"i": 0, "k": ["named-uuid", "i0"], "ka": ["set", [["named-uuid", "i0"]]]},
"uuid-name": "i0"},
{"op": "insert",
"table": "link1",
- "row": {"i": 1, "ka": ["set", [["named-uuid", "i1"]]]},
+ "row": {"i": 1, "k": ["named-uuid", "i0"], "ka": ["set", [["named-uuid", "i1"]]]},
"uuid-name": "i1"},
{"op": "insert",
"table": "link1",
- "row": {"i": 2, "ka": ["set", [["named-uuid", "i2"]]]},
+ "row": {"i": 2, "k": ["named-uuid", "i0"], "ka": ["set", [["named-uuid", "i2"]]]},
"uuid-name": "i2"},
{"op": "insert",
"table": "link1",
- "row": {"i": 3, "ka": ["set", [["named-uuid", "i3"]]]},
+ "row": {"i": 3, "k": ["named-uuid", "i0"], "ka": ["set", [["named-uuid", "i3"]]]},
"uuid-name": "i3"}]' \
'[{"op": "update",
"table": "link1",
'[{"op": "update",
"table": "link1",
"where": [],
- "row": {"ka": ["set", [["uuid", "#0#"], ["uuid", "88702e78-845b-4a6e-ad08-cf68922ae84a"], ["uuid", "#2#"], ["uuid", "1ac2b12e-b767-4805-a55d-43976e40c465"]]]}}]']],
+ "row": {"ka": ["set", [["uuid", "#0#"], ["uuid", "88702e78-845b-4a6e-ad08-cf68922ae84a"], ["uuid", "#2#"], ["uuid", "1ac2b12e-b767-4805-a55d-43976e40c465"]]]}}]' \
+ '+[{"op": "delete",
+ "table": "link1",
+ "where": []}]']],
[[000: empty
001: {"error":null,"result":[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"uuid":["uuid","<2>"]},{"uuid":["uuid","<3>"]}]}
-002: i=0 k= ka=[0] l2= uuid=<0>
-002: i=1 k= ka=[1] l2= uuid=<1>
-002: i=2 k= ka=[2] l2= uuid=<2>
-002: i=3 k= ka=[3] l2= uuid=<3>
+002: i=0 k=0 ka=[0] l2= uuid=<0>
+002: i=1 k=0 ka=[1] l2= uuid=<1>
+002: i=2 k=0 ka=[2] l2= uuid=<2>
+002: i=3 k=0 ka=[3] l2= uuid=<3>
003: {"error":null,"result":[{"count":4}]}
-004: i=0 k= ka=[0 1 2 3] l2= uuid=<0>
-004: i=1 k= ka=[0 1 2 3] l2= uuid=<1>
-004: i=2 k= ka=[0 1 2 3] l2= uuid=<2>
-004: i=3 k= ka=[0 1 2 3] l2= uuid=<3>
-005: {"error":null,"result":[{"count":4}]}
-006: i=0 k= ka=[0 2] l2= uuid=<0>
-006: i=1 k= ka=[0 2] l2= uuid=<1>
-006: i=2 k= ka=[0 2] l2= uuid=<2>
-006: i=3 k= ka=[0 2] l2= uuid=<3>
-007: done
+004: i=0 k=0 ka=[0 1 2 3] l2= uuid=<0>
+004: i=1 k=0 ka=[0 1 2 3] l2= uuid=<1>
+004: i=2 k=0 ka=[0 1 2 3] l2= uuid=<2>
+004: i=3 k=0 ka=[0 1 2 3] l2= uuid=<3>
+005: {"error":null,"result":[{"count":4},{"details":"reference to nonexistent row <4>","error":"referential integrity violation"}]}
+006: {"error":null,"result":[{"count":4}]}
+007: empty
+008: done
]])
OVSDB_CHECK_IDL([external-linking idl, consistent ops],
"uuid-name": "row0"},
{"op": "insert",
"table": "link1",
- "row": {"i": 1, "l2": ["set", [["named-uuid", "row0"]]]},
+ "row": {"i": 1, "k": ["named-uuid", "row1"], "l2": ["set", [["named-uuid", "row0"]]]},
"uuid-name": "row1"}]']],
[[000: empty
001: {"error":null,"result":[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]}]}
002: i=0 l1= uuid=<0>
-002: i=1 k= ka=[] l2=0 uuid=<1>
+002: i=1 k=1 ka=[] l2=0 uuid=<1>
003: done
]])
--- /dev/null
+AT_BANNER([OVSDB -- schemas])
+
+OVSDB_CHECK_POSITIVE([schema with valid refTables],
+ [[parse-schema \
+ '{"name": "mydb",
+ "tables": {
+ "a": {
+ "columns": {
+ "map": {
+ "type": {
+ "key": {
+ "type": "uuid",
+ "refTable": "b"},
+ "value": {
+ "type": "uuid",
+ "refTable": "a"}}}}},
+ "b": {
+ "columns": {
+ "aRef": {
+ "type": {
+ "key": {
+ "type": "uuid",
+ "refTable": "a"}}}}}}}']],
+ [[{"name":"mydb","tables":{"a":{"columns":{"map":{"type":{"key":{"refTable":"b","type":"uuid"},"value":{"refTable":"a","type":"uuid"}}}}},"b":{"columns":{"aRef":{"type":{"key":{"refTable":"a","type":"uuid"}}}}}}}]])
+
+OVSDB_CHECK_NEGATIVE([schema with invalid refTables],
+ [[parse-schema \
+ '{"name": "mydb",
+ "tables": {
+ "a": {
+ "columns": {
+ "map": {
+ "type": {
+ "key": {
+ "type": "uuid",
+ "refTable": "c"},
+ "value": {
+ "type": "uuid",
+ "refTable": "a"}}}}},
+ "b": {
+ "columns": {
+ "aRef": {
+ "type": {
+ "key": {
+ "type": "uuid",
+ "refTable": "a"}}}}}}}']],
+ [[test-ovsdb: syntax error: column map key refers to undefined table c]])
[[parse-base-type '{"type": "string", "maxLength": -1}']],
[maxLength out of valid range 0 to 4294967295])
+OVSDB_CHECK_POSITIVE([uuid refTable],
+ [[parse-base-type '{"type": "uuid", "refTable": "myTable"}' ]],
+ [{"refTable":"myTable","type":"uuid"}])
+OVSDB_CHECK_NEGATIVE([uuid refTable must be valid id],
+ [[parse-base-type '{"type": "uuid", "refTable": "a-b-c"}' ]],
+ [Type mismatch for member 'refTable'])
+
OVSDB_CHECK_NEGATIVE([void is not a valid base-type],
[[parse-base-type '["void"]' ]], ["void" is not an atomic-type])
OVSDB_CHECK_NEGATIVE(["type" member must be present],
m4_include([tests/ovsdb-column.at])
m4_include([tests/ovsdb-table.at])
m4_include([tests/ovsdb-row.at])
+m4_include([tests/ovsdb-schema.at])
m4_include([tests/ovsdb-condition.at])
m4_include([tests/ovsdb-mutation.at])
m4_include([tests/ovsdb-query.at])
" query-distinct TABLE [ROW,...] [CONDITION,...] COLUMNS\n"
" add each ROW to TABLE, then query and print the rows that\n"
" satisfy each CONDITION and have distinct COLUMNS.\n"
+ " parse-schema JSON\n"
+ " parse JSON as an OVSDB schema, and re-serialize\n"
" transact COMMAND\n"
" execute each specified transactional COMMAND:\n"
" commit\n"
exit(exit_code);
}
+static void
+do_parse_schema(int argc UNUSED, char *argv[])
+{
+ struct ovsdb_schema *schema;
+ struct json *json;
+
+ json = parse_json(argv[1]);
+ check_ovsdb_error(ovsdb_schema_from_json(json, &schema));
+ json_destroy(json);
+ print_and_free_json(ovsdb_schema_to_json(schema));
+ ovsdb_schema_destroy(schema);
+}
+
static void
do_execute(int argc UNUSED, char *argv[])
{
rpc = NULL;
}
+ setvbuf(stdout, NULL, _IOLBF, 0);
+
symtab = ovsdb_symbol_table_create();
for (i = 2; i < argc; i++) {
+ char *arg = argv[i];
struct jsonrpc_msg *request, *reply;
int error;
- seqno = print_updated_idl(idl, rpc, step++, seqno);
+ if (*arg == '+') {
+ /* The previous transaction didn't change anything. */
+ arg++;
+ } else {
+ seqno = print_updated_idl(idl, rpc, step++, seqno);
+ }
- if (!strcmp(argv[i], "reconnect")) {
+ if (!strcmp(arg, "reconnect")) {
printf("%03d: reconnect\n", step++);
ovsdb_idl_force_reconnect(idl);
- } else if (argv[i][0] != '[') {
- idl_set(idl, argv[i], step++);
+ } else if (arg[0] != '[') {
+ idl_set(idl, arg, step++);
} else {
- struct json *json = parse_json(argv[i]);
+ struct json *json = parse_json(arg);
substitute_uuids(json, symtab);
request = jsonrpc_create_request("transact", json, NULL);
error = jsonrpc_transact_block(rpc, request, &reply);
{ "query", 3, 3, do_query },
{ "query-distinct", 4, 4, do_query_distinct },
{ "transact", 1, INT_MAX, do_transact },
+ { "parse-schema", 1, 1, do_parse_schema },
{ "execute", 2, INT_MAX, do_execute },
{ "trigger", 2, INT_MAX, do_trigger },
{ "idl", 1, INT_MAX, do_idl },
as well.
.
.ST "Database Command Syntax"
-.PP
-By default, database commands refuse to make some kinds of
-modifications that could violate database structuring constraints. If
-you are sure that you know what you are doing, use \fB\-\-force\fR to
-override this safety measure. Constraints that are enforced by the
-database server itself, instead of by \fBovs\-vsctl\fR, cannot be
-overridden this way.
.
.IP "\fBlist \fItable \fR[\fIrecord\fR]..."
List the values of all columns of each specified \fIrecord\fR. If no
or empty map, as appropriate. This command applies only to columns
that are allowed to be empty.
.
-.IP "\fB\-\-force create \fItable column\fR[\fB:\fIkey\fR]\fB=\fIvalue\fR..."
+.IP "create \fItable column\fR[\fB:\fIkey\fR]\fB=\fIvalue\fR..."
Creates a new record in \fItable\fR and sets the initial values of
each \fIcolumn\fR. Columns not explicitly set will receive their
default values. Outputs the UUID of the new row.
-.IP
-This command requires the \fB\-\-force\fR option.
.
-.IP "\fB\-\-force \fR[\fB\-\-if\-exists\fR] \fBdestroy \fItable record\fR..."
+.IP "\fR[\fB\-\-if\-exists\fR] \fBdestroy \fItable record\fR..."
Deletes each specified \fIrecord\fR from \fItable\fR. Unless
\fB\-\-if\-exists\fR is specified, each \fIrecord\fRs must exist.
-.IP
-This command requires the \fB\-\-force\fR option.
.SH "EXAMPLES"
Create a new bridge named br0 and add port eth0 to it:
.IP
static void
cmd_create(struct vsctl_context *ctx)
{
- bool force = shash_find(&ctx->options, "--force");
const char *table_name = ctx->argv[1];
const struct vsctl_table_class *table;
const struct ovsdb_idl_row *row;
int i;
- if (!force) {
- vsctl_fatal("\"create\" requires --force");
- }
-
table = get_table(table_name);
row = ovsdb_idl_txn_insert(ctx->txn, table->class);
for (i = 2; i < ctx->argc; i++) {
static void
cmd_destroy(struct vsctl_context *ctx)
{
- bool force = shash_find(&ctx->options, "--force");
bool must_exist = !shash_find(&ctx->options, "--if-exists");
const char *table_name = ctx->argv[1];
const struct vsctl_table_class *table;
int i;
- if (!force) {
- vsctl_fatal("\"destroy\" requires --force");
- }
-
table = get_table(table_name);
for (i = 2; i < ctx->argc; i++) {
const struct ovsdb_idl_row *row;
/* Parameter commands. */
{"get", 3, INT_MAX, cmd_get, NULL, "--if-exists"},
{"list", 1, INT_MAX, cmd_list, NULL, ""},
- {"create", 2, INT_MAX, cmd_create, post_create, "--force"},
- {"destroy", 1, INT_MAX, cmd_destroy, NULL, "--force,--if-exists"},
{"set", 3, INT_MAX, cmd_set, NULL, ""},
{"add", 4, INT_MAX, cmd_add, NULL, ""},
{"remove", 4, INT_MAX, cmd_remove, NULL, ""},
{"clear", 3, INT_MAX, cmd_clear, NULL, ""},
+ {"create", 2, INT_MAX, cmd_create, post_create, ""},
+ {"destroy", 1, INT_MAX, cmd_destroy, NULL, "--if-exists"},
{NULL, 0, 0, NULL, NULL, NULL},
};
s["idlPrefix"] = "ovsrec_"
s["idlHeader"] = "\"vswitchd/vswitch-idl.h\""
-s["tables"]["Open_vSwitch"]["columns"]["bridges"]["type"]["keyRefTable"] = "Bridge"
-s["tables"]["Open_vSwitch"]["columns"]["controller"]["type"]["keyRefTable"] = "Controller"
-s["tables"]["Open_vSwitch"]["columns"]["ssl"]["type"]["keyRefTable"] = "SSL"
-s["tables"]["Bridge"]["columns"]["ports"]["type"]["keyRefTable"] = "Port"
-s["tables"]["Bridge"]["columns"]["mirrors"]["type"]["keyRefTable"] = "Mirror"
-s["tables"]["Bridge"]["columns"]["netflow"]["type"]["keyRefTable"] = "NetFlow"
-s["tables"]["Bridge"]["columns"]["sflow"]["type"]["keyRefTable"] = "sFlow"
-s["tables"]["Bridge"]["columns"]["controller"]["type"]["keyRefTable"] = "Controller"
-s["tables"]["Port"]["columns"]["interfaces"]["type"]["keyRefTable"] = "Interface"
-s["tables"]["Mirror"]["columns"]["select_src_port"]["type"]["keyRefTable"] = "Port"
-s["tables"]["Mirror"]["columns"]["select_dst_port"]["type"]["keyRefTable"] = "Port"
-s["tables"]["Mirror"]["columns"]["output_port"]["type"]["keyRefTable"] = "Port"
"columns": {
"bridges": {
"comment": "Set of bridges managed by the daemon.",
- "type": {"key": "uuid", "min": 0, "max": "unlimited"}},
+ "type": {"key": {"type": "uuid",
+ "refTable": "Bridge"},
+ "min": 0, "max": "unlimited"}},
"controller": {
"comment": "Default Controller used by bridges.",
- "type": {"key": "uuid", "min": 0, "max": 1}},
+ "type": {"key": {"type": "uuid",
+ "refTable": "Controller"},
+ "min": 0, "max": 1}},
"managers": {
"comment": "Remote database clients to which the Open vSwitch's database server should connect or to which it should listen.",
"type": {"key": "string", "min": 0, "max": "unlimited"}},
"ssl": {
"comment": "SSL used globally by the daemon.",
- "type": {"key": "uuid", "min": 0, "max": 1}},
+ "type": {"key": {"type": "uuid",
+ "refTable": "SSL"},
+ "min": 0, "max": 1}},
"next_cfg": {
"comment": "Sequence number for client to increment when it modifies the configuration and wishes to wait for Open vSwitch to finish applying the changes.",
"type": "integer"},
"ephemeral": true},
"ports": {
"comment": "Ports included in the bridge.",
- "type": {"key": "uuid", "min": 0, "max": "unlimited"}},
+ "type": {"key": {"type": "uuid",
+ "refTable": "Port"},
+ "min": 0, "max": "unlimited"}},
"mirrors": {
"comment": "Port mirroring configuration.",
- "type": {"key": "uuid", "min": 0, "max": "unlimited"}},
+ "type": {"key": {"type": "uuid",
+ "refTable": "Mirror"},
+ "min": 0, "max": "unlimited"}},
"netflow": {
"comment": "NetFlow configuration.",
- "type": {"key": "uuid", "min": 0, "max": 1}},
+ "type": {"key": {"type": "uuid",
+ "refTable": "NetFlow"},
+ "min": 0, "max": 1}},
"sflow": {
"comment": "sFlow configuration.",
- "type": {"key": "uuid", "min": 0, "max": 1}},
+ "type": {"key": {"type": "uuid",
+ "refTable": "sFlow"},
+ "min": 0, "max": 1}},
"controller": {
"comment": "OpenFlow controller. If unset, defaults to that specified by the parent Open_vSwitch.",
- "type": {"key": "uuid", "min": 0, "max": 1}},
+ "type": {"key": {"type": "uuid",
+ "refTable": "Controller"},
+ "min": 0, "max": 1}},
"other_config": {
"comment": "Key-value pairs for configuring rarely used bridge features. The currently defined key-value pairs are: \"datapath-id\", exactly 12 hex digits to set the OpenFlow datapath ID to a specific value; \"hwaddr\", exactly 12 hex digits in the form \"XX:XX:XX:XX:XX:XX\" to set the hardware address of the local port and influence the datapath ID.",
"type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}},
"type": "string"},
"interfaces": {
"comment": "The Port's Interfaces. If there is more than one, this is a bonded Port.",
- "type": {"key": "uuid", "min": 1, "max": "unlimited"}},
+ "type": {"key": {"type": "uuid",
+ "refTable": "Interface"},
+ "min": 1, "max": "unlimited"}},
"trunks": {
"comment": "The 802.1Q VLAN(s) that this port trunks. Should be empty if this port trunks all VLAN(s) or if this is not a trunk port.",
"type": {"key": {"type": "integer",
"type": "string"},
"select_src_port": {
"comment": "Ports on which arriving packets are selected for mirroring. If this column and select_dst_port are both empty, then all packets on all ports are selected for mirroring.",
- "type": {"key": "uuid", "min": 0, "max": "unlimited"}},
+ "type": {"key": {"type": "uuid",
+ "refTable": "Port"},
+ "min": 0, "max": "unlimited"}},
"select_dst_port": {
"comment": "Ports on which departing packets are selected for mirroring.",
- "type": {"key": "uuid", "min": 0, "max": "unlimited"}},
+ "type": {"key": {"type": "uuid",
+ "refTable": "Port"}, "min": 0, "max": "unlimited"}},
"select_vlan": {
"comment": "VLANs on which packets are selected for mirroring. An empty set selects packets on all VLANs.",
"type": {"key": {"type": "integer",
"min": 0, "max": 4096}},
"output_port": {
"comment": "Output port for selected packets. Mutually exclusive with output_vlan.",
- "type": {"key": "uuid", "min": 0, "max": 1}},
+ "type": {"key": {"type": "uuid",
+ "refTable": "Port"}, "min": 0, "max": 1}},
"output_vlan": {
"comment": "Output VLAN for selected packets. Mutually exclusive with output_port.",
"type": {"key": {"type": "integer",