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:
parent
bd76d25d8b
commit
0d0f05b909
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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 *,
|
||||
|
26
ovsdb/SPECS
26
ovsdb/SPECS
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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[];
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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 \
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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
47
tests/ovsdb-schema.at
Normal 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]])
|
@ -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],
|
||||
|
@ -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])
|
||||
|
@ -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 },
|
||||
|
@ -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
|
||||
|
@ -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},
|
||||
};
|
||||
|
@ -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"
|
||||
|
@ -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",
|
||||
|
Loading…
x
Reference in New Issue
Block a user