2
0
mirror of https://github.com/openvswitch/ovs synced 2025-08-22 01:51:26 +00:00

ovsdb: Add support for referential integrity in the database itself.

This commit is contained in:
Ben Pfaff 2010-02-08 14:09:41 -08:00
parent bd76d25d8b
commit 0d0f05b909
24 changed files with 688 additions and 164 deletions

View File

@ -619,7 +619,10 @@ check_string_constraints(const char *s,
/* 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)

View File

@ -142,6 +142,8 @@ ovsdb_base_type_init(struct ovsdb_base_type *base, enum ovsdb_atomic_type type)
break;
case OVSDB_TYPE_UUID:
base->u.uuid.refTableName = NULL;
base->u.uuid.refTable = NULL;
break;
case OVSDB_N_TYPES:
@ -172,6 +174,9 @@ ovsdb_base_type_clone(struct ovsdb_base_type *dst,
break;
case OVSDB_TYPE_UUID:
if (dst->u.uuid.refTableName) {
dst->u.uuid.refTableName = xstrdup(dst->u.uuid.refTableName);
}
break;
case OVSDB_N_TYPES:
@ -200,6 +205,7 @@ ovsdb_base_type_destroy(struct ovsdb_base_type *base)
break;
case OVSDB_TYPE_UUID:
free(base->u.uuid.refTableName);
break;
case OVSDB_N_TYPES:
@ -263,7 +269,7 @@ ovsdb_base_type_has_constraints(const struct ovsdb_base_type *base)
|| base->u.string.maxLen != UINT_MAX);
case OVSDB_TYPE_UUID:
return false;
return base->u.uuid.refTableName != NULL;
case OVSDB_N_TYPES:
NOT_REACHED();
@ -411,6 +417,17 @@ ovsdb_base_type_from_json(struct ovsdb_base_type *base,
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) {
@ -485,6 +502,10 @@ ovsdb_base_type_to_json(const struct ovsdb_base_type *base)
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:

View File

@ -67,6 +67,11 @@ struct ovsdb_base_type {
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;
};
@ -79,7 +84,8 @@ struct ovsdb_base_type {
#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 *,

View File

@ -209,8 +209,14 @@ is represented by <database-schema>, as described below.
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>
@ -309,9 +315,19 @@ In general, "result" contains some number of successful results,
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

View File

@ -39,17 +39,6 @@ It will be output on an \fB#include\fR line in the source file
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

View File

@ -203,6 +203,9 @@ class BaseType:
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:
@ -219,17 +222,11 @@ 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

View File

@ -17,9 +17,11 @@
#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"
@ -79,6 +81,23 @@ ovsdb_schema_from_file(const char *file_name, struct ovsdb_schema **schemap)
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)
{
@ -120,6 +139,29 @@ 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;
}
@ -148,6 +190,18 @@ ovsdb_schema_to_json(const struct ovsdb_schema *schema)
return json;
}
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)
{
@ -166,6 +220,19 @@ 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;
}

View File

@ -1,4 +1,4 @@
/* 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.
@ -35,6 +35,7 @@ allocate_row(const struct ovsdb_table *table)
struct ovsdb_row *row = xmalloc(row_size);
row->table = (struct ovsdb_table *) table;
row->txn_row = NULL;
row->n_refs = 0;
return row;
}

View File

@ -1,4 +1,4 @@
/* 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.
@ -29,6 +29,13 @@ 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_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[];
};

View File

@ -131,12 +131,169 @@ ovsdb_txn_row_commit(struct ovsdb_txn_row *txn_row)
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) {
@ -215,6 +372,7 @@ ovsdb_txn_row_modify(struct ovsdb_txn *txn, const struct ovsdb_row *ro_row_)
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);

View File

@ -26,6 +26,7 @@ TESTSUITE_AT = \
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 \

View File

@ -7,7 +7,3 @@
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"

View File

@ -1,25 +1,109 @@
{"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
}
}
}
}
}
}

View File

@ -451,7 +451,7 @@ AT_BANNER([ovs-vsctl unit tests -- database commands])
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])],
@ -496,7 +496,7 @@ AT_CHECK([RUN_OVS_VSCTL([remove br br0 other_config 'datapath_id="0123456789ab"'
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])
@ -506,13 +506,13 @@ AT_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`])],
@ -577,13 +577,7 @@ AT_CHECK([RUN_OVS_VSCTL([remove n `cat netflow-uuid` targets '"1.2.3.4:567"'])],
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
@ -595,7 +589,7 @@ dnl The bug is documented in ovs-vsctl.8.
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>

View File

@ -11,6 +11,20 @@ m4_define([ORDINAL_SCHEMA],
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",
@ -376,6 +390,104 @@ OVSDB_CHECK_EXECUTION([insert and update constraints],
[[[{"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

View File

@ -227,55 +227,58 @@ OVSDB_CHECK_IDL([self-linking idl, inconsistent ops],
[['[{"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",
@ -284,24 +287,25 @@ OVSDB_CHECK_IDL([self-linking idl, sets],
'[{"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],
@ -312,11 +316,11 @@ 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
]])

47
tests/ovsdb-schema.at Normal file
View File

@ -0,0 +1,47 @@
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]])

View File

@ -70,6 +70,13 @@ OVSDB_CHECK_NEGATIVE([maxLength must not be negative],
[[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],

View File

@ -41,6 +41,7 @@ m4_include([tests/ovsdb-data.at])
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])

View File

@ -159,6 +159,8 @@ usage(void)
" 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"
@ -1138,6 +1140,19 @@ do_query_distinct(int argc UNUSED, char *argv[])
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[])
{
@ -1773,20 +1788,28 @@ do_idl(int argc, 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);
@ -1833,6 +1856,7 @@ static struct command all_commands[] = {
{ "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 },

View File

@ -444,13 +444,6 @@ as \fB{}\fR, and curly braces may be optionally enclose non-empty maps
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
@ -503,18 +496,14 @@ Sets each \fIcolumn\fR in \fIrecord\fR in \fItable\fR to the empty set
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

View File

@ -2113,16 +2113,11 @@ cmd_clear(struct vsctl_context *ctx)
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++) {
@ -2158,16 +2153,11 @@ post_create(struct vsctl_context *ctx)
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;
@ -2397,12 +2387,12 @@ static const struct vsctl_command_syntax all_commands[] = {
/* 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},
};

View File

@ -7,15 +7,3 @@
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"

View File

@ -6,16 +6,22 @@
"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"},
@ -37,19 +43,29 @@
"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"}},
@ -70,7 +86,9 @@
"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",
@ -148,10 +166,13 @@
"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",
@ -160,7 +181,8 @@
"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",